You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

298 lines
9.8KB

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