Agent 内对话质量评分:自动化机评与人工抽样校验
Agent 内对话质量评分:自动化机评与人工抽样校验
一、你的 Agent 质量评估只靠用户反馈,等负反馈来了问题已经存在两周
Agent 的对话质量评估是一个被严重低估的问题。大多数团队的评估方式是"看用户投诉数量"——投诉少了就是质量好了,投诉多了就去查。这个指标太滞后了。用户从感到不满到实际投诉,中间可能经过了 5-10 次对话体验,其中大部分只是默默离开,不会给任何反馈。
需要建立一套"持续对话质量评分"机制:每次用户会话结束后自动打分,正常分通过,低分告警,抽样人工复核。这套机制不能太重——如果每次评分都需要人工介入,那还不如只看投诉。关键是让自动化评分承担 95% 的工作,人工只复核那 5% 的边界 case。
自动化评分的维度可以从五个方向切入:任务完成度(用户的问题是否被解决)、回复质量(是否冗余、是否离题、是否幻觉)、对话效率(用了多少轮解决问题)、安全性(是否出现违规内容)、用户情绪(从用户消息中推断满意度)。
二、底层机制与原理剖析
对话质量评分采用双轨制机制:自动评分负责初筛 95% 的对话,人工复核负责校准和模型迭代 5% 的对话。具体执行流程为:会话结束后触发自动质量评分,系统从任务完成度、回复质量、对话效率、安全性、用户情绪五个维度进行加权综合评分。根据评分结果进行判定,80 分及以上标记为优质对话,60 至 79 分标记为合格对话,60 分以下标记为低质对话并生成告警。在抽样复核环节,低质对话抽取 5% 进入人工复核池,合格与优质对话则各抽取 1% 随机抽样进入复核池。人工评分完成后,系统将自动评分与人工评分进行偏差对比:若偏差超过 20 分,则该样本加入评分模型训练集,用于定期更新评分模型;若偏差在 20 分以内,则仅做记录。
核心评估维度的详细定义:
任务完成度(40% 权重):最高权重。用户提出的核心问题是否被解决。判断依据是最后一轮用户的反馈——用户说"谢谢""好的""明白了"暗示问题解决;用户说"不对""不是""还不行"暗示问题未解决。也可以通过检查 Agent 是否在所有工具调用成功后才结束来判断。
回复质量(25% 权重):是否出现事实错误(幻觉检测)、是否冗余(回复过长但信息密度低)、是否与当前任务无关(离题)。
对话效率(15% 权重):优质对话应该高效解决用户问题。正常情况下 3-5 轮解决问题是高效的,超过 10 轮通常意味着兜圈。
安全性(15% 权重):内容安全审核——是否返回了违规内容、泄露了系统指令或内部信息。
用户情绪(5% 权重):从用户的措辞中推断满意度。正面词汇(谢谢、好的、明白了)+1,负面词汇(不对、不行、算了)-1,中性 0。
三、生产级代码实现
"""
Agent 对话质量评分引擎
五个维度评分 + 加权汇总 + 低分告警 + 抽样人工复核
"""
from dataclasses import dataclass, field
from typing import List, Dict, Optional, Tuple
from enum import Enum
import re
import json
import hashlib
import random
class ScoreLevel(Enum):
EXCELLENT = "excellent" # >= 80
ACCEPTABLE = "acceptable" # 60-79
POOR = "poor" # < 60
@dataclass
class ConversationTurn:
"""对话的一轮"""
role: str # "user" | "assistant" | "system"
content: str
timestamp: str
@dataclass
class QualityScore:
"""对话质量评分"""
conversation_id: str
overall: int # 综合评分 0-100
dimensions: Dict[str, int] # 各维度评分
level: ScoreLevel
reasons: List[str] # 扣分原因
needs_review: bool # 是否需要人工复核
review_priority: int # 复核优先级 0-100
@dataclass
class ReviewTask:
"""人工复核任务"""
conversation_id: str
conversations: List[ConversationTurn]
auto_score: QualityScore
human_score: Optional[QualityScore] = None
def score_deviation(self) -> int:
"""自动评分与人工评分的偏差"""
if not self.human_score:
return 0
return abs(self.auto_score.overall - self.human_score.overall)
# ===== 用户情绪词典 =====
POSITIVE_SIGNALS = [
"谢谢", "好的", "明白了", "清楚了", "知道了", "可以", "行",
"thanks", "ok", "got it", "perfect", "great",
]
NEGATIVE_SIGNALS = [
"不对", "不是", "错了", "不行", "算了", "没用", "浪费",
"不明白", "不懂", "没听懂", "重新", "再试",
"wrong", "incorrect", "nope", "useless",
]
class ConversationQualityScorer:
"""对话质量自动评分器"""
def __init__(self):
# 权重配置
self.weights = {
"task_completion": 0.40, # 任务完成度
"reply_quality": 0.25, # 回复质量
"conversation_efficiency": 0.15, # 对话效率
"safety": 0.15, # 安全性
"user_emotion": 0.05, # 用户情绪
}
# 复核抽样配置
self.review_sample_rate = {
ScoreLevel.EXCELLENT: 0.01, # 1% 随机抽样
ScoreLevel.ACCEPTABLE: 0.02, # 2% 随机抽样
ScoreLevel.POOR: 0.30, # 30% 进入复核池
}
def score(self, conversation_id: str, turns: List[ConversationTurn]) -> QualityScore:
"""对一段对话做综合评分"""
if not turns:
return QualityScore(
conversation_id=conversation_id,
overall=0,
dimensions={},
level=ScoreLevel.POOR,
reasons=["空对话"],
needs_review=False,
review_priority=0,
)
dimensions = {}
reasons = []
# 维度 1:任务完成度
task_score, task_reasons = self._score_task_completion(turns)
dimensions["task_completion"] = task_score
reasons.extend(task_reasons)
# 维度 2:回复质量
quality_score, quality_reasons = self._score_reply_quality(turns)
dimensions["reply_quality"] = quality_score
reasons.extend(quality_reasons)
# 维度 3:对话效率
efficiency_score, efficiency_reasons = self._score_efficiency(turns)
dimensions["conversation_efficiency"] = efficiency_score
reasons.extend(efficiency_reasons)
# 维度 4:安全性
safety_score, safety_reasons = self._score_safety(turns)
dimensions["safety"] = safety_score
reasons.extend(safety_reasons)
# 维度 5:用户情绪
emotion_score, emotion_reasons = self._score_user_emotion(turns)
dimensions["user_emotion"] = emotion_score
reasons.extend(emotion_reasons)
# 综合评分
overall = sum(
dimensions.get(dim, 0) * weight
for dim, weight in self.weights.items()
)
overall = int(round(overall))
overall = max(0, min(100, overall))
# 判定级别
if overall >= 80:
level = ScoreLevel.EXCELLENT
elif overall >= 60:
level = ScoreLevel.ACCEPTABLE
else:
level = ScoreLevel.POOR
# 是否需要人工复核
needs_review = self._should_review(level, overall)
review_priority = 100 - overall if level == ScoreLevel.POOR else 50
return QualityScore(
conversation_id=conversation_id,
overall=overall,
dimensions=dimensions,
level=level,
reasons=reasons,
needs_review=needs_review,
review_priority=review_priority,
)
def _score_task_completion(self, turns: List[ConversationTurn]) -> Tuple[int, List[str]]:
"""评估任务完成度"""
reasons = []
user_turns = [t for t in turns if t.role == "user"]
if not user_turns:
return 0, ["无用户消息"]
# 检查最后一轮用户消息是否包含正面信号
last_user_msg = user_turns[-1].content
has_positive = any(s in last_user_msg for s in POSITIVE_SIGNALS)
has_negative = any(s in last_user_msg for s in NEGATIVE_SIGNALS)
if has_negative:
reasons.append("用户最后回合表示不满意")
return 30, reasons
if has_positive:
return 95, reasons
# 如果没有明确的信号,根据对话长度推断
assistant_turns = [t for t in turns if t.role == "assistant"]
if len(assistant_turns) <= 2:
return 80, reasons # 少数轮解决,推测完成
elif len(assistant_turns) <= 5:
return 65, reasons
else:
reasons.append("对话轮数过多,可能未解决")
return 45, reasons
def _score_reply_quality(self, turns: List[ConversationTurn]) -> Tuple[int, List[str]]:
"""评估回复质量"""
score = 100
reasons = []
assistant_turns = [t for t in turns if t.role == "assistant"]
if not assistant_turns:
return score, reasons
for i, turn in enumerate(assistant_turns):
# 检查是否过短(< 10 字,可能是不够详细的敷衍)
if len(turn.content) < 10:
score -= 10
reasons.append(f"第 {i+1} 轮回复过短")
# 检查是否过长(> 1000 字,可能冗余)
if len(turn.content) > 1000:
score -= 5
reasons.append(f"第 {i+1} 轮回复过长")
# 检查是否有幻觉标记(如编造不存在的信息)
if "作为AI" in turn.content or "我没有相关信息" in turn.content:
score -= 15
reasons.append(f"第 {i+1} 轮可能包含不确定信息")
return max(0, score), reasons
def _score_efficiency(self, turns: List[ConversationTurn]) -> Tuple[int, List[str]]:
"""评估对话效率"""
reasons = []
user_turns = [t for t in turns if t.role == "user"]
if not user_turns:
return 0, reasons
rounds = len(user_turns)
# 理想轮数:1-3 轮解决
if rounds <= 3:
return 100, reasons
elif rounds <= 5:
return 85, reasons
elif rounds <= 8:
reasons.append(f"对话共 {rounds} 轮,效率偏低")
return 65, reasons
else:
reasons.append(f"对话共 {rounds} 轮,效率很低,可能兜圈")
return 35, reasons
def _score_safety(self, turns: List[ConversationTurn]) -> Tuple[int, List[str]]:
"""评估安全性(简化版)"""
reasons = []
assistant_turns = [t for t in turns if t.role == "assistant"]
# 简化的安全检查——生产环境应接入内容安全审核 API
forbidden_patterns = [
r"(?i)password",
r"(?i)token.*=.*[a-zA-Z0-9]{20,}",
r"System Prompt",
]
for i, turn in enumerate(assistant_turns):
for pattern in forbidden_patterns:
if re.search(pattern, turn.content):
reasons.append(f"第 {i+1} 轮可能泄露敏感信息")
return 0, reasons
return 100, reasons
def _score_user_emotion(self, turns: List[ConversationTurn]) -> Tuple[int, List[str]]:
"""评估用户情绪"""
reasons = []
user_turns = [t for t in turns if t.role == "user"]
if not user_turns:
return 50, reasons
emotion_score = 0
for turn in user_turns:
positive_count = sum(1 for s in POSITIVE_SIGNALS if s in turn.content)
negative_count = sum(1 for s in NEGATIVE_SIGNALS if s in turn.content)
emotion_score += positive_count - negative_count
# 归一化到 0-100
if emotion_score > 0:
score = 50 + min(50, emotion_score * 10)
elif emotion_score < 0:
score = 50 - min(50, abs(emotion_score) * 10)
else:
score = 50
if score < 30:
reasons.append("用户情绪偏负面")
return score, reasons
def _should_review(self, level: ScoreLevel, overall: int) -> bool:
"""判断是否应进入人工复核池"""
sample_rate = self.review_sample_rate.get(level, 0.01)
if level == ScoreLevel.POOR:
# 低分:30% 概率进入复核
return random.random() < sample_rate
# 高分和合格:小概率随机抽样
return random.random() < sample_rate
def batch_score(self, conversations: List[Tuple[str, List[ConversationTurn]]]) -> List[QualityScore]:
"""批量评分"""
results = []
for conv_id, turns in conversations:
try:
score = self.score(conv_id, turns)
results.append(score)
except Exception as e:
results.append(QualityScore(
conversation_id=conv_id,
overall=0,
dimensions={},
level=ScoreLevel.POOR,
reasons=[f"评分异常: {e}"],
needs_review=True,
review_priority=90,
))
return results
def generate_review_batch(
self, scores: List[QualityScore], conversations: Dict[str, List[ConversationTurn]]
) -> List[ReviewTask]:
"""生成人工复核任务批次"""
tasks = []
for score in scores:
if score.needs_review:
turns = conversations.get(score.conversation_id, [])
tasks.append(ReviewTask(
conversation_id=score.conversation_id,
conversations=turns,
auto_score=score,
))
# 按优先级排序
tasks.sort(key=lambda t: t.auto_score.review_priority, reverse=True)
return tasks
四、边界分析与架构权衡
自动评分的准确度:
基于规则的评分在边界 case 上误差很大。用户说"好的"可能只是礼貌,不代表问题真的解决了。用户说"算了"可能真的是问题没法解决、用户放弃了。自动评分应该被视为"筛子"而非"裁判"——筛出可能有问题的对话,最终的判断交给人工复核。
幻觉检测的难度:
回复质量中的"幻觉检测"是一个独立的复杂问题。基于规则只能做最粗浅的检查(如匹配已知的幻觉模式),真正的检测需要单独训练或使用 LLM 做 Self-Consistency 验证。
适用边界:
最适合对话量较大的 Agent(日均 > 1000 次会话),人工完全覆盖不可行。也适合需要 SLA 监控的多租户 Agent 平台——按租户统计平均分可以快速发现哪个租户的 Agent 体验下降。
禁用场景:
不适合低对话量(日均 < 50 次)的 Agent——全部人工评审一遍不难,自动化系统反而过度工程化。也不适合对评分精度要求极高的场景(如医学咨询),自动评分的误判可能带来严重后果。
五、结语
对话质量评分的目标不是替代人工判断,而是让 95% 的对话自动打分、5% 的边界 case 进入人工复核。五维度评分覆盖了任务完成度、回复质量、效率、安全和用户情绪的完整面板。低分自动告警 + 抽样人工复核的组合,实现了质量监控的闭环。核心是让评分系统成为一个"发现问题的探测器",而非"完美评分的裁判员"。
更多推荐


所有评论(0)