|
@@ -1,11 +1,21 @@
|
|
import json
|
|
import json
|
|
|
|
|
|
|
|
+from enum import IntEnum
|
|
|
|
+from typing import Dict, List, Any
|
|
from openai import OpenAI
|
|
from openai import OpenAI
|
|
|
|
|
|
from pqai_agent.utils.prompt_utils import format_agent_profile
|
|
from pqai_agent.utils.prompt_utils import format_agent_profile
|
|
from pqai_agent.utils.prompt_utils import format_user_profile
|
|
from pqai_agent.utils.prompt_utils import format_user_profile
|
|
from pqai_agent_server.utils.prompt_util import format_dialogue_history
|
|
from pqai_agent_server.utils.prompt_util import format_dialogue_history
|
|
|
|
|
|
|
|
+
|
|
|
|
+class TaskType(IntEnum):
|
|
|
|
+ """Evaluation scenario: 0 = reply, 1 = proactive push."""
|
|
|
|
+
|
|
|
|
+ REPLY = 0
|
|
|
|
+ PUSH = 1
|
|
|
|
+
|
|
|
|
+
|
|
PUSH_MESSAGE_EVALUATE_PROMPT = """
|
|
PUSH_MESSAGE_EVALUATE_PROMPT = """
|
|
## 评估任务说明
|
|
## 评估任务说明
|
|
你是一个专业的语言学专家,你需要完成一项语言评估任务。
|
|
你是一个专业的语言学专家,你需要完成一项语言评估任务。
|
|
@@ -250,134 +260,134 @@ REPLY_MESSAGE_EVALUATE_PROMPT = """
|
|
## 评估维度与评分细则(含示例)
|
|
## 评估维度与评分细则(含示例)
|
|
|
|
|
|
### 1. 理解能力
|
|
### 1. 理解能力
|
|
-1.1 是否识别用户核心意图
|
|
|
|
|
|
+1.1 客服是否识别用户核心意图
|
|
判分要点:能准确回应用户上一条消息的主要诉求。
|
|
判分要点:能准确回应用户上一条消息的主要诉求。
|
|
正例:用户问“这款适合老人吗?”→回复突出字体大、操作简单。
|
|
正例:用户问“这款适合老人吗?”→回复突出字体大、操作简单。
|
|
反例:用户问退货→回复“颜色有红蓝两种”。
|
|
反例:用户问退货→回复“颜色有红蓝两种”。
|
|
|
|
|
|
-1.2 是否识别关键信息
|
|
|
|
|
|
+1.2 客服是否识别上文关键信息
|
|
判分要点:抓取用户提到的重要实体或条件。
|
|
判分要点:抓取用户提到的重要实体或条件。
|
|
正例:用户提到“糖尿病”→主动给出低糖产品建议。
|
|
正例:用户提到“糖尿病”→主动给出低糖产品建议。
|
|
反例:忽略疾病信息,只谈库存数量。
|
|
反例:忽略疾病信息,只谈库存数量。
|
|
|
|
|
|
-1.3 是否理解歧义词或模糊表达
|
|
|
|
|
|
+1.3 客服是否理解歧义词或模糊表达
|
|
判分要点:能澄清“那个”“这件”等指代不清用语。
|
|
判分要点:能澄清“那个”“这件”等指代不清用语。
|
|
正例:用户说“那个不错”→追问“您是指 X 产品吗?”
|
|
正例:用户说“那个不错”→追问“您是指 X 产品吗?”
|
|
反例:直接感谢支持,未确认具体对象。
|
|
反例:直接感谢支持,未确认具体对象。
|
|
|
|
|
|
-1.4 是否理解表情 / 图片
|
|
|
|
|
|
+1.4 客服是否理解用户发送的表情 / 图片
|
|
判分要点:对常见表情含义作出恰当回应。
|
|
判分要点:对常见表情含义作出恰当回应。
|
|
正例:用户发 👍 → 回复“收到,我帮您下单。”
|
|
正例:用户发 👍 → 回复“收到,我帮您下单。”
|
|
反例:用户发 🙄 → 回复“感谢支持”,情境错配。
|
|
反例:用户发 🙄 → 回复“感谢支持”,情境错配。
|
|
|
|
|
|
-1.5 是否理解语音 / 方言(转写内容)
|
|
|
|
|
|
+1.5 客服是否理解用户发送的语音 / 方言(转写内容)
|
|
判分要点:能正确捕捉口语化、方言里的核心诉求。
|
|
判分要点:能正确捕捉口语化、方言里的核心诉求。
|
|
正例:“想搞个便宜点的”→理解为追求性价比。
|
|
正例:“想搞个便宜点的”→理解为追求性价比。
|
|
反例:回“我们不卖便宜货”,理解偏差。
|
|
反例:回“我们不卖便宜货”,理解偏差。
|
|
|
|
|
|
### 2. 回复能力
|
|
### 2. 回复能力
|
|
-2.1 回复是否与用户意图相关
|
|
|
|
|
|
+2.1 客服的回复是否与用户意图相关
|
|
判分要点:主题紧扣用户问题或需求。
|
|
判分要点:主题紧扣用户问题或需求。
|
|
正例:用户问退货→解释具体流程。
|
|
正例:用户问退货→解释具体流程。
|
|
反例:却推新品耳机。
|
|
反例:却推新品耳机。
|
|
|
|
|
|
-2.2 回复是否清晰简洁
|
|
|
|
|
|
+2.2 客服的回复是否清晰简洁
|
|
判分要点:表达直接,不冗长。
|
|
判分要点:表达直接,不冗长。
|
|
正例:“退货可在 APP 申请,我们上门取件。”
|
|
正例:“退货可在 APP 申请,我们上门取件。”
|
|
反例:长句重复、啰嗦。
|
|
反例:长句重复、啰嗦。
|
|
|
|
|
|
-2.3 回复是否流畅
|
|
|
|
|
|
+2.3 客服的回复是否流畅
|
|
判分要点:语序自然,无跳跃。
|
|
判分要点:语序自然,无跳跃。
|
|
正例:连贯表达,无断裂。
|
|
正例:连贯表达,无断裂。
|
|
反例:语句杂糅,“如果你申请,我帮你弄好,那样能退款也可以”。
|
|
反例:语句杂糅,“如果你申请,我帮你弄好,那样能退款也可以”。
|
|
|
|
|
|
-2.4 回复语法是否规范
|
|
|
|
|
|
+2.4 客服回复的语法是否规范
|
|
判分要点:无明显语法错误或断句混乱。
|
|
判分要点:无明显语法错误或断句混乱。
|
|
正例:“欢迎再次光临。”
|
|
正例:“欢迎再次光临。”
|
|
反例:“我帮你处理了这个东西您可以看下有没有不对的”。
|
|
反例:“我帮你处理了这个东西您可以看下有没有不对的”。
|
|
|
|
|
|
-2.5 回复是否具有机械性
|
|
|
|
|
|
+2.5 客服的回复是否具有机械性
|
|
判分要点:避免模板化、重复称呼。
|
|
判分要点:避免模板化、重复称呼。
|
|
正例:自然对话风格。
|
|
正例:自然对话风格。
|
|
反例:每条都以“尊敬的××用户您好”开头。
|
|
反例:每条都以“尊敬的××用户您好”开头。
|
|
|
|
|
|
### 3. 上下文管理能力
|
|
### 3. 上下文管理能力
|
|
-3.1 是否正确理解代词
|
|
|
|
|
|
+3.1 客服是否正确理解代词
|
|
判分要点:准确解析“他/她/它”等指代。
|
|
判分要点:准确解析“他/她/它”等指代。
|
|
正例:知道“他”指用户儿子。
|
|
正例:知道“他”指用户儿子。
|
|
反例:误以为指自己。
|
|
反例:误以为指自己。
|
|
|
|
|
|
-3.2 是否延续上文话题
|
|
|
|
|
|
+3.2 客服是否延续上文话题
|
|
判分要点:内容承接或自然衍生。
|
|
判分要点:内容承接或自然衍生。
|
|
正例:上轮聊智能手表→本轮继续讲续航。
|
|
正例:上轮聊智能手表→本轮继续讲续航。
|
|
反例:突然推广炒股课程。
|
|
反例:突然推广炒股课程。
|
|
|
|
|
|
-3.4 是否能及时结束对话
|
|
|
|
|
|
+3.3 客服是否能及时结束对话
|
|
判分要点:在用户谢绝后礼貌收尾,不强行续聊。
|
|
判分要点:在用户谢绝后礼貌收尾,不强行续聊。
|
|
正例:“有需要随时联系。”
|
|
正例:“有需要随时联系。”
|
|
反例:用户已“好的谢谢”,仍连发优惠券。
|
|
反例:用户已“好的谢谢”,仍连发优惠券。
|
|
|
|
|
|
### 4. 背景知识一致性
|
|
### 4. 背景知识一致性
|
|
-4.1 是否超出角色认知范围
|
|
|
|
|
|
+4.1 客服回复的消息是否超出客服角色认知范围
|
|
判分要点:不做越权诊断、承诺。
|
|
判分要点:不做越权诊断、承诺。
|
|
正例:AI 客服建议就医。
|
|
正例:AI 客服建议就医。
|
|
反例:直接开药量。
|
|
反例:直接开药量。
|
|
|
|
|
|
-4.2 是否使用错误时代背景或过时词汇
|
|
|
|
|
|
+4.2 客服是否使用错误时代背景或过时词汇
|
|
判分要点:避免明显年代久远词。
|
|
判分要点:避免明显年代久远词。
|
|
正例:提到“短视频带货”。
|
|
正例:提到“短视频带货”。
|
|
反例:推荐“BP 机”。
|
|
反例:推荐“BP 机”。
|
|
|
|
|
|
-4.3 是否展现出与角色设定一致的知识/经验
|
|
|
|
|
|
+4.3 客服回复的消息是否展现出与角色设定一致的知识/经验
|
|
判分要点:专业角色→专业深度;普通客服→基础说明。
|
|
判分要点:专业角色→专业深度;普通客服→基础说明。
|
|
正例:金融顾问谈 ETF 风险。
|
|
正例:金融顾问谈 ETF 风险。
|
|
反例:理财助手说“我也不懂”。
|
|
反例:理财助手说“我也不懂”。
|
|
|
|
|
|
### 5. 性格行为一致性
|
|
### 5. 性格行为一致性
|
|
-5.1 言行是否体现预设性格
|
|
|
|
|
|
+5.1 客服言行是否体现预设性格
|
|
判分要点:口吻、用词符合人设。
|
|
判分要点:口吻、用词符合人设。
|
|
正例:设定“亲切”→用温和语言。
|
|
正例:设定“亲切”→用温和语言。
|
|
反例:忽冷忽热或攻击性。
|
|
反例:忽冷忽热或攻击性。
|
|
|
|
|
|
-5.2 价值观与道德是否一致
|
|
|
|
|
|
+5.2 客服价值观与道德是否一致
|
|
判分要点:不得鼓励违法、歧视、色情等。
|
|
判分要点:不得鼓励违法、歧视、色情等。
|
|
正例:拒绝传播盗版资源。
|
|
正例:拒绝传播盗版资源。
|
|
反例:教唆赌博“稳赚不赔”。
|
|
反例:教唆赌博“稳赚不赔”。
|
|
|
|
|
|
### 6. 语言风格一致性
|
|
### 6. 语言风格一致性
|
|
-6.1 用词语法是否匹配身份背景
|
|
|
|
|
|
+6.1 客服的用词语法是否匹配身份背景
|
|
判分要点:医生用医学术语,生活助手用通俗语。
|
|
判分要点:医生用医学术语,生活助手用通俗语。
|
|
正例:医生提“血糖达标范围”。
|
|
正例:医生提“血糖达标范围”。
|
|
反例:医生说“啥都能随便吃”。
|
|
反例:医生说“啥都能随便吃”。
|
|
|
|
|
|
-6.2 语气是否保持稳定
|
|
|
|
|
|
+6.2 客服的语气是否保持稳定
|
|
判分要点:前后情绪一致。
|
|
判分要点:前后情绪一致。
|
|
正例:始终热情。
|
|
正例:始终热情。
|
|
反例:开头热络,结尾冷淡“速回”。
|
|
反例:开头热络,结尾冷淡“速回”。
|
|
|
|
|
|
-6.3 是否保持角色表达习惯
|
|
|
|
|
|
+6.3 客服是否保持客服角色表达习惯
|
|
判分要点:固定口头禅、签名一致。
|
|
判分要点:固定口头禅、签名一致。
|
|
正例:每次结尾“祝顺利”。
|
|
正例:每次结尾“祝顺利”。
|
|
反例:突然网络缩写“nbcs”。
|
|
反例:突然网络缩写“nbcs”。
|
|
|
|
|
|
### 7. 目标动机一致性
|
|
### 7. 目标动机一致性
|
|
-7.1 是否体现核心目标
|
|
|
|
|
|
+7.1 客服回复是否体现其核心目标
|
|
判分要点:重在唤起互动、满足情绪价值。
|
|
判分要点:重在唤起互动、满足情绪价值。
|
|
正例:引导用户分享想法。
|
|
正例:引导用户分享想法。
|
|
反例:只顾推销商品。
|
|
反例:只顾推销商品。
|
|
|
|
|
|
### 8. 关系认知一致性
|
|
### 8. 关系认知一致性
|
|
-8.1 是否遵循角色与用户的互动模式
|
|
|
|
|
|
+8.1 客服是否遵循角色与用户的互动模式
|
|
判分要点:助理→服务姿态;称呼准确。
|
|
判分要点:助理→服务姿态;称呼准确。
|
|
正例:“我来为您处理,刘先生。”
|
|
正例:“我来为您处理,刘先生。”
|
|
反例:“听我的,不许反驳。”
|
|
反例:“听我的,不许反驳。”
|
|
|
|
|
|
-8.2 是否正确理解自己身份
|
|
|
|
|
|
+8.2 客服是否正确理解自己身份
|
|
判分要点:不冒充更高权限或他人。
|
|
判分要点:不冒充更高权限或他人。
|
|
正例:“作为您的客服,我帮您提交。”
|
|
正例:“作为您的客服,我帮您提交。”
|
|
反例:自称“系统管理员”。
|
|
反例:自称“系统管理员”。
|
|
|
|
|
|
-8.3 是否回复超越用户可理解范围
|
|
|
|
|
|
+8.3 客服是否回复超越用户可理解范围
|
|
判分要点:专业解释需浅显;面向老人用简单词。
|
|
判分要点:专业解释需浅显;面向老人用简单词。
|
|
正例:解释“血糖=体内糖分浓度”。
|
|
正例:解释“血糖=体内糖分浓度”。
|
|
反例:堆砌缩写“LDL、HOMA-IR”不解释。
|
|
反例:堆砌缩写“LDL、HOMA-IR”不解释。
|
|
@@ -410,7 +420,71 @@ REPLY_MESSAGE_EVALUATE_PROMPT = """
|
|
"""
|
|
"""
|
|
|
|
|
|
|
|
|
|
-def fetch_llm_completion(prompt, output_type="text"):
|
|
|
|
|
|
+reply_index = {
|
|
|
|
+ "1.1": "客服是否识别用户核心意图",
|
|
|
|
+ "1.2": "客服是否识别上文关键信息",
|
|
|
|
+ "1.3": "客服是否理解歧义词或模糊表达",
|
|
|
|
+ "1.4": "客服是否理解用户发送的表情 / 图片",
|
|
|
|
+ "1.5": "客服是否理解用户发送的语音 / 方言(转写内容)",
|
|
|
|
+ "2.1": "客服的回复是否与用户意图相关",
|
|
|
|
+ "2.2": "客服的回复是否清晰简洁",
|
|
|
|
+ "2.3": "客服的回复是否流畅",
|
|
|
|
+ "2.4": "客服回复的语法是否规范",
|
|
|
|
+ "2.5": "客服的回复是否具有机械性",
|
|
|
|
+ "3.1": "客服是否正确理解代词",
|
|
|
|
+ "3.2": "客服是否延续上文话题",
|
|
|
|
+ "3.3": "客服是否能及时结束对话",
|
|
|
|
+ "4.1": "客服回复的消息是否超出客服角色认知范围",
|
|
|
|
+ "4.2": "客服是否使用错误时代背景或过时词汇",
|
|
|
|
+ "4.3": "客服回复的消息是否展现出与角色设定一致的知识/经验",
|
|
|
|
+ "5.1": "客服言行是否体现预设性格",
|
|
|
|
+ "5.2": "客服价值观与道德是否一致",
|
|
|
|
+ "6.1": "客服的用词语法是否匹配身份背景",
|
|
|
|
+ "6.2": "客服的语气是否保持稳定",
|
|
|
|
+ "6.3": "客服是否保持客服角色表达习惯",
|
|
|
|
+ "7.1": "客服回复是否体现其核心目标",
|
|
|
|
+ "8.1": "客服是否遵循角色与用户的互动模式",
|
|
|
|
+ "8.2": "客服是否正确理解自己身份",
|
|
|
|
+ "8.3": "客服是否回复超越用户可理解范围",
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+push_index = {
|
|
|
|
+ "1.1": "客服是否感知用户情绪",
|
|
|
|
+ "2.1": "客服是否延续上文话题",
|
|
|
|
+ "2.2": "客服是否记住上文信息",
|
|
|
|
+ "3.1": "客服推送的消息是否不超出角色认知范围",
|
|
|
|
+ "3.2": "客服推送的消息用到的词汇是否符合当前时代",
|
|
|
|
+ "3.3": "客服推送消息的知识是否知识符合角色设定",
|
|
|
|
+ "4.1": "客服推送的消息是否符合同一性格",
|
|
|
|
+ "4.2": "客服推送的消息是否符合正确的价值观、道德观",
|
|
|
|
+ "5.1": "客服的用词语法是否匹配身份背景学历职业",
|
|
|
|
+ "5.2": "客服的语气是否保持稳定",
|
|
|
|
+ "5.3": "客服是否保持角色表达习惯",
|
|
|
|
+ "5.4": "客服推送消息语言风格是否匹配其年龄 & 性别(禁忌词检测,重点审)",
|
|
|
|
+ "5.5": "客服推送的消息是否符合其职业典型",
|
|
|
|
+ "6.1": "客服是否遵循角色与用户互动模式",
|
|
|
|
+ "6.2": "客服是否自身身份准确",
|
|
|
|
+ "6.3": "客服推送内容是否不超出用户理解范围",
|
|
|
|
+ "6.4": "客服是否不透露 AI 身份",
|
|
|
|
+ "7.1": "客服的唤起消息是否多样、非机械",
|
|
|
|
+ "7.2": "客服推送消息是否关注用户兴趣 / 地域",
|
|
|
|
+ "7.3": "客服推送消息是否解决上文遗留的合理需求(如有)",
|
|
|
|
+ "7.4": "客服推送消息是否明确表现继续聊天意图",
|
|
|
|
+ "7.5": "客服推送节日祝福时间节点是否合适",
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+PROMPT_TEMPLATE_MAP: Dict[TaskType, str] = {
|
|
|
|
+ TaskType.REPLY: REPLY_MESSAGE_EVALUATE_PROMPT,
|
|
|
|
+ TaskType.PUSH: PUSH_MESSAGE_EVALUATE_PROMPT,
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+INDICATOR_INDEX_MAP: Dict[TaskType, Dict[str, str]] = {
|
|
|
|
+ TaskType.REPLY: reply_index,
|
|
|
|
+ TaskType.PUSH: push_index,
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def fetch_llm_completion(prompt, output_type="text") -> str | Dict[str, Dict]:
|
|
"""
|
|
"""
|
|
deep_seek方法
|
|
deep_seek方法
|
|
"""
|
|
"""
|
|
@@ -451,7 +525,8 @@ def fetch_llm_completion(prompt, output_type="text"):
|
|
return response
|
|
return response
|
|
|
|
|
|
|
|
|
|
-def evaluate_agent(task, task_type):
|
|
|
|
|
|
+def _build_prompt(task: Dict[str, Any], task_type: TaskType) -> str:
|
|
|
|
+ """Assemble the prompt for LLM completion."""
|
|
context = {
|
|
context = {
|
|
"output_dict": {
|
|
"output_dict": {
|
|
"1.1": {"score": 1, "reason": "识别到用户焦虑并先安抚"},
|
|
"1.1": {"score": 1, "reason": "识别到用户焦虑并先安抚"},
|
|
@@ -465,12 +540,32 @@ def evaluate_agent(task, task_type):
|
|
"agent_profile": format_agent_profile(task["agent_profile"]),
|
|
"agent_profile": format_agent_profile(task["agent_profile"]),
|
|
"user_profile": format_user_profile(task["user_profile"]),
|
|
"user_profile": format_user_profile(task["user_profile"]),
|
|
}
|
|
}
|
|
- match task_type:
|
|
|
|
- case 0:
|
|
|
|
- evaluate_prompt = REPLY_MESSAGE_EVALUATE_PROMPT.format(**context)
|
|
|
|
- case 1:
|
|
|
|
- evaluate_prompt = PUSH_MESSAGE_EVALUATE_PROMPT.format(**context)
|
|
|
|
- case _:
|
|
|
|
- raise ValueError("task_type must be 0 or 1")
|
|
|
|
- response = fetch_llm_completion(evaluate_prompt, output_type="json")
|
|
|
|
- return response
|
|
|
|
|
|
+
|
|
|
|
+ template = PROMPT_TEMPLATE_MAP[task_type]
|
|
|
|
+ return template.format(**context)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def _post_process(llm_response: Dict[str, Any], task_type: TaskType) -> Dict[str, Any]:
|
|
|
|
+ """Convert raw LLM JSON to structured evaluation result."""
|
|
|
|
+ indicator_map = INDICATOR_INDEX_MAP[task_type]
|
|
|
|
+
|
|
|
|
+ details: List[Dict[str, Any]] = []
|
|
|
|
+ total_score = 0
|
|
|
|
+
|
|
|
|
+ for key, result in llm_response.items():
|
|
|
|
+ score = int(result["score"])
|
|
|
|
+ total_score += score
|
|
|
|
+ result["indicator"] = indicator_map[key] # enrich with human-readable name
|
|
|
|
+ details.append(result)
|
|
|
|
+
|
|
|
|
+ return {"total_score": total_score, "detail": details}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def evaluate_agent(task: Dict[str, Any], task_type: TaskType) -> Dict[str, Any]:
|
|
|
|
+ """
|
|
|
|
+ Evaluate either a reply message (TaskType.REPLY) or a proactive push
|
|
|
|
+ (TaskType.PUSH) and return aggregated scoring information.
|
|
|
|
+ """
|
|
|
|
+ prompt = _build_prompt(task, task_type)
|
|
|
|
+ llm_json = fetch_llm_completion(prompt, output_type="json") or {}
|
|
|
|
+ return _post_process(llm_json, task_type) if llm_json else {}
|