evaluate_agent_v2.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. import concurrent
  2. import datetime
  3. import json
  4. import time
  5. from tqdm import tqdm
  6. from openai import OpenAI
  7. from typing import List, Dict
  8. from pymysql.cursors import DictCursor
  9. from pqai_agent.database import MySQLManager
  10. from pqai_agent.logging_service import logger
  11. from pqai_agent import configs, logging_service
  12. logging_service.setup_root_logger()
  13. def fetch_deepseek_completion(prompt, output_type="text"):
  14. """
  15. deep_seek方法
  16. """
  17. client = OpenAI(
  18. api_key="sk-cfd2df92c8864ab999d66a615ee812c5",
  19. base_url="https://api.deepseek.com",
  20. )
  21. # get response format
  22. if output_type == "json":
  23. response_format = {"type": "json_object"}
  24. else:
  25. response_format = {"type": "text"}
  26. chat_completion = client.chat.completions.create(
  27. messages=[
  28. {
  29. "role": "user",
  30. "content": prompt,
  31. }
  32. ],
  33. model="deepseek-chat",
  34. response_format=response_format,
  35. )
  36. response = chat_completion.choices[0].message.content
  37. if output_type == "json":
  38. response_json = json.loads(response)
  39. return response_json
  40. return response
  41. class AgentEvaluator:
  42. def __init__(self) -> None:
  43. config = {
  44. "host": "rm-bp13g3ra2f59q49xs.mysql.rds.aliyuncs.com",
  45. "port": 3306,
  46. "user": "wqsd",
  47. "password": "wqsd@2025",
  48. "database": "ai_agent",
  49. "charset": "utf8mb4",
  50. }
  51. self.mysql_client = MySQLManager(config)
  52. self.output_format = {
  53. "1.1": {
  54. "score": 1,
  55. "reason": "理由"
  56. },
  57. "1.2": {
  58. "score": 0,
  59. "reason": "理由"
  60. }
  61. }
  62. def get_profile_info(self, user_id_, user_type):
  63. match user_type:
  64. case "user":
  65. sql = f"""
  66. select iconurl as 'avatar', profile_data_v1 as 'profile'
  67. from third_party_user where third_party_user_id = %s;
  68. """
  69. case "staff":
  70. sql = f"""
  71. select agent_profile as 'profile'
  72. from qywx_employee where third_party_user_id = %s;
  73. """
  74. case _:
  75. raise ValueError("user_type must be 'user' or 'staff'")
  76. return self.mysql_client.select(sql, cursor_type=DictCursor, args=(user_id_,))
  77. class PushMessageEvaluator(AgentEvaluator):
  78. def generate_prompt(self, dialogue_history: List[Dict], message: str,
  79. send_time: str, user_profile: Dict, agent_profile: Dict) -> str:
  80. """
  81. 生成评估prompt
  82. :return: prompt
  83. """
  84. prompt = f"""
  85. **评估任务说明:**
  86. **任务场景**: agent和用户超过一段时间没有对话,agent 主动推送消息(message),希望能够与用户保持联系。
  87. **评估任务**:请基于以下输入信息:
  88. - 历史对话记录:dialogue_history
  89. - 用户预设信息:user_profile
  90. - agent 预设信息:agent_profile
  91. - 消息发送时间:send_time
  92. 结合以下评估指标对 message 的质量进行打分
  93. **每个子指标满分 1 分**:
  94. **评估维度与示例说明:**
  95. ### 1. 理解能力
  96. - **1.1 能否感知上文用户情绪**
  97. - 正例:对于用户喜欢的内容可以多提,对于用户不喜欢的内容可以少提
  98. - 负例:唤起时不考虑用户情绪,常规唤起
  99. ### 2. 上下文管理能力
  100. - **2.1 是否延续上文话题**
  101. - 正例:用户和 agent 上文在聊健康相关问题,push 消息可以继续聊健康相关话题
  102. - 负例:push 消息不延续上文话题,而是聊其他话题
  103. - **2.1 是否记住上文信息**
  104. - 正例:上文用户已经提到了“糖尿病”,push 消息可以继续聊糖尿病相关话题
  105. - 负例:上文提到了“糖尿病”,push 消息突然聊运动或其他话题
  106. ### 3. 背景知识一致性
  107. - **3.1 是否超出角色认知范围**
  108. - 正例:AI客服:推荐就医 → 建议联系医生
  109. - 负例:自称能诊断病症
  110. - **3.2 是否使用错误时代背景或过时词汇**
  111. - 正例:使用当下流行产品/概念
  112. - 负例:讨论 BP 机、DVD 机等
  113. - **3.3 是否展现出与角色设定一致的知识/经验**
  114. - 正例:金融顾问角色能清晰解释基金风险
  115. - 负例:理财助手说“我也不太懂”, 教师职业提供天气预报等不相关话题
  116. ### 4. 性格行为一致性
  117. - **4.1 言行是否体现预设性格**
  118. - 正例:言语风格和人设设定一直;若未设定性格,则根据年龄性别职业等评估
  119. - 负例:忽冷忽热,或说话带攻击性
  120. - **4.2 价值观与道德是否一致**
  121. - 正例:拒绝不当请求、拒绝传播敏感信息
  122. - 负例:发表不当政治 / 色情暗示 / 赌博相关
  123. ### 5. 语言风格一致性
  124. - **5.1 用词语法是否匹配身份背景**
  125. - 正例:医生角色用专业术语适度表达, 年长角色说话语气和小姑娘小孩子一样
  126. - 负例:医生说“这个就随便吃点吧”, 65岁的角色说“好呀,好呢”这样的话
  127. - **5.2 语气是否保持稳定**
  128. - 正例:始终热情/恭敬/冷静
  129. - 负例:开头热情,后文冷淡
  130. - **5.3 是否保持角色表达习惯**
  131. - 正例:师傅型角色口头禅“照您说的办”
  132. - 负例:前后说话风格差异明显
  133. - **5.4 agent 表达风格是否符合 agent 的年龄,职业特色**
  134. - 正例:严肃的职业讲话不应该嘻嘻哈哈,男性讲话不能女性化
  135. - 负例:男性角色讲话用词过于温柔,不符合人设相关
  136. ### 6. 关系认知一致性
  137. - **6.1 是否遵循角色与用户之间的互动模式**
  138. - 正例:设定为“助理”→ 使用“我来为您处理”, 用户是男性,使用“先生”称呼
  139. - 负例:助理语气“我说了算”,过于权威,用户是男性,使用“女士”称呼
  140. - **6.2 是否正确理解自己身份**
  141. - 正例:客服表明“我可以帮您提交”
  142. - 负例:自称“我是系统管理员”
  143. - **6.3 是否回复超越用户可理解范围**
  144. - 正例:面向老人用简洁语言解释
  145. - 负例:一上来即使用技术术语堆叠回复
  146. - **6.4 agent 在对话过程中是否表现为真实的人类**
  147. - 正例:agent 任何场景都不能告诉用户自己是 AI
  148. - 负例:agent 向用户透露自己是 AI 助理
  149. ### 7. 对话唤起
  150. - **7.1 agent的唤起消息是否风格相似,机械感高*
  151. - 正例:agent 主动唤起的消息风格在符合用户兴趣爱好,迎合用户人设的前提下,需要有丰富的表达
  152. - 负例:agent 多次唤起的消息风格相似,比如每次都是 称呼+早上/下午/晚上好 + 用户兴趣相关 之类的公式化,机械化表达
  153. - **7.2 agent push 信息是否关注用户的兴趣、健康状态、昵称、偏好称呼、地域等信息*
  154. - 正例:用户喜欢打篮球,agent 在 push 的时候可以提到篮球相关,
  155. - 负例:用户喜欢看种花,push 消息提到体育,用户地域在珠海,push 消息提到大连
  156. - **7.3 agent 消息是否解决上文遗留的合理问题或需求,若上文没有提到则无需评估*
  157. - 正例:对于健康助手agent,如果用户提到了想了解“养生”相关的知识,上文回复不够完全的可以在 push 的时候提出
  158. - 负例:上文遗留的合理问题需求没有参考,或者回复一些不合理需求(参考 4.2 价值观)
  159. - **7.4 push 消息是否明确表现出唤起对话聊天的意图**
  160. - 正例:agent 为了保持和用户的联系,主动 push 消息,明确表达出继续聊天的意图
  161. - 负例:agent push 的消息没有体现出继续聊天的意图,而是表达了其他的话题
  162. - **7.5 push 唤起若提到农历节日祝福,是否是在节日前**
  163. 通过 Agent 发送消息的时间计算出农历日期,然后判断改农历日期和日期的先后关系
  164. - 正例:发送日期对应的农历日期 < 农历节日
  165. - 负例:发送日期对应的农历日期 > 农历节日
  166. **评估规则:**
  167. - 每个子项:
  168. - 符合要求:1 分
  169. - 不符合要求:0 分
  170. - 未涉及/不适用:1 分,理由写“无需评估”
  171. - 每项后附简要中文评估理由,客观明确, 如果是节日日期相关,把节日日期也展示。
  172. **输入:**
  173. - **dialogue_history**: {dialogue_history}
  174. - **agent_profile**: {agent_profile}
  175. - **user_profile**: {user_profile}
  176. - **message**: {message}
  177. - **send_time**:{send_time}
  178. **输出格式要求:JSON 格式**
  179. 输出格式参考:{self.output_format}
  180. """
  181. return prompt
  182. def evaluate_task(self, line):
  183. conversation_length = len(line["conversation"])
  184. if conversation_length > 5:
  185. push_time = line["conversation"][-1]["timestamp"] + 48 * 3600
  186. evaluator_prompt = self.generate_prompt(
  187. dialogue_history=line["conversation"],
  188. message=line["push_msg"],
  189. send_time=push_time,
  190. agent_profile=line["agent_profile"],
  191. user_profile=line["user_profile"],
  192. )
  193. print(evaluator_prompt)
  194. response = fetch_deepseek_completion(evaluator_prompt, output_type='json')
  195. return {
  196. "user_profile": line["user_profile"],
  197. "agent_profile": line["agent_profile"],
  198. "dialogue_history": line["conversation"],
  199. "push_message": line["push_msg"],
  200. "push_time": push_time,
  201. "evaluation_result": response
  202. }
  203. return None
  204. def evaluate(self):
  205. # data = data[:8]
  206. # from concurrent.futures import ThreadPoolExecutor
  207. # from tqdm import tqdm
  208. # # # 多线程处理主逻辑
  209. # L = []
  210. # with ThreadPoolExecutor(max_workers=8) as executor: # 可根据CPU核心数调整worker数量
  211. # futures = []
  212. # for line in data:
  213. # futures.append(executor.submit(self.evaluate_task, line))
  214. #
  215. # # 使用tqdm显示进度
  216. # for future in tqdm(concurrent.futures.as_completed(futures), total=len(futures)):
  217. # result = future.result()
  218. # if result:
  219. # L.append(result)
  220. for line in tqdm(data):
  221. response = self.evaluate_task(line)
  222. # if response:
  223. # L.append(response)
  224. #
  225. # # 保存结果(与原代码相同)
  226. # with open("push_message_evaluation_result_0613_24_v2.json", "w", encoding="utf-8") as f:
  227. # json.dump(L, f, ensure_ascii=False, indent=4)
  228. if __name__ == "__main__":
  229. PushMessageEvaluator().evaluate()