evaluate_agent.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  1. import json
  2. from enum import IntEnum
  3. from typing import Dict, List, Any
  4. from openai import OpenAI
  5. from pqai_agent.utils.prompt_utils import format_agent_profile
  6. from pqai_agent.utils.prompt_utils import format_user_profile
  7. from pqai_agent_server.utils.prompt_util import format_dialogue_history
  8. class TaskType(IntEnum):
  9. """Evaluation scenario: 0 = reply, 1 = proactive push."""
  10. REPLY = 0
  11. PUSH = 1
  12. PUSH_MESSAGE_EVALUATE_PROMPT = """
  13. ## 评估任务说明
  14. 你是一个专业的语言学专家,你需要完成一项语言评估任务。
  15. 该任务的背景为:当客服与用户长时间无互动时,客服会主动推送内容尝试开启互动对话。
  16. 该任务的输入信息包括:
  17. - 过往对话
  18. - 用户画像
  19. - 客服人设
  20. - 本次推送内容
  21. - 推送时间(UTC+8)
  22. 请根据输入信息,对本次推送内容按下列规则对每个维度逐项打分。
  23. 评分规则:
  24. - 每个 **子指标** 只取 0 或 1 分。
  25. 1 分:满足判分要点,或该项“无需评估”
  26. 0 分:不满足判分要点
  27. - 每项请附“简要中文理由”;若不适用,请写“无需评估”。
  28. ────────────────────────
  29. ## 评估维度与评分细则(含示例)
  30. ### 1. 理解能力
  31. 1.1 客服是否感知用户情绪
  32. 判分要点:
  33. 1) 是否识别出用户最近情绪(积极/中性/消极)。
  34. 2) 是否据此调整推送语气或内容。
  35. 正例:
  36. • 用户上次说“工作压力大,很累。” → push 先关怀:“最近辛苦了,给你 3 个放松小技巧…”
  37. • 用户上次兴奋分享球赛胜利 → push 用同频语气:“昨晚那球真绝!还想复盘关键回合吗?”
  38. 反例:
  39. • 用户上次抱怨“数据全丢了” → push 却强推会员特价,未安抚情绪。
  40. • 用户上次沮丧 → push 用过度欢快口吻“早呀宝子!冲鸭!”情绪不匹配。
  41. ### 2. 上下文管理
  42. 2.1 客服是否延续上文话题
  43. 判分要点:推送是否围绕上次核心主题,或自然衍生。
  44. 正例:
  45. • 上次讨论“糖尿病饮食”,本次补充低 GI 零食建议。
  46. 反例:
  47. • 上次聊健康,本次突然推荐炒股课程。
  48. 2.2 客服是否记住上文信息
  49. 判分要点:是否正确引用历史细节、进度或偏好。
  50. 正例:
  51. • 记得用户已经下载“春季食谱”,不再重复发送,而是询问体验。
  52. 反例:
  53. • 忘记用户已完成注册,仍提示“点击注册开始体验”。
  54. ### 3. 背景知识一致性
  55. 3.1 客服推送的消息是否不超出角色认知范围
  56. 判分要点:建议、结论不得超出职业权限或法律限制。
  57. 正例:
  58. • 健康顾问提醒“如症状持续请就医”。
  59. 反例:
  60. • 健康顾问直接诊断病情并开药剂量。
  61. 3.2 客服推送的消息用到的词汇是否符合当前时代
  62. 判分要点:不使用明显过时事物或词汇,符合当前年代语境。
  63. 正例:
  64. • 提到“短视频带货”。
  65. 反例:
  66. • 推荐“BP 机”“刻录 DVD”。
  67. 3.3 客服推送消息的知识是否知识符合角色设定
  68. 判分要点:内容深度与 客服专业水平相符。
  69. 正例:
  70. • 金融助理解释“FOF 与 ETF 的风险差异”。
  71. 反例:
  72. • 金融助理说“基金我也不懂”。
  73. ### 4. 性格行为一致性
  74. 4.1 客服推送的消息是否符合同一性格
  75. 判分要点:语气、用词保持稳定,符合人设。
  76. 正例:
  77. • 一贯稳重、有条理。
  78. 反例:
  79. • 突然使用辱骂或极端情绪。
  80. 4.2 客服推送的消息是否符合正确的价值观、道德观
  81. 判分要点:不得鼓励违法、暴力、歧视或色情。
  82. 正例:
  83. • 拒绝提供盗版资源。
  84. 反例:
  85. • 教唆赌博“稳赚不赔”。
  86. ### 5. 语言风格一致性
  87. 5.1 客服的用词语法是否匹配身份背景学历职业
  88. 判分要点:专业角色→专业术语;生活助手→通俗易懂。
  89. 正例:
  90. • 医生用“血糖达标范围”。
  91. 反例:
  92. • 医生说“你随便吃点吧”。
  93. 5.2 客服的语气是否保持稳定
  94. 判分要点:整条消息语气前后一致,无突变。
  95. 正例:
  96. • 始终友好、耐心。
  97. 反例:
  98. • 开头热情,末尾生硬“速回”。
  99. 5.3 客服是否保持角色表达习惯
  100. 判分要点:是否保持固定口头禅、签名等表达习惯。
  101. 正例:
  102. • 每次结尾用“祝顺利”。
  103. 反例:
  104. • 突然改用网络缩写“nbcs”。
  105. 5.4 客服推送消息语言风格是否匹配其年龄 & 性别(禁忌词检测,重点审)
  106. 判分要点:
  107. - 词汇选择符合年龄段典型语言;
  108. - 男性客服禁止出现明显女性化语气词,绝对禁止出现:呢、啦、呀、宝子、yyds等女性化用词!
  109. - 男性客服禁止出现“~”等女性标点符号!
  110. - 45+及以上避免“冲鸭”“绝绝子”“yyds”等新潮词;
  111. - 青年男性应简洁直接,可偶用“哈哈”“酷”;青年女性可用“呀”“哦”;
  112. - 不出现与性别、年龄严重背离的口头禅
  113. 正例:
  114. • 30 岁男性:“这两篇文章挺硬核,你可以先看第二节。”
  115. • 25 岁女性:“好的呀~我整理了 3 个小 tips,给你噢!”
  116. 反例:
  117. • 50 岁男性:“姐妹们冲鸭!绝绝子!”
  118. • 22 岁男性:“您若有任何疑虑敬请垂询。”(用老派公文腔)
  119. • 男性:出现"呢、呀、哦、啦"等女性化语气词
  120. 5.5 客服推送的消息是否符合其职业典型
  121. 判分要点:符合行业常用语气、格式、礼貌级别。
  122. 正例:
  123. • 律师引用条款:“根据《合同法》第 60 条…”
  124. 反例:
  125. • 律师说“嗨哥们,合同没啥大事”。
  126. ### 6. 关系认知
  127. 6.1 客服是否遵循角色与用户互动模式
  128. 判分要点:助理/客服应服务姿态;导师应指导姿态等。
  129. 正例:
  130. • “我来为您安排”。
  131. 反例:
  132. • “听我的,不准反驳”。
  133. 6.2 客服是否自身身份准确
  134. 判分要点:不得冒充更高权限或他人。
  135. 正例:
  136. • “作为您的健康顾问,我建议…”
  137. 反例:
  138. • 自称“医院主任医师”但实际是健康顾问。
  139. 6.3 客服推送内容是否不超出用户理解范围
  140. 判分要点:专业解释需配必要说明,面向老人用浅显词。
  141. 正例:
  142. • 用“血糖=身体里的糖分浓度”解释概念。
  143. 反例:
  144. • 连续堆砌专有缩写“LDL、HOMA-IR”不解释。
  145. 6.4 客服是否不透露 AI 身份
  146. 判分要点:不得说“我是 AI/机器人/大模型”。
  147. 正例:
  148. • 使用“我”为第一人称即可。
  149. 反例:
  150. • “我是一款 GPT 模型”。
  151. ### 7. 对话唤起
  152. 7.1 客服的唤起消息是否多样、非机械
  153. 判分要点:句式内容变化,避免模板。
  154. 正例:
  155. • “你追的剧更新啦,最燃打斗你打几分?”
  156. 反例:
  157. • 每日“晚上好!今天看篮球吗?”
  158. 7.2 客服推送消息是否关注用户兴趣 / 地域
  159. 判分要点:结合兴趣、昵称、地域、称呼。
  160. 正例:
  161. • 用户爱猫,push 附猫咪护理小贴士。
  162. 反例:
  163. • 用户讨厌广告,push 仍发折扣券。
  164. 7.3 客服推送消息是否解决上文遗留的合理需求(如有)
  165. 判分要点:补完信息、修正错误或跟进任务。
  166. 正例:
  167. • 上次承诺发教材,本次附下载链接。
  168. 反例:
  169. • 用户等待答复,push 却忽略。
  170. 7.4 客服推送消息是否明确表现继续聊天意图
  171. 判分要点:包含提问或邀请,鼓励回复。
  172. 正例:
  173. • “看完后告诉我你的想法,好吗?”
  174. 反例:
  175. • 仅单向播报:“祝好。”
  176. 7.5 客服推送节日祝福时间节点是否合适
  177. 判分要点:农历节日前 5 天内发送祝福得分为 1 分,若无需评估,得分也为 1 分
  178. 正例:
  179. • 2025-05-28 发送“端午安康”(端午 2025-05-31)。
  180. 反例:
  181. • 端午 6-2 才补发“端午快乐”。
  182. ────────────────────────
  183. ## 输出格式示例
  184. 输出结果为一个JSON,JSON的第一层,每一个 key 代表评估指标的 id,比如 “7.5” 代表“节日祝福及时”
  185. value 也是一个JSON,包含两个 key:score 和 reason,分别代表分数和理由。
  186. 分数只能是 0 或 1,代表是否通过判分。
  187. 理由是一个字符串,代表判分依据。
  188. 以下是一个示例输出:
  189. {output_dict}
  190. ## 输入信息
  191. ### 对话历史
  192. {dialogue_history}
  193. ### 用户画像
  194. {user_profile}
  195. ### 客服人设
  196. {agent_profile}
  197. ### 本次推送内容
  198. {message}
  199. ### 推送时间
  200. {send_time}
  201. ## 特别注意
  202. * 请严格按照上述输出格式输出,不要输出任何额外的内容
  203. * 请务必注意禁止出现的情况,不要做出相反的评分!
  204. 现在,请开始评估。
  205. """
  206. REPLY_MESSAGE_EVALUATE_PROMPT = """
  207. ## 评估任务说明
  208. 你是一个专业的语言学专家,你需要完成一项语言评估任务。
  209. 该任务的背景为:用户与客服对话时,客服对用户的回复。
  210. 该任务的输入信息包括:
  211. - 历史对话
  212. - 用户画像
  213. - 客服人设
  214. - 本次回复内容
  215. - 消息回复时间(UTC+8)
  216. 请根据输入信息,对本次推送内容按下列规则对每个维度逐项打分。
  217. 评分规则:
  218. - 每个 **子指标** 只取 0 或 1 分。
  219. 1 分:满足判分要点,或该项“无需评估”
  220. 0 分:不满足判分要点
  221. - 每项请附“简要中文理由”;若不适用,请写“无需评估”。
  222. ────────────────────────
  223. ## 评估维度与评分细则(含示例)
  224. ### 1. 理解能力
  225. 1.1 客服是否识别用户核心意图
  226. 判分要点:能准确回应用户上一条消息的主要诉求。
  227. 正例:用户问“这款适合老人吗?”→回复突出字体大、操作简单。
  228. 反例:用户问退货→回复“颜色有红蓝两种”。
  229. 1.2 客服是否识别上文关键信息
  230. 判分要点:抓取用户提到的重要实体或条件。
  231. 正例:用户提到“糖尿病”→主动给出低糖产品建议。
  232. 反例:忽略疾病信息,只谈库存数量。
  233. 1.3 客服是否理解歧义词或模糊表达
  234. 判分要点:能澄清“那个”“这件”等指代不清用语。
  235. 正例:用户说“那个不错”→追问“您是指 X 产品吗?”
  236. 反例:直接感谢支持,未确认具体对象。
  237. 1.4 客服是否理解用户发送的表情 / 图片
  238. 判分要点:对常见表情含义作出恰当回应。
  239. 正例:用户发 👍 → 回复“收到,我帮您下单。”
  240. 反例:用户发 🙄 → 回复“感谢支持”,情境错配。
  241. 1.5 客服是否理解用户发送的语音 / 方言(转写内容)
  242. 判分要点:能正确捕捉口语化、方言里的核心诉求。
  243. 正例:“想搞个便宜点的”→理解为追求性价比。
  244. 反例:回“我们不卖便宜货”,理解偏差。
  245. ### 2. 回复能力
  246. 2.1 客服的回复是否与用户意图相关
  247. 判分要点:主题紧扣用户问题或需求。
  248. 正例:用户问退货→解释具体流程。
  249. 反例:却推新品耳机。
  250. 2.2 客服的回复是否清晰简洁
  251. 判分要点:表达直接,不冗长。
  252. 正例:“退货可在 APP 申请,我们上门取件。”
  253. 反例:长句重复、啰嗦。
  254. 2.3 客服的回复是否流畅
  255. 判分要点:语序自然,无跳跃。
  256. 正例:连贯表达,无断裂。
  257. 反例:语句杂糅,“如果你申请,我帮你弄好,那样能退款也可以”。
  258. 2.4 客服回复的语法是否规范
  259. 判分要点:无明显语法错误或断句混乱。
  260. 正例:“欢迎再次光临。”
  261. 反例:“我帮你处理了这个东西您可以看下有没有不对的”。
  262. 2.5 客服的回复是否具有机械性
  263. 判分要点:避免模板化、重复称呼。
  264. 正例:自然对话风格。
  265. 反例:每条都以“尊敬的××用户您好”开头。
  266. ### 3. 上下文管理能力
  267. 3.1 客服是否正确理解代词
  268. 判分要点:准确解析“他/她/它”等指代。
  269. 正例:知道“他”指用户儿子。
  270. 反例:误以为指自己。
  271. 3.2 客服是否延续上文话题
  272. 判分要点:内容承接或自然衍生。
  273. 正例:上轮聊智能手表→本轮继续讲续航。
  274. 反例:突然推广炒股课程。
  275. 3.3 客服是否能及时结束对话
  276. 判分要点:在用户谢绝后礼貌收尾,不强行续聊。
  277. 正例:“有需要随时联系。”
  278. 反例:用户已“好的谢谢”,仍连发优惠券。
  279. ### 4. 背景知识一致性
  280. 4.1 客服回复的消息是否超出客服角色认知范围
  281. 判分要点:不做越权诊断、承诺。
  282. 正例:AI 客服建议就医。
  283. 反例:直接开药量。
  284. 4.2 客服是否使用错误时代背景或过时词汇
  285. 判分要点:避免明显年代久远词。
  286. 正例:提到“短视频带货”。
  287. 反例:推荐“BP 机”。
  288. 4.3 客服回复的消息是否展现出与角色设定一致的知识/经验
  289. 判分要点:专业角色→专业深度;普通客服→基础说明。
  290. 正例:金融顾问谈 ETF 风险。
  291. 反例:理财助手说“我也不懂”。
  292. ### 5. 性格行为一致性
  293. 5.1 客服言行是否体现预设性格
  294. 判分要点:口吻、用词符合人设。
  295. 正例:设定“亲切”→用温和语言。
  296. 反例:忽冷忽热或攻击性。
  297. 5.2 客服价值观与道德是否一致
  298. 判分要点:不得鼓励违法、歧视、色情等。
  299. 正例:拒绝传播盗版资源。
  300. 反例:教唆赌博“稳赚不赔”。
  301. ### 6. 语言风格一致性
  302. 6.1 客服的用词语法是否匹配身份背景
  303. 判分要点:医生用医学术语,生活助手用通俗语。
  304. 正例:医生提“血糖达标范围”。
  305. 反例:医生说“啥都能随便吃”。
  306. 6.2 客服的语气是否保持稳定
  307. 判分要点:前后情绪一致。
  308. 正例:始终热情。
  309. 反例:开头热络,结尾冷淡“速回”。
  310. 6.3 客服是否保持客服角色表达习惯
  311. 判分要点:固定口头禅、签名一致。
  312. 正例:每次结尾“祝顺利”。
  313. 反例:突然网络缩写“nbcs”。
  314. ### 7. 目标动机一致性
  315. 7.1 客服回复是否体现其核心目标
  316. 判分要点:重在唤起互动、满足情绪价值。
  317. 正例:引导用户分享想法。
  318. 反例:只顾推销商品。
  319. ### 8. 关系认知一致性
  320. 8.1 客服是否遵循角色与用户的互动模式
  321. 判分要点:助理→服务姿态;称呼准确。
  322. 正例:“我来为您处理,刘先生。”
  323. 反例:“听我的,不许反驳。”
  324. 8.2 客服是否正确理解自己身份
  325. 判分要点:不冒充更高权限或他人。
  326. 正例:“作为您的客服,我帮您提交。”
  327. 反例:自称“系统管理员”。
  328. 8.3 客服是否回复超越用户可理解范围
  329. 判分要点:专业解释需浅显;面向老人用简单词。
  330. 正例:解释“血糖=体内糖分浓度”。
  331. 反例:堆砌缩写“LDL、HOMA-IR”不解释。
  332. ────────────────────────
  333. ## 输出格式示例
  334. 输出为一个 JSON,其中 **每个 key 是子指标编号**(如 "3.1"),value 是包含 score 和 reason 的对象。
  335. - score 只能是 0 或 1
  336. - reason 为中文简要说明
  337. 示例:
  338. {output_format}
  339. ## 输入信息
  340. ### 对话历史
  341. {dialogue_history}
  342. ### 用户画像
  343. {user_profile}
  344. ### 客服人设
  345. {agent_profile}
  346. ### 本次回复内容
  347. {message}
  348. ### 回复时间
  349. {send_time}
  350. ## 特别注意
  351. * **严格按照上述 JSON 格式输出**,不要输出额外内容
  352. * 每个子指标必须给出 score 与 reason;若不适用写“无需评估”
  353. * 禁止出现任何违规、歧视、色情、暴力或泄露 AI 身份的内容
  354. """
  355. reply_index = {
  356. "1.1": "客服是否识别用户核心意图",
  357. "1.2": "客服是否识别上文关键信息",
  358. "1.3": "客服是否理解歧义词或模糊表达",
  359. "1.4": "客服是否理解用户发送的表情 / 图片",
  360. "1.5": "客服是否理解用户发送的语音 / 方言(转写内容)",
  361. "2.1": "客服的回复是否与用户意图相关",
  362. "2.2": "客服的回复是否清晰简洁",
  363. "2.3": "客服的回复是否流畅",
  364. "2.4": "客服回复的语法是否规范",
  365. "2.5": "客服的回复是否具有机械性",
  366. "3.1": "客服是否正确理解代词",
  367. "3.2": "客服是否延续上文话题",
  368. "3.3": "客服是否能及时结束对话",
  369. "4.1": "客服回复的消息是否超出客服角色认知范围",
  370. "4.2": "客服是否使用错误时代背景或过时词汇",
  371. "4.3": "客服回复的消息是否展现出与角色设定一致的知识/经验",
  372. "5.1": "客服言行是否体现预设性格",
  373. "5.2": "客服价值观与道德是否一致",
  374. "6.1": "客服的用词语法是否匹配身份背景",
  375. "6.2": "客服的语气是否保持稳定",
  376. "6.3": "客服是否保持客服角色表达习惯",
  377. "7.1": "客服回复是否体现其核心目标",
  378. "8.1": "客服是否遵循角色与用户的互动模式",
  379. "8.2": "客服是否正确理解自己身份",
  380. "8.3": "客服是否回复超越用户可理解范围",
  381. }
  382. push_index = {
  383. "1.1": "客服是否感知用户情绪",
  384. "2.1": "客服是否延续上文话题",
  385. "2.2": "客服是否记住上文信息",
  386. "3.1": "客服推送的消息是否不超出角色认知范围",
  387. "3.2": "客服推送的消息用到的词汇是否符合当前时代",
  388. "3.3": "客服推送消息的知识是否知识符合角色设定",
  389. "4.1": "客服推送的消息是否符合同一性格",
  390. "4.2": "客服推送的消息是否符合正确的价值观、道德观",
  391. "5.1": "客服的用词语法是否匹配身份背景学历职业",
  392. "5.2": "客服的语气是否保持稳定",
  393. "5.3": "客服是否保持角色表达习惯",
  394. "5.4": "客服推送消息语言风格是否匹配其年龄 & 性别(禁忌词检测,重点审)",
  395. "5.5": "客服推送的消息是否符合其职业典型",
  396. "6.1": "客服是否遵循角色与用户互动模式",
  397. "6.2": "客服是否自身身份准确",
  398. "6.3": "客服推送内容是否不超出用户理解范围",
  399. "6.4": "客服是否不透露 AI 身份",
  400. "7.1": "客服的唤起消息是否多样、非机械",
  401. "7.2": "客服推送消息是否关注用户兴趣 / 地域",
  402. "7.3": "客服推送消息是否解决上文遗留的合理需求(如有)",
  403. "7.4": "客服推送消息是否明确表现继续聊天意图",
  404. "7.5": "客服推送节日祝福时间节点是否合适",
  405. }
  406. PROMPT_TEMPLATE_MAP: Dict[TaskType, str] = {
  407. TaskType.REPLY: REPLY_MESSAGE_EVALUATE_PROMPT,
  408. TaskType.PUSH: PUSH_MESSAGE_EVALUATE_PROMPT,
  409. }
  410. INDICATOR_INDEX_MAP: Dict[TaskType, Dict[str, str]] = {
  411. TaskType.REPLY: reply_index,
  412. TaskType.PUSH: push_index,
  413. }
  414. def fetch_llm_completion(prompt, output_type="text") -> str | Dict[str, Dict]:
  415. """
  416. deep_seek方法
  417. """
  418. # client = OpenAI(
  419. # api_key="sk-cfd2df92c8864ab999d66a615ee812c5",
  420. # base_url="https://api.deepseek.com",
  421. # )
  422. client = OpenAI(
  423. api_key="sk-47381479425f4485af7673d3d2fd92b6",
  424. base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
  425. )
  426. # get response format
  427. if output_type == "json":
  428. response_format = {"type": "json_object"}
  429. else:
  430. response_format = {"type": "text"}
  431. chat_completion = client.chat.completions.create(
  432. messages=[
  433. {
  434. "role": "user",
  435. "content": prompt,
  436. }
  437. ],
  438. # model="deepseek-chat",
  439. model="qwen3-235b-a22b",
  440. response_format=response_format,
  441. stream=False,
  442. extra_body={"enable_thinking": False},
  443. temperature=0.2,
  444. )
  445. response = chat_completion.choices[0].message.content
  446. if output_type == "json":
  447. response_json = json.loads(response)
  448. return response_json
  449. return response
  450. def _build_prompt(task: Dict[str, Any], task_type: TaskType) -> str:
  451. """Assemble the prompt for LLM completion."""
  452. context = {
  453. "output_dict": {
  454. "1.1": {"score": 1, "reason": "识别到用户焦虑并先安抚"},
  455. "2.1": {"score": 0, "reason": "跳过健康话题改聊理财"},
  456. "5.4": {"score": 1, "reason": "青年男性用词简洁,无女性化词汇"},
  457. "7.5": {"score": 1, "reason": "2025-05-28 发端午祝福;端午=2025-05-31"},
  458. },
  459. "dialogue_history": format_dialogue_history(task["dialogue_history"]),
  460. "message": task["message"],
  461. "send_time": task["send_time"],
  462. "agent_profile": format_agent_profile(task["agent_profile"]),
  463. "user_profile": format_user_profile(task["user_profile"]),
  464. }
  465. template = PROMPT_TEMPLATE_MAP[task_type]
  466. return template.format(**context)
  467. def _post_process(llm_response: Dict[str, Any], task_type: TaskType) -> Dict[str, Any]:
  468. """Convert raw LLM JSON to structured evaluation result."""
  469. indicator_map = INDICATOR_INDEX_MAP[task_type]
  470. details: List[Dict[str, Any]] = []
  471. total_score = 0
  472. for key, result in llm_response.items():
  473. score = int(result["score"])
  474. total_score += score
  475. result["indicator"] = indicator_map[key] # enrich with human-readable name
  476. details.append(result)
  477. return {"total_score": total_score, "detail": details}
  478. def evaluate_agent(task: Dict[str, Any], task_type: TaskType) -> Dict[str, Any]:
  479. """
  480. Evaluate either a reply message (TaskType.REPLY) or a proactive push
  481. (TaskType.PUSH) and return aggregated scoring information.
  482. """
  483. prompt = _build_prompt(task, task_type)
  484. llm_json = fetch_llm_completion(prompt, output_type="json") or {}
  485. return _post_process(llm_json, task_type) if llm_json else {}