第 9 章 · 评测体系
没有评测的 Agent 开发等于盲飞:每次改 prompt、换模型、调工具描述,你都不知道系统是变好了还是变坏了——而这三类改动每天都发生在每个 Agent 团队。评测是把 Agent 开发从"手工艺"变成"工程"的分界线。
9.1 场景(scenario):评测的基本单位
一个场景 = 一次可重复的 Agent 运行 + 对结果的判定:
scenario = {
"id": "refund-policy-basic",
"name": "退款政策问答",
"task_prompt": "用户问:上个月买的耳机能退款吗?请查询政策并回答。"
"假设你已获取用户订单信息(耳机,35 天前购买)。",
"modul": "chat", # 运行形态
"assertions": [ # 程序化断言
{"type": "contains", "value": "35 天"},
{"type": "not_contains", "value": "无法退款"},
{"type": "regex", "value": "(?i)退款"},
],
"llm_rubric": "回答是否准确引用了退款政策并给出了可执行的下一步?",
}
两类判定器,各有分工:
- 程序化断言:contains / not_contains / regex。快、零成本、确定性强——但只能约束"形式";
- LLM-as-judge:用(更强的)模型按 rubric 给分。能评"质量",但有偏差(见 9.2)——所以评审不可用时不能阻塞判定,程序化断言必须独立成立。
场景设计的原则:从生产失败里长出来。每个线上失败案例都应该问"能不能变成一个场景";场景库的价值随真实度上升——纯拍脑袋的场景测不出真问题。
9.2 LLM-as-judge 的偏差与校准
judge 用模型评模型,偏差是系统性的,不是随机噪声:
| 偏差 | 表现 | 缓解 |
|---|---|---|
| 位置偏差 | 两个候选放一起评时,偏好先出现/后出现的那个 | 交换顺序评两次,取一致结论 |
| 自我偏好 | judge 偏爱与自己风格相似的输出(用模型 X 评模型 X 的输出要警惕) | judge 与被评模型来自不同家族 |
| 长度偏差 | 偏爱更长的回答 | rubric 里明确"简洁性是评分维度" |
| rubric 漂移 | 同样的 rubric 不同时间给分不同 | 固定打分锚点(每个分值给示例)+ 定期抽样人审校准 |
judge 的 prompt 工程同样重要——差的 judge prompt:
请评价以下回答的质量,给出 1-10 分。 # 没有锚点、没有维度、没有输出格式
合格的 judge prompt:
你是严格的评审。基于以下维度独立评分(每项 0-2 分):
1. 事实准确性:陈述与给定政策原文是否一致
2. 完整性:用户问题的各部分是否都被回答
3. 可执行性:用户能否直接按回答行动
输出 JSON:{"accuracy": x, "completeness": x, "actionability": x, "reasons": ["…"]}
逐条引用回答中的原句作为依据,不允许泛泛评价。
打分理由强制输出是校准的抓手:定期抽样人审"judge 的理由 vs 人的判断",分歧案例沉淀回 rubric——judge 本身也是一个需要评测的系统。
9.3 pass^k:测量非确定性
LLM 输出有随机性:同一场景跑 10 次,可能 7 过 3 不过。单次通过率掩盖了这一点。pass^k 指标:同一场景独立重跑 k 次,k 次全部通过才算通过。
pass@1 = 单次通过率(乐观,上线体验的下界不可靠)
pass^k = k 次全过率(保守,接近"每次都行"的用户体验)
工程含义:pass@1 = 90% 的场景,pass^8 ≈ 43%——如果用户每天触发这个场景 8 次,"感觉经常出错"就是真实体验。对高频场景必须用 pass^k 做门槛。成本按 k 倍计,所以 pass^k 用于关键场景子集,全量场景跑 pass@1。
9.4 轨迹评测:不只评结果,还评过程
只断言最终输出,会漏掉过程性退化。例:换模型后,回答质量不变,但"该查数据库时不再查了,开始瞎编"——输出断言全过,系统已经退化。
轨迹(trajectory)断言示例:
{"type": "tool_called", "value": "query_refund_policy"}, # 必须调用过政策查询
{"type": "tool_not_called", "value": "execute_shell"}, # 不允许碰 shell
{"type": "max_tool_calls", "value": 5}, # 效率上限
轨迹数据的采集粒度分级(全量保存的代价是存储与隐私):
- 轻量:工具名 + 参数摘要 + 耗时 + 是否成功(日常全量采集);
- 完整:全参数与完整输出(仅评测场景/失败案例/抽样开启)。
轨迹级回归的威力:改一个工具的 description,跑一遍场景库,报告显示"3 个场景的工具调用序列变了,其中 1 个判定退化"——这类退化用输出断言完全看不见。
9.5 回归测试:把评测接进开发流程
Agent 的 CI 与传统 CI 的差异:测试本身调用 LLM(有成本、有随机性)。分层设计:
每次 PR:核心冒烟集(10-20 个场景,便宜模型 judge 或纯程序化断言)—— < $1
每晚:全量场景库(数百场景)—— 成本可控,早晨看报告
每周:pass^k 深跑关键场景 + judge 校准抽样
发布前:候选 vs 线上基线 的对比评测(gate)
候选门禁(candidate gate)是发布流程的关键闸门:改动的候选版本在与线上完全一致的评测环境里跑场景库,与近 N 天基线分对比,退化即拦截。工程细节:候选运行的评测结果要单独标记(如 trigger=candidate),不能混入基线时间序列——否则基线被候选污染,门禁失去参照系。
9.6 在线评测与 A/B 实验
离线场景测不出的是:真实流量分布、真实用户反馈、真实成本。A/B 实验补上这一层。
分桶:确定性哈希,同一用户永远同桶:
def assign_bucket(user_id: str) -> int:
return int(hashlib.md5(user_id.encode()).hexdigest(), 16) % 100
# bucket < experiment.bucket_pct → 实验组;否则 → 基线组
指标:实验组的覆盖配置(如新 prompt/便宜模型)随请求生效,按桶聚合:
- 点踩率(用户负反馈率)——最直接的质量信号;
- 任务失败率、重试率;
- token 消耗与成本(实验组常为了省钱,省了多少要用数据说话)。
显著性检验:桶间点踩率差异用两比例 z 检验,避免"看两天数据拍脑袋":
import math
def two_proportion_z_test(success_a, n_a, success_b, n_b):
"""a=实验组, b=基线组。返回 z 与双尾 p 值。"""
if n_a <= 0 or n_b <= 0:
return None, None
p1, p2 = success_a / n_a, success_b / n_b
p_pool = (success_a + success_b) / (n_a + n_b)
se = math.sqrt(p_pool * (1 - p_pool) * (1 / n_a + 1 / n_b))
if se == 0:
return 0.0, 1.0
z = (p1 - p2) / se
p = math.erfc(abs(z) / math.sqrt(2)) # 双尾
return z, p
z, p = two_proportion_z_test(success_a=30, n_a=1000, success_b=50, n_b=1000)
# p < 0.05 才谈得上"差异显著";p=0.2 时看到的差距大概率是噪声
实验纪律:先定指标再开跑(否则会变成看着数据找口径);样本量要提前估算(低频事件如点踩需要更大的量);实验结束就关(长期分桶会让两群用户的体验持续分裂)。
9.7 数据飞轮:让失败产生价值
评测的终局形态是与改进闭环连接:
线上失败案例(点踩/任务失败)
→ LLM 归因(错在哪类环节:理解/工具选择/知识缺失/执行)
→ 改进候选(修 prompt / 补知识 / 改工具描述——按归因分类走不同管道)
→ 候选门禁评测(9.5)→ 人工审核 → 发布
→ 场景库沉淀:每个失败案例转化为一到多个新场景
再往前一步是训练数据:轨迹(9.4 完整模式)+ 结果信号(成功/失败/评分)→ SFT 数据集(成功轨迹)与 DPO 偏好对(同场景成败配对)→ 微调 → 回灌评测集验证。评测通过率就是这条流水线的质量门禁——没有评测体系的数据飞轮是在盲灌模型。
实现作业
- 给第 2-5 章的笔记 Agent 建 8 个场景(含 2 个故意刁难的:文件不存在、要求做危险操作),实现断言引擎与运行器;
- 实现 judge:按 9.2 的模板写 rubric prompt,输出 JSON 分数,评审失败时不阻塞(程序化断言独立成立);
- 实现 pass^k:同一场景 k=5 重跑,对比 pass@1 与 pass^8 的差异——找一个"大部分时候对偶尔翻车"的场景体会两者差距;
- 实现 A/B 分桶 + z 检验报告器,用两个假桶数据(各 1000 次)跑一遍,再调小样本量观察 p 值怎么失去意义。
深入材料
- τ-bench(arXiv 2406.12045,Sierra:工具 Agent + 用户模拟 + pass^k 指标的经典设定)
- Judging LLM-as-a-Judge(arXiv 2306.05685,MT-Bench:位置/长度/自我偏好偏差的系统性实验)
- promptfoo / OpenAI Evals(开源评测框架,场景组织方式的参考)
读者留言
COMMENTS 暂无还没有留言,来说第一句?