選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

283 行
9.5KB

  1. # encoding:utf-8
  2. import json
  3. import os
  4. import uuid
  5. from uuid import getnode as get_mac
  6. import requests
  7. import plugins
  8. from bridge.context import ContextType
  9. from bridge.reply import Reply, ReplyType
  10. from common.log import logger
  11. from plugins import *
  12. """利用百度UNIT实现智能对话
  13. 如果命中意图,返回意图对应的回复,否则返回继续交付给下个插件处理
  14. """
  15. @plugins.register(
  16. name="BDunit",
  17. desire_priority=0,
  18. hidden=True,
  19. desc="Baidu unit bot system",
  20. version="0.1",
  21. author="jackson",
  22. )
  23. class BDunit(Plugin):
  24. def __init__(self):
  25. super().__init__()
  26. try:
  27. curdir = os.path.dirname(__file__)
  28. config_path = os.path.join(curdir, "config.json")
  29. conf = None
  30. if not os.path.exists(config_path):
  31. raise Exception("config.json not found")
  32. else:
  33. with open(config_path, "r") as f:
  34. conf = json.load(f)
  35. self.service_id = conf["service_id"]
  36. self.api_key = conf["api_key"]
  37. self.secret_key = conf["secret_key"]
  38. self.access_token = self.get_token()
  39. self.handlers[Event.ON_HANDLE_CONTEXT] = self.on_handle_context
  40. logger.info("[BDunit] inited")
  41. except Exception as e:
  42. logger.warn("[BDunit] init failed, ignore ")
  43. raise e
  44. def on_handle_context(self, e_context: EventContext):
  45. if e_context["context"].type != ContextType.TEXT:
  46. return
  47. content = e_context["context"].content
  48. logger.debug("[BDunit] on_handle_context. content: %s" % content)
  49. parsed = self.getUnit2(content)
  50. intent = self.getIntent(parsed)
  51. if intent: # 找到意图
  52. logger.debug("[BDunit] Baidu_AI Intent= %s", intent)
  53. reply = Reply()
  54. reply.type = ReplyType.TEXT
  55. reply.content = self.getSay(parsed)
  56. e_context["reply"] = reply
  57. e_context.action = EventAction.BREAK_PASS # 事件结束,并跳过处理context的默认逻辑
  58. else:
  59. e_context.action = EventAction.CONTINUE # 事件继续,交付给下个插件或默认逻辑
  60. def get_help_text(self, **kwargs):
  61. help_text = "本插件会处理询问实时日期时间,天气,数学运算等问题,这些技能由您的百度智能对话UNIT决定\n"
  62. return help_text
  63. def get_token(self):
  64. """获取访问百度UUNIT 的access_token
  65. #param api_key: UNIT apk_key
  66. #param secret_key: UNIT secret_key
  67. Returns:
  68. string: access_token
  69. """
  70. url = "https://aip.baidubce.com/oauth/2.0/token?client_id={}&client_secret={}&grant_type=client_credentials".format(
  71. self.api_key, self.secret_key
  72. )
  73. payload = ""
  74. headers = {"Content-Type": "application/json", "Accept": "application/json"}
  75. response = requests.request("POST", url, headers=headers, data=payload)
  76. # print(response.text)
  77. return response.json()["access_token"]
  78. def getUnit(self, query):
  79. """
  80. NLU 解析version 3.0
  81. :param query: 用户的指令字符串
  82. :returns: UNIT 解析结果。如果解析失败,返回 None
  83. """
  84. url = (
  85. "https://aip.baidubce.com/rpc/2.0/unit/service/v3/chat?access_token="
  86. + self.access_token
  87. )
  88. request = {
  89. "query": query,
  90. "user_id": str(get_mac())[:32],
  91. "terminal_id": "88888",
  92. }
  93. body = {
  94. "log_id": str(uuid.uuid1()),
  95. "version": "3.0",
  96. "service_id": self.service_id,
  97. "session_id": str(uuid.uuid1()),
  98. "request": request,
  99. }
  100. try:
  101. headers = {"Content-Type": "application/json"}
  102. response = requests.post(url, json=body, headers=headers)
  103. return json.loads(response.text)
  104. except Exception:
  105. return None
  106. def getUnit2(self, query):
  107. """
  108. NLU 解析 version 2.0
  109. :param query: 用户的指令字符串
  110. :returns: UNIT 解析结果。如果解析失败,返回 None
  111. """
  112. url = (
  113. "https://aip.baidubce.com/rpc/2.0/unit/service/chat?access_token="
  114. + self.access_token
  115. )
  116. request = {"query": query, "user_id": str(get_mac())[:32]}
  117. body = {
  118. "log_id": str(uuid.uuid1()),
  119. "version": "2.0",
  120. "service_id": self.service_id,
  121. "session_id": str(uuid.uuid1()),
  122. "request": request,
  123. }
  124. try:
  125. headers = {"Content-Type": "application/json"}
  126. response = requests.post(url, json=body, headers=headers)
  127. return json.loads(response.text)
  128. except Exception:
  129. return None
  130. def getIntent(self, parsed):
  131. """
  132. 提取意图
  133. :param parsed: UNIT 解析结果
  134. :returns: 意图数组
  135. """
  136. if parsed and "result" in parsed and "response_list" in parsed["result"]:
  137. try:
  138. return parsed["result"]["response_list"][0]["schema"]["intent"]
  139. except Exception as e:
  140. logger.warning(e)
  141. return ""
  142. else:
  143. return ""
  144. def hasIntent(self, parsed, intent):
  145. """
  146. 判断是否包含某个意图
  147. :param parsed: UNIT 解析结果
  148. :param intent: 意图的名称
  149. :returns: True: 包含; False: 不包含
  150. """
  151. if parsed and "result" in parsed and "response_list" in parsed["result"]:
  152. response_list = parsed["result"]["response_list"]
  153. for response in response_list:
  154. if (
  155. "schema" in response
  156. and "intent" in response["schema"]
  157. and response["schema"]["intent"] == intent
  158. ):
  159. return True
  160. return False
  161. else:
  162. return False
  163. def getSlots(self, parsed, intent=""):
  164. """
  165. 提取某个意图的所有词槽
  166. :param parsed: UNIT 解析结果
  167. :param intent: 意图的名称
  168. :returns: 词槽列表。你可以通过 name 属性筛选词槽,
  169. 再通过 normalized_word 属性取出相应的值
  170. """
  171. if parsed and "result" in parsed and "response_list" in parsed["result"]:
  172. response_list = parsed["result"]["response_list"]
  173. if intent == "":
  174. try:
  175. return parsed["result"]["response_list"][0]["schema"]["slots"]
  176. except Exception as e:
  177. logger.warning(e)
  178. return []
  179. for response in response_list:
  180. if (
  181. "schema" in response
  182. and "intent" in response["schema"]
  183. and "slots" in response["schema"]
  184. and response["schema"]["intent"] == intent
  185. ):
  186. return response["schema"]["slots"]
  187. return []
  188. else:
  189. return []
  190. def getSlotWords(self, parsed, intent, name):
  191. """
  192. 找出命中某个词槽的内容
  193. :param parsed: UNIT 解析结果
  194. :param intent: 意图的名称
  195. :param name: 词槽名
  196. :returns: 命中该词槽的值的列表。
  197. """
  198. slots = self.getSlots(parsed, intent)
  199. words = []
  200. for slot in slots:
  201. if slot["name"] == name:
  202. words.append(slot["normalized_word"])
  203. return words
  204. def getSayByConfidence(self, parsed):
  205. """
  206. 提取 UNIT 置信度最高的回复文本
  207. :param parsed: UNIT 解析结果
  208. :returns: UNIT 的回复文本
  209. """
  210. if parsed and "result" in parsed and "response_list" in parsed["result"]:
  211. response_list = parsed["result"]["response_list"]
  212. answer = {}
  213. for response in response_list:
  214. if (
  215. "schema" in response
  216. and "intent_confidence" in response["schema"]
  217. and (
  218. not answer
  219. or response["schema"]["intent_confidence"]
  220. > answer["schema"]["intent_confidence"]
  221. )
  222. ):
  223. answer = response
  224. return answer["action_list"][0]["say"]
  225. else:
  226. return ""
  227. def getSay(self, parsed, intent=""):
  228. """
  229. 提取 UNIT 的回复文本
  230. :param parsed: UNIT 解析结果
  231. :param intent: 意图的名称
  232. :returns: UNIT 的回复文本
  233. """
  234. if parsed and "result" in parsed and "response_list" in parsed["result"]:
  235. response_list = parsed["result"]["response_list"]
  236. if intent == "":
  237. try:
  238. return response_list[0]["action_list"][0]["say"]
  239. except Exception as e:
  240. logger.warning(e)
  241. return ""
  242. for response in response_list:
  243. if (
  244. "schema" in response
  245. and "intent" in response["schema"]
  246. and response["schema"]["intent"] == intent
  247. ):
  248. try:
  249. return response["action_list"][0]["say"]
  250. except Exception as e:
  251. logger.warning(e)
  252. return ""
  253. return ""
  254. else:
  255. return ""