evaluate_reply_agent.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. import json
  2. import datetime
  3. import random
  4. import traceback
  5. import concurrent.futures
  6. from tqdm import tqdm
  7. from openai import OpenAI
  8. from pymysql.cursors import DictCursor
  9. from pqai_agent.database import MySQLManager
  10. evaluation_metrics_dict = {
  11. "1.2": "是否识别关键信息",
  12. "1.3": "是否能够理解歧义词/模糊词",
  13. "1.4": "是否能理解表情包,图片消息",
  14. "1.5": "是否能理解语音/方言",
  15. "2.1": "回复是否与用户意图相关",
  16. "2.2": "回复是否清晰简洁",
  17. "2.3": "回复是否流畅",
  18. "2.4": "回复语法是否规范",
  19. "3.1": "是否能理解代词(他,她, 她, 这个那个)",
  20. "3.2": "是否能延续上文话题",
  21. "3.3": "是否记住上文的基础信息",
  22. "3.4": "是否及时结束聊天",
  23. "4.1": "是否讨论超出角色认知范围的信息",
  24. "4.2": "是否讨论了不符合当前时代背景的语言、物品、事件、概念",
  25. "4.3": "是否表现出与agent 人设相符的专业知识、生活经验或者常识",
  26. "5.1": "agent 的言行是否反映其预设的核心性格",
  27. "5.2": "agent 的价值观和道德观是否符合其预设标准",
  28. "6.1": "agent 使用的词汇、句式、语法复杂度、行话/俚语是否符合其身份、教育背景和时代?",
  29. "6.2": "agent 语气、语调(恭敬、傲慢、亲切、疏离、热情、冷淡)是否稳定?",
  30. "6.3": "agent 表达习惯、口头禅是否符合角色预设特点",
  31. "7.1": "agent 在对话中表现出的目标、关注重心是否与其设定的核心动机一致?",
  32. "8.1": "agent 是否按照预设的互动模式与用户沟通",
  33. "8.2": "agent 是否对自身角色有正确理解",
  34. "8.3": "agent 是否回复超越用户认知的信息"
  35. }
  36. def fetch_deepseek_completion(prompt, output_type='text'):
  37. """
  38. deep_seek方法
  39. """
  40. client = OpenAI(
  41. api_key='sk-cfd2df92c8864ab999d66a615ee812c5',
  42. base_url="https://api.deepseek.com"
  43. )
  44. # get response format
  45. if output_type == "json":
  46. response_format = {"type": "json_object"}
  47. else:
  48. response_format = {"type": "text"}
  49. chat_completion = client.chat.completions.create(
  50. messages=[
  51. {
  52. "role": "user",
  53. "content": prompt,
  54. }
  55. ],
  56. model="deepseek-reasoner",
  57. response_format=response_format,
  58. )
  59. response = chat_completion.choices[0].message.content
  60. if output_type == "json":
  61. response_json = json.loads(response)
  62. return response_json
  63. return response
  64. def get_profile_info(user_id_, user_type):
  65. match user_type:
  66. case "user":
  67. sql = f"""
  68. select iconurl as 'avatar', profile_data_v1 as 'profile'
  69. from third_party_user where third_party_user_id = %s;
  70. """
  71. case "staff":
  72. sql = f"""
  73. select agent_profile as 'profile'
  74. from qywx_employee where third_party_user_id = %s;
  75. """
  76. case _:
  77. raise ValueError("user_type must be 'user' or 'staff'")
  78. return mysql_client.select(sql, cursor_type=DictCursor, args=(user_id_,))
  79. def evaluate_reply_agent_prompt(dialogue_history, message, user_profile_, agent_profile, push_time):
  80. """
  81. :param dialogue_history:
  82. :param message:
  83. :param user_profile_:
  84. :param agent_profile:
  85. :return:
  86. """
  87. output_format = {
  88. "1.1": {
  89. "score": 1,
  90. "reason": "理由"
  91. },
  92. "1.2": {
  93. "score": 0,
  94. "reason": "理由"
  95. }
  96. }
  97. prompt_ = f"""
  98. **评估任务说明:**
  99. 你需要对 agent 当前回复的消息(message)进行质量评估。
  100. 请基于以下输入信息:
  101. - 历史对话记录:dialogue_history
  102. - 用户预设信息:user_profile
  103. - agent 预设信息:agent_profile
  104. - 消息发送时间:push_time
  105. 结合以下指标打分,**每个子指标满分 1 分**:
  106. **评估维度与示例说明:**
  107. ### 1. 理解能力
  108. - **1.1 是否识别用户核心意图**
  109. - 正例:用户:这款适合老人吗?→ agent:是的,它字体更大、操作简单
  110. - 负例:回复:“颜色有红蓝两种” → 偏离意图
  111. - **1.2 是否识别关键信息**
  112. - 正例:用户提到“糖尿病”,agent 结合健康推荐产品
  113. - 负例:忽略关键信息,只介绍型号/库存
  114. - **1.3 是否理解歧义词或模糊表达**
  115. - 正例:用户说“那个不错”,agent 明确“您是指X产品吗?”
  116. - 负例:直接“感谢喜欢”,未澄清
  117. - **1.4 是否理解表情/图片**
  118. - 正例:用户发 👍 → agent 回复“收到,我帮您下单”
  119. - 负例:用户发 🙄 → agent 回复“感谢支持”
  120. - **1.5 是否理解语音/方言(转写内容)**
  121. - 正例:“想搞个便宜点的” → 理解为追求性价比
  122. - 负例:回复“我们不卖便宜货” → 理解偏差
  123. ### 2. 回复能力
  124. - **2.1 回复是否与用户意图相关**
  125. - 正例:用户问退货 → agent 回复具体流程
  126. - 负例:agent 回复“本店新品推荐”
  127. - **2.2 回复是否清晰简洁**
  128. - 正例:“退货可在APP内申请,我们会上门取件”
  129. - 负例:“嗯这个如果说退货吧,其实我们也可以...”
  130. - **2.3 回复是否流畅**
  131. - 正例:语言通顺无跳跃
  132. - 负例:表达混乱,“如果你申请,我帮你弄好,那样能退款也可以”
  133. - **2.4 回复语法是否规范**
  134. - 正例:“欢迎再次光临”
  135. - 负例:“我帮你处理了这个东西您可以看下有没有不对的”
  136. - **2.5 回复是否具有机械性**
  137. - 正例:回复的语句需要保持正常聊天风格
  138. - 负例:每次回复消息均包含用户称呼等属于首次聊天需要用到的称呼语句
  139. ### 3. 上下文管理能力
  140. - **3.1 是否正确理解代词**
  141. - 正例:用户:“他说不错” → agent 理解“他”为儿子
  142. - 负例:理解为用户本人
  143. - **3.2 是否延续上文话题**
  144. - 正例:上轮聊智能手表 → 本轮继续其功能
  145. - 负例:突然推广耳机
  146. - **3.4 是否能及时结束对话**
  147. - 正例:用户说“好的谢谢” → agent 回复“有需要随时联系”
  148. - 负例:用户已表达结束意图 → agent 仍持续推销
  149. ### 4. 背景知识一致性
  150. - **4.1 是否超出角色认知范围**
  151. - 正例:AI客服:推荐就医 → 建议联系医生
  152. - 负例:自称能诊断病症
  153. - **4.2 是否使用错误时代背景或过时词汇**
  154. - 正例:使用当下流行产品/概念
  155. - 负例:讨论 BP 机、DVD 机等
  156. - **4.3 是否展现出与角色设定一致的知识/经验**
  157. - 正例:金融顾问角色能清晰解释基金风险
  158. - 负例:理财助手说“我也不太懂”
  159. ### 5. 性格行为一致性
  160. - **5.1 言行是否体现预设性格**
  161. - 正例:设定为“亲切” → 用词温和,如“亲爱的、别担心~”
  162. - 负例:忽冷忽热,或说话带攻击性
  163. - **5.2 价值观与道德是否一致**
  164. - 正例:拒绝不当请求、拒绝传播敏感信息
  165. - 负例:发表不当政治/色情暗示 / 赌博相关
  166. ### 6. 语言风格一致性
  167. - **6.1 用词语法是否匹配身份背景**
  168. - 正例:医生角色用专业术语适度表达, 年长角色说话语气和小姑娘小孩子一样
  169. - 负例:医生说“这个就随便吃点吧”, 65岁的角色说“好呀,好呢”这样的话
  170. - **6.2 语气是否保持稳定**
  171. - 正例:始终热情/恭敬/冷静
  172. - 负例:开头热情,后文冷淡
  173. - **6.3 是否保持角色表达习惯**
  174. - 正例:师傅型角色口头禅“照您说的办”
  175. - 负例:前后说话风格差异明显
  176. ### 7. 目标动机一致性
  177. - **7.1 是否体现核心目标**
  178. - 正例:核心是为了和用户保持联系,为用户提供情绪价值
  179. - 负例:一味推销
  180. ### 8. 关系认知一致性
  181. - **8.1 是否遵循角色与用户之间的互动模式**
  182. - 正例:设定为“助理”→ 使用“我来为您处理”, 用户是男性,使用“先生”称呼
  183. - 负例:助理语气“我说了算”,过于权威,用户是男性,使用“女士”称呼
  184. - **8.2 是否正确理解自己身份**
  185. - 正例:客服表明“我可以帮您提交”
  186. - 负例:自称“我是系统管理员”
  187. - **8.3 是否回复超越用户可理解范围**
  188. - 正例:面向老人用简洁语言解释
  189. - 负例:一上来即使用技术术语堆叠回复
  190. **评估规则:**
  191. - 每个子项:
  192. - 符合要求:1 分
  193. - 不符合要求:0 分
  194. - 未涉及/不适用:1 分,理由写“无需评估”
  195. - 每项后附简要中文评估理由,客观明确。
  196. **输入:**
  197. - **对话历史**: {dialogue_history}
  198. - **Agent 预设信息**: {agent_profile}
  199. - **用户预设信息**: {user_profile_}
  200. - **Agent 消息**: {message}
  201. - **Agent 发送消息时间**:{push_time}
  202. **输出格式要求:JSON 格式**
  203. 输出格式参考:{output_format}
  204. """
  205. return prompt_
  206. config = {
  207. 'host': 'rm-bp13g3ra2f59q49xs.mysql.rds.aliyuncs.com',
  208. 'port': 3306,
  209. 'user': 'wqsd',
  210. 'password': 'wqsd@2025',
  211. 'database': 'ai_agent',
  212. 'charset': 'utf8mb4'
  213. }
  214. mysql_client = MySQLManager(config)
  215. if __name__ == '__main__':
  216. import pqai_agent.logging_service
  217. pqai_agent.logging_service.setup_root_logger()
  218. with open("reply_data_set_filter_2.json", "r", encoding="utf-8") as f:
  219. data = json.load(f)
  220. data = [i for i in data if i['user_active_rate'] > 0.4]
  221. print(len(data))
  222. # 随机选择100个对话
  223. dialogues = random.sample(data, 80)
  224. dialogue_with_profile = []
  225. for dialogue in dialogues:
  226. agent_profile = get_profile_info(dialogue['staff_id'], 'staff')
  227. user_profile = get_profile_info(dialogue['user_id'], 'user')
  228. dialogue['agent_profile'] = json.loads(agent_profile[0]['profile'])
  229. dialogue['user_profile'] = json.loads(user_profile[0]['profile'])
  230. dialogue_with_profile.append(dialogue)
  231. F = []
  232. errors = []
  233. from threading import Lock
  234. import concurrent.futures
  235. write_lock = Lock()
  236. def process_sample(sub_dialogues):
  237. try:
  238. message = sub_dialogues["conversation"]
  239. agent_message = sub_dialogues["reply_msg"]
  240. push_time = sub_dialogues["reply_time"]
  241. user_profile = sub_dialogues["user_profile"]
  242. staff_profile = sub_dialogues["agent_profile"]
  243. if not agent_message:
  244. return None
  245. prompt = evaluate_reply_agent_prompt(
  246. message, agent_message, user_profile, staff_profile, push_time
  247. )
  248. response = fetch_deepseek_completion(prompt, output_type='json')
  249. return {
  250. "user_profile": user_profile,
  251. "agent_profile": staff_profile,
  252. "dialogue_history": message,
  253. "push_message": agent_message,
  254. "push_time": push_time,
  255. "evaluation_result": response
  256. }
  257. except Exception as e:
  258. # 捕获异常并存储
  259. error_msg = f"Error processing sample: {e}\n{traceback.format_exc()}"
  260. with write_lock:
  261. errors.append(error_msg)
  262. return None
  263. # 使用线程池处理
  264. with concurrent.futures.ThreadPoolExecutor(max_workers=8) as executor:
  265. # 提交所有任务
  266. futures = {executor.submit(process_sample, sample): sample for sample in dialogues}
  267. # 使用tqdm创建进度条
  268. for future in tqdm(concurrent.futures.as_completed(futures), total=len(dialogues), desc="Evaluating"):
  269. result = future.result()
  270. if result:
  271. with write_lock:
  272. F.append(result)
  273. # 打印处理过程中遇到的错误
  274. if errors:
  275. print(f"\nEncountered {len(errors)} errors during processing:")
  276. for error in errors[:5]: # 最多打印前5个错误
  277. print(error)
  278. # 保存结果
  279. with open("push_message_evaluation_result_7.json", "w", encoding="utf-8") as f:
  280. json.dump(F, f, ensure_ascii=False, indent=4)