taskbased.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # import sys
  4. # import json
  5. from typing import (Any,
  6. List,
  7. Text,
  8. Dict
  9. )
  10. from scene import Kernel, Msg, Scene
  11. from config import (get_logger,
  12. FILE_PATH,
  13. START,
  14. END,
  15. HANGUP,
  16. TRANSFER,
  17. EMPTY,
  18. UNK,
  19. CONTINUE,
  20. MAX_TIMES_ANSWER,
  21. GENERATED,
  22. FIXED,
  23. # MOUDLES,
  24. FAQ,
  25. PURSUE)
  26. # from importlib import reload
  27. from botloader import Speech
  28. from util import (get_robot_speeches
  29. ,speech_main_contents
  30. ,get_next_code_with_track
  31. ,business_service
  32. ,intent_service
  33. ,nlg_service
  34. )
  35. import re
  36. #
  37. # # from io import StringIO
  38. # from func import get_next_code_with_track
  39. logger = get_logger("taskbased")
  40. # 预加载话术
  41. speeches = Speech.get_speech(FILE_PATH)
  42. class RobotService(Kernel):
  43. def __init__(self, **kwargs):
  44. self.code = kwargs.get("code", "start")
  45. self.times = kwargs.get("times", 1)
  46. self.answer = kwargs.get("answer", [])
  47. self.robot_speech_status = kwargs.get("robot_speech_status", None)
  48. self.robot_speech_interrupt = kwargs.get("robot_speech_interrupt", None)
  49. self.robot_speech = kwargs.get("robot_speech", '') # 机器人话术
  50. self.status = kwargs.get("status", None) # 电话场景通话状态
  51. def next(self, bid, uid, session_id, asr) -> Msg:
  52. # 进入下一节点
  53. questions = speeches[bid]
  54. if self.code == END:
  55. # others bot 跳转结果页
  56. return Msg(END)
  57. elif self.code == START:
  58. code = list(questions)[0]
  59. topic = questions[code]
  60. else:
  61. topic = questions[self.code]
  62. self.code = topic['id']
  63. timeout = topic["waitTime"]
  64. inputType = topic['inputType']
  65. if not topic['tools']:
  66. return Msg(code=self.code,
  67. question=self.robot_speech,
  68. interruptable=self.robot_speech_interrupt,
  69. wait_time=timeout,
  70. action=self.status,
  71. inputType= inputType
  72. )
  73. else:
  74. tools = topic["tools"]
  75. option = business_service(session_id, uid, self.code, tools, asr)
  76. msg = Msg(code=self.code,
  77. option=option,
  78. session=session_id,
  79. asr=asr
  80. )
  81. self.parse(msg, bid, uid, session_id)
  82. return self.next(bid, uid, session_id, msg.asr)
  83. def parse(self, msg, bid, uid, session_id) -> None:
  84. # 根据用户反馈解析数据
  85. questions = speeches[bid]
  86. self.answer.append(dict(code=msg.code,
  87. question=self.robot_speech,
  88. option=msg.option,
  89. nodeName=questions[msg.code]['nodeName'])
  90. )
  91. logger.info("userId: {}, code: {}, question:{}, user_asr:{}, answer:{}" \
  92. .format(uid,
  93. msg.code,
  94. self.robot_speech,
  95. msg.option[-1]['asr'],
  96. msg.option[-1]['title'],
  97. )
  98. )
  99. # 当前节点话术状态选择
  100. robot_speech, robot_speech_status, robot_speech_interrupt = get_robot_speeches(msg, bid, uid, questions)
  101. if msg.code == "101.00" and self.robot_speech and msg.option[-1]['title'] == "未听清":
  102. self.robot_speech = self.robot_speech
  103. else:
  104. self.robot_speech = robot_speech if robot_speech else self.robot_speech
  105. self.robot_speech_status = robot_speech_status
  106. self.robot_speech_interrupt = robot_speech_interrupt
  107. self.code = msg.code
  108. self.judge_over(questions)
  109. if msg.code != END:
  110. self.code = get_next_code_with_track(uid, msg.code, msg.option[-1]['title'], questions)
  111. if len(questions[self.code]['options']) <= 1 or not \
  112. questions[self.code]['options'][0]['title']:
  113. if questions[self.code]['nodeName'] in ["转人工", "操作故障转人工"] or self.code in ["99.01", "99.00"]:
  114. self.status = TRANSFER
  115. else:
  116. self.status = HANGUP
  117. robot_speech, self.robot_speech_interrupt = speech_main_contents(uid, bid, self.code,
  118. questions, msg.option)
  119. else:
  120. self.status = CONTINUE
  121. if self.code != msg.code or msg.option[-1]['title'] == "FAQ":
  122. self.times = 1
  123. else:
  124. self.times +=1
  125. robot_speech, self.robot_speech_interrupt = speech_main_contents(uid, bid, self.code,
  126. questions, msg.option)
  127. if msg.code == "101.00" and self.robot_speech and msg.option[-1]['title'] == "未听清":
  128. self.robot_speech = self.robot_speech
  129. elif robot_speech:
  130. self.robot_speech = robot_speech
  131. @staticmethod
  132. def get_next_node(code: Text = None,
  133. option: Text = None,
  134. _questions: Dict[Any, Any] = None):
  135. """
  136. @param code: node id
  137. @param option: 响应结果
  138. @param _questions: 话术对象
  139. @return:
  140. """
  141. options = _questions[code]['options']
  142. for cell in options:
  143. if option == cell['title']:
  144. return cell['next']
  145. return code
  146. def judge_over(self, _questions: Dict[Any, Any] = None):
  147. # 判断结束(根据目前话术版本,以【节点跳转逻辑】为依据)
  148. options = _questions[self.code]['options']
  149. if len(options) == 1 and not options[0]['title']:
  150. self.code = END
  151. class Dialog(Scene):
  152. def __init__(self, _case=None):
  153. if _case:
  154. self.case=_case
  155. else:
  156. self.case = RobotService()
  157. def dialogue(self, msg, bid, uid, session_id) -> Msg:
  158. # 驱动对话机器人跳转
  159. # 会话开始
  160. logger.info(f"节点:{msg.code}, 话术:{msg.question}, 用户asr:{msg.asr}, 用户意图:{msg.option}")
  161. if msg.code == "start":
  162. self.case.parse(msg, bid, uid, session_id)
  163. return self.case.next(bid, uid, session_id, msg.asr)
  164. # 触发下一条问答
  165. self.case.parse(msg, bid, uid, session_id)
  166. return self.case.next(bid, uid, session_id, msg.asr)
  167. def get_current_code(self, bid=None):
  168. # 当前节点code
  169. if bid not in speeches:
  170. logger.info("({}) is not exist".format(bid))
  171. questions = speeches[bid]
  172. if self.case.code == START:
  173. return list(questions)[0]
  174. return self.case.code
  175. def get_current_content(self, bid=None, uid=None):
  176. # 当前播报话术
  177. from util import nlg_service
  178. questions = speeches[bid]
  179. cur_code = self.get_current_code(bid)
  180. topic = questions[cur_code]
  181. node_name = topic['nodeName']
  182. main_question = topic['mainContent']
  183. main_type = topic['mainType']
  184. main_interrupt = topic['mainInterrupt']
  185. timeout = topic["waitTime"]
  186. inputType = topic["inputType"]
  187. next_act = HANGUP if self.case.code == END else CONTINUE
  188. if main_type == GENERATED:
  189. # TODO 生成话术
  190. self.case.robot_speech = nlg_service(uid, bid, node_name, 0)
  191. # self.case.robot_speech = main_question
  192. elif main_type == FIXED:
  193. self.case.robot_speech = main_question
  194. cur = self.case.robot_speech
  195. return Msg(code=cur_code,
  196. question=self.case.robot_speech,
  197. interruptable=main_interrupt,
  198. wait_time=timeout,
  199. action=next_act,
  200. inputType=inputType
  201. )
  202. def mapping_semantics(self,
  203. code: Text = None,
  204. asr: Text = None,
  205. bid: Text = None,
  206. uid: Text = None,
  207. sessionid: Text = None):
  208. """
  209. """
  210. pattern = r'DTMF(.*?)DTMF'
  211. matches = re.findall(pattern, asr, re.DOTALL)
  212. if matches:
  213. asr = re.sub("[()]", "", matches[-1])
  214. else:
  215. asr = asr.split("###")[-1]
  216. asr = asr.strip(r""""$%&'()*+,,-./:;<=>?@[\]^_`{|}~。??!""")
  217. # 有按键,提取按键内容
  218. questions = speeches[bid]
  219. res = []
  220. options = questions[code]['options']
  221. node_name = questions[code]['nodeName']
  222. max_times = questions[code]['maxTimes']
  223. main_type = questions[code]['mainType']
  224. if not self.case.robot_speech:
  225. if main_type == GENERATED:
  226. self.case.robot_speech = nlg_service(uid, bid, node_name, 0)
  227. elif main_type == FIXED:
  228. self.case.robot_speech = questions[code]['mainContent']
  229. for cell in options:
  230. if asr in cell['commonReply']:
  231. tmp = dict(title=cell['title'],
  232. asr=asr,
  233. isFaq=False,
  234. faqContent='')
  235. res.append(tmp)
  236. if not res and code not in ["2.10", "3.10", "4.10", "2.40", "3.40", "4.40", "5.10", "5.40"]:
  237. intent_res = intent_service(node_name, asr, bid, code, uid, sessionid)
  238. logger.info(f"intent_res:{intent_res}")
  239. if intent_res:
  240. for cell in options:
  241. for intent in intent_res:
  242. if cell['title'] == intent['intent'] or (cell['title'] == 'FAQ' and intent['isFaq']):
  243. res.append(dict(title=cell['title'],
  244. asr=asr,
  245. isFaq=True if intent['isFaq'] else False,
  246. faqContent=intent['ans'] if intent['isFaq'] else '',
  247. firstclass = intent.get('class') if intent.get('class') else '',
  248. subclass = intent.get('subclass') if intent.get('subclass')
  249. else '',
  250. )
  251. )
  252. logger.info("语义模型映射结果:{}".format(res))
  253. return res
  254. if not res:
  255. res.append(dict(title=UNK, asr=asr, isFaq=False, faqContent=''))
  256. else:
  257. return res
  258. if (self.case.times >= int(max_times) + 1) and int(max_times) != 0:
  259. res.append(dict(title=MAX_TIMES_ANSWER, asr=asr, isFaq=False, faqContent=''))
  260. return res