258 lines
9.0KB

  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(self.api_key, self.secret_key)
  71. payload = ""
  72. headers = {"Content-Type": "application/json", "Accept": "application/json"}
  73. response = requests.request("POST", url, headers=headers, data=payload)
  74. # print(response.text)
  75. return response.json()["access_token"]
  76. def getUnit(self, query):
  77. """
  78. NLU 解析version 3.0
  79. :param query: 用户的指令字符串
  80. :returns: UNIT 解析结果。如果解析失败,返回 None
  81. """
  82. url = "https://aip.baidubce.com/rpc/2.0/unit/service/v3/chat?access_token=" + self.access_token
  83. request = {
  84. "query": query,
  85. "user_id": str(get_mac())[:32],
  86. "terminal_id": "88888",
  87. }
  88. body = {
  89. "log_id": str(uuid.uuid1()),
  90. "version": "3.0",
  91. "service_id": self.service_id,
  92. "session_id": str(uuid.uuid1()),
  93. "request": request,
  94. }
  95. try:
  96. headers = {"Content-Type": "application/json"}
  97. response = requests.post(url, json=body, headers=headers)
  98. return json.loads(response.text)
  99. except Exception:
  100. return None
  101. def getUnit2(self, query):
  102. """
  103. NLU 解析 version 2.0
  104. :param query: 用户的指令字符串
  105. :returns: UNIT 解析结果。如果解析失败,返回 None
  106. """
  107. url = "https://aip.baidubce.com/rpc/2.0/unit/service/chat?access_token=" + self.access_token
  108. request = {"query": query, "user_id": str(get_mac())[:32]}
  109. body = {
  110. "log_id": str(uuid.uuid1()),
  111. "version": "2.0",
  112. "service_id": self.service_id,
  113. "session_id": str(uuid.uuid1()),
  114. "request": request,
  115. }
  116. try:
  117. headers = {"Content-Type": "application/json"}
  118. response = requests.post(url, json=body, headers=headers)
  119. return json.loads(response.text)
  120. except Exception:
  121. return None
  122. def getIntent(self, parsed):
  123. """
  124. 提取意图
  125. :param parsed: UNIT 解析结果
  126. :returns: 意图数组
  127. """
  128. if parsed and "result" in parsed and "response_list" in parsed["result"]:
  129. try:
  130. return parsed["result"]["response_list"][0]["schema"]["intent"]
  131. except Exception as e:
  132. logger.warning(e)
  133. return ""
  134. else:
  135. return ""
  136. def hasIntent(self, parsed, intent):
  137. """
  138. 判断是否包含某个意图
  139. :param parsed: UNIT 解析结果
  140. :param intent: 意图的名称
  141. :returns: True: 包含; False: 不包含
  142. """
  143. if parsed and "result" in parsed and "response_list" in parsed["result"]:
  144. response_list = parsed["result"]["response_list"]
  145. for response in response_list:
  146. if "schema" in response and "intent" in response["schema"] and response["schema"]["intent"] == intent:
  147. return True
  148. return False
  149. else:
  150. return False
  151. def getSlots(self, parsed, intent=""):
  152. """
  153. 提取某个意图的所有词槽
  154. :param parsed: UNIT 解析结果
  155. :param intent: 意图的名称
  156. :returns: 词槽列表。你可以通过 name 属性筛选词槽,
  157. 再通过 normalized_word 属性取出相应的值
  158. """
  159. if parsed and "result" in parsed and "response_list" in parsed["result"]:
  160. response_list = parsed["result"]["response_list"]
  161. if intent == "":
  162. try:
  163. return parsed["result"]["response_list"][0]["schema"]["slots"]
  164. except Exception as e:
  165. logger.warning(e)
  166. return []
  167. for response in response_list:
  168. if "schema" in response and "intent" in response["schema"] and "slots" in response["schema"] and response["schema"]["intent"] == intent:
  169. return response["schema"]["slots"]
  170. return []
  171. else:
  172. return []
  173. def getSlotWords(self, parsed, intent, name):
  174. """
  175. 找出命中某个词槽的内容
  176. :param parsed: UNIT 解析结果
  177. :param intent: 意图的名称
  178. :param name: 词槽名
  179. :returns: 命中该词槽的值的列表。
  180. """
  181. slots = self.getSlots(parsed, intent)
  182. words = []
  183. for slot in slots:
  184. if slot["name"] == name:
  185. words.append(slot["normalized_word"])
  186. return words
  187. def getSayByConfidence(self, parsed):
  188. """
  189. 提取 UNIT 置信度最高的回复文本
  190. :param parsed: UNIT 解析结果
  191. :returns: UNIT 的回复文本
  192. """
  193. if parsed and "result" in parsed and "response_list" in parsed["result"]:
  194. response_list = parsed["result"]["response_list"]
  195. answer = {}
  196. for response in response_list:
  197. if (
  198. "schema" in response
  199. and "intent_confidence" in response["schema"]
  200. and (not answer or response["schema"]["intent_confidence"] > answer["schema"]["intent_confidence"])
  201. ):
  202. answer = response
  203. return answer["action_list"][0]["say"]
  204. else:
  205. return ""
  206. def getSay(self, parsed, intent=""):
  207. """
  208. 提取 UNIT 的回复文本
  209. :param parsed: UNIT 解析结果
  210. :param intent: 意图的名称
  211. :returns: UNIT 的回复文本
  212. """
  213. if parsed and "result" in parsed and "response_list" in parsed["result"]:
  214. response_list = parsed["result"]["response_list"]
  215. if intent == "":
  216. try:
  217. return response_list[0]["action_list"][0]["say"]
  218. except Exception as e:
  219. logger.warning(e)
  220. return ""
  221. for response in response_list:
  222. if "schema" in response and "intent" in response["schema"] and response["schema"]["intent"] == intent:
  223. try:
  224. return response["action_list"][0]["say"]
  225. except Exception as e:
  226. logger.warning(e)
  227. return ""
  228. return ""
  229. else:
  230. return ""