update_mismatch_record.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. import json
  2. from time import sleep
  3. from typing import Dict
  4. import re
  5. from openai import OpenAI
  6. import asyncio
  7. import aiohttp
  8. from pydantic import BaseModel
  9. from app.admin.schema.org import CurrentIntentOrgIns
  10. from app.call_center.crud.crud_intent_records import intent_records_dao
  11. from app.call_center.crud.crud_mismatch_records import mismatch_records_dao
  12. from app.call_center.schema.intent_records import GetIntentRecordsDetails
  13. from app.call_center.schema.mismatch_records import GetMismatchRecordsDetails
  14. from common.log import log
  15. from common.oai import send_request_with_retry, generate_json, generate_text, generate_json_by_class
  16. from core.conf import settings
  17. from database.db_mysql import async_db_session
  18. from database.db_redis import redis_client
  19. from utils.serializers import select_as_dict
  20. class Keyword(BaseModel):
  21. user_intent: str
  22. similar_reply: list[str]
  23. keywords: list[str]
  24. regular: list[str]
  25. async def update_mismatch_record():
  26. async with async_db_session.begin() as db:
  27. record = await mismatch_records_dao.get_earliest_record(db)
  28. if not record:
  29. return None
  30. record_data = GetMismatchRecordsDetails(**select_as_dict(record))
  31. log.info(f"[oai] record_data: {record_data}")
  32. log.info(f"[oai] record_data.missed: {record_data.missed}")
  33. # 机构 id
  34. org_id = record_data.org_id
  35. # 从缓存中获取机构信息
  36. key = f'{settings.TOKEN_CALL_REDIS_PREFIX}:{org_id}'
  37. org_json = await redis_client.get(key)
  38. if not org_json:
  39. # 缓存中没有,从数据库中获取
  40. from app.admin.crud.crud_org import org_dao
  41. org = await org_dao.get(db, org_id)
  42. if not org and org.status is not 1:
  43. log.error(f"意向评级时,机构不存在 org_id: {org_id}")
  44. return None
  45. log.info(f"org: {org}")
  46. org_data = CurrentIntentOrgIns(**select_as_dict(org))
  47. # 将数据放进缓存
  48. await redis_client.setex(
  49. key,
  50. settings.JWT_USER_REDIS_EXPIRE_SECONDS,
  51. org_data.model_dump_json(),
  52. )
  53. else:
  54. org_data = CurrentIntentOrgIns(**json.loads(org_json))
  55. # 开始过滤
  56. llm_ignore = record_data.llm_ignore
  57. ignore_response_data = None
  58. if record_data.ignore == 0:
  59. ignore_messages = [
  60. {"role": "system", "content": f"""# 任务介绍
  61. 公司在用语音识别系统巡检电话通话记录时,可能会因为通话时的背景噪音、方言等问题,导致用户的语音内容没有被正确识别,请根据以下通话记录,判断识别到的内容是否无意义或使上下文不通顺,非常感谢!
  62. # 注意事项
  63. 不用考虑机器人的回复,因为即便识别正确,机器人的回复也可能有误
  64. # 输出要求
  65. 1. 如放在上下文中无意义,请回复一个数字0
  66. 2. 如放在上下文中有意义,请回复一个数字1
  67. 3. 请不要回复 0 或 1 以外的文字内容"""
  68. },
  69. {
  70. "role": "user",
  71. "content": f"""# 通话记录
  72. {record_data.chat_history}
  73. # 可能识别有误的内容:{record_data.missed}
  74. """
  75. }
  76. ]
  77. filter_model = "gpt-3.5-turbo"
  78. if org_data.model == "DeepSeek-V3":
  79. filter_model = org_data.model
  80. ignore_response_data = generate_text(org_data.openai_key, org_data.openai_base, filter_model, ignore_messages)
  81. if ignore_response_data and isinstance(ignore_response_data.choices, list) and len(ignore_response_data.choices) > 0:
  82. first_choice = ignore_response_data.choices[0]
  83. if first_choice and first_choice.message:
  84. ignore_content = first_choice.message.content
  85. if ignore_content == "0":
  86. status = 1
  87. llm_ignore = 1
  88. # 推送
  89. url = org_data.mismatch_callback
  90. log.error(f"url: {url}")
  91. if url:
  92. headers = {
  93. "Content-Type": "application/json"
  94. }
  95. data = {
  96. "internal_id": record_data.id,
  97. "external_id": record_data.external_id,
  98. "ignore": record_data.ignore,
  99. "llm_ignore": llm_ignore
  100. }
  101. is_success = await send_request_with_retry(url, data, headers, max_retries=3,
  102. delay_between_retries=2)
  103. if is_success:
  104. status = 2
  105. async with async_db_session.begin() as db:
  106. try:
  107. await mismatch_records_dao.update_llm_ignore(db, record_data.id, llm_ignore,
  108. ignore_response_data.to_dict(),
  109. status)
  110. except Exception as e:
  111. log.error(f"更新意图记录时发生异常:{e}")
  112. finally:
  113. return None
  114. elif ignore_content == "1":
  115. llm_ignore = 2
  116. ignore_response_data = ignore_response_data.to_dict()
  117. # 开始分析
  118. if org_data.model == "gpt-4o":
  119. keyword_messages = [
  120. {"role": "system", "content": f"""# 任务介绍
  121. 公司在用程序巡检电话通话记录时,有些用户的回复内容没有被关键词库识别到,请根据以下通话记录,给出一些类似的回复,以及对关键词库的具体建议,非常感谢!
  122. # 输出要求
  123. 1. 用户意图:结合上下文,首先考虑可能的语音识别错误并纠错,然后分析未识别内容的根本意图,用户说这句话的背后真实目的是什么,结合上下文挖掘用户最深的意图,请不要停留在表面的意思。
  124. 2. 类似回复:首先应包含未识别的内容原文:{record_data.missed}。其次生成尽可能多的类似意图的回复。
  125. 2. 关键词库:从类似回复中抽取特征词,要求有两点:一是特征词应尽可能的准确,即要覆盖住此类问题,又要尽可能避免和其他语境相冲突。二是需要注重通用性,一些专有名词、人名、地名、产品名、公司名等需要排除在外。
  126. 3. 正则表达式:对关键词词进行解耦,用正则表达式进行表示,专注于主要内容,排除次要内容
  127. # 注意事项
  128. 1. 应贴近真实的外呼场景,用户的素质参差不齐,请避免使用任何浮夸的用词,避免使用高级词汇,避免使用任何的礼貌用语或敬语,适当的低素质些,请至少给出20条结果
  129. 2. 通话记录为录音转译,因此可能有错别字或音同字不同的情况(例如:借和接),请根据上下文分析后就成可能的错字错误
  130. 3. 正则关键词库和正则表达式中也应该考虑到音同字不同的情况,且避免使用匹配次数相关的语法如{0,2}"""
  131. },
  132. {
  133. "role": "user",
  134. "content": f"""# 通话记录
  135. {record_data.chat_history}
  136. # 可能识别有误的内容:{record_data.missed}
  137. """
  138. }
  139. ]
  140. response_data = generate_json_by_class(org_data.openai_key, org_data.openai_base, org_data.model, keyword_messages, Keyword)
  141. else:
  142. keyword_messages = [
  143. {"role": "system", "content": f"""# 任务介绍
  144. 公司在用程序巡检电话通话记录时,有些用户的回复内容没有被关键词库识别到,请根据以下通话记录,给出一些类似的回复,以及对关键词库的具体建议,非常感谢!
  145. # 输出要求
  146. 1. 用户意图:结合上下文,首先考虑可能的语音识别错误并纠错,然后分析未识别内容的根本意图,用户说这句话的背后真实目的是什么,结合上下文挖掘用户最深的意图,请不要停留在表面的意思。
  147. 2. 类似回复:首先应包含未识别的内容原文:{record_data.missed}。其次生成尽可能多的类似意图的回复。
  148. 2. 关键词库:从类似回复中抽取特征词,要求有两点:一是特征词应尽可能的准确,即要覆盖住此类问题,又要尽可能避免和其他语境相冲突。二是需要注重通用性,一些专有名词、人名、地名、产品名、公司名等需要排除在外。
  149. 3. 正则表达式:对关键词词进行解耦,用正则表达式进行表示,专注于主要内容,排除次要内容
  150. # 注意事项
  151. 1. 应贴近真实的外呼场景,用户的素质参差不齐,请避免使用任何浮夸的用词,避免使用高级词汇,避免使用任何的礼貌用语或敬语,适当的低素质些,请至少给出20条结果
  152. 2. 通话记录为录音转译,因此可能有错别字或音同字不同的情况(例如:借和接),请根据上下文分析后就成可能的错字错误
  153. 3. 正则关键词库和正则表达式中也应该考虑到音同字不同的情况,且避免使用匹配次数相关的语法如{{0, 2}}"""
  154. },
  155. {
  156. "role": "user",
  157. "content": f"""# 通话记录
  158. {record_data.chat_history}
  159. # 可能识别有误的内容:{record_data.missed}
  160. # 请以下方的json结构输出
  161. {{
  162. user_intent: str
  163. similar_reply: list[str]
  164. keywords: list[str]
  165. regular: list[str]
  166. }}
  167. """
  168. }
  169. ]
  170. response_data = generate_json_by_class(org_data.openai_key, org_data.openai_base, org_data.model, keyword_messages, { "type": "json_object" })
  171. log.info(f"response_data: {response_data}")
  172. log.info(f"response_data.message: {response_data.choices[0].message.content}")
  173. # log.info(f"response_data.message.content: {response_data.message.content}")
  174. if response_data and isinstance(response_data.choices, list) and len(response_data.choices) > 0:
  175. first_choice = response_data.choices[0]
  176. if first_choice and first_choice.message:
  177. response_json = first_choice.message.content
  178. if response_json:
  179. match = re.search(r'\{.*\}', response_json, re.DOTALL)
  180. if not match:
  181. return None
  182. json_str = match.group(0)
  183. intent = json.loads(json_str)
  184. user_intent = intent.get('user_intent')
  185. similar_reply = intent.get('similar_reply')
  186. keywords = intent.get('keywords', [])
  187. regular = intent.get('regular', [])
  188. if user_intent and similar_reply:
  189. status = 1
  190. # 推送
  191. url = org_data.mismatch_callback
  192. log.error(f"url: {url}")
  193. if url:
  194. headers = {
  195. "Content-Type": "application/json"
  196. }
  197. data = {
  198. "internal_id": record_data.id,
  199. "external_id": record_data.external_id,
  200. "ignore": record_data.ignore,
  201. "llm_ignore": llm_ignore,
  202. "user_intent": user_intent,
  203. "similar_reply": similar_reply,
  204. "keywords": keywords,
  205. "regular": regular
  206. }
  207. is_success = await send_request_with_retry(url, data, headers, max_retries=3, delay_between_retries=2)
  208. if is_success:
  209. status = 2
  210. async with async_db_session.begin() as db:
  211. try:
  212. await mismatch_records_dao.update(db, record_data.id, llm_ignore, user_intent, similar_reply, keywords, regular, {"messages": keyword_messages}, response_data.to_dict(), status, ignore_response_data)
  213. except Exception as e:
  214. log.error(f"更新意图记录时发生异常:{e}")