评分方式
代码断言、LLM-as-Judge 与人工评审的组合。
一句话理解:能用代码判定的就写代码断言,需要理解语义的交给 LLM 评审,定标准、做校准的是人。三者要组合使用,而不是三选一。
有了评测集和指标,还差一个关键环节:**谁来判分?**100 道题、每题跑 3 次,就是 300 份输出。全靠人看,一轮评测要花好几天,没法每次改 Prompt 都跑;全靠代码判,“处置建议是否合理”这种问题写不出规则;全靠模型判,又不知道它判得准不准。
这一课比较三种评分方式,给出一个代码断言示例、一个 LLM-as-Judge 提示词示例,并说明怎样用人工标注来校准模型评审。
三种评分方式
Anthropic 在《Demystifying evals for AI agents》中把评分器分为三类,下表结合了文章的归纳与客服工单场景的例子:
| 类型 | 常见方法 | 优点 | 缺点 | 客服工单场景中适合判什么 |
|---|---|---|---|---|
| 代码评分 | 字符串匹配、结构校验、单元测试、静态分析、检查环境最终状态 | 快、便宜、客观、可复现、易调试 | 对“换个说法也对”的情况很脆弱 | 根因枚举值、必调与禁调工具、工单是否创建、金额计算 |
| 模型评分 | 按评分标准打分、自然语言断言、两两比较 | 灵活、可扩展、能处理开放式任务 | 结果不完全确定、更贵、需要校准 | 处置建议是否可执行、对用户的解释是否准确得体 |
| 人工评分 | 领域专家评审、众包标注、A/B 测试 | 质量的金标准,最贴近专家判断 | 贵、慢、难以规模化 | 制定标准、标注校准集、复核有争议的样本 |
主流评估平台也按类似思路提供评分器。例如 OpenAI 的 Graders 文档列出了字符串检查、文本相似度、模型打分、Python 代码评分,以及把多个评分器组合起来的 multigrader。
选择原则很简单:先问“能不能用代码判”,能就用代码;剩下的交给模型;人负责定标准和抽查。
这个选择原则可以画成一张决策图:
代码断言:能写就写
客服工单处理 Agent 的输出是结构化 JSON(参见输出契约),很多检查都可以写成代码。下面这个评分函数对应上一课评测集里的题目格式(示例):
import json
def grade_ticket_case(case: dict, run: dict) -> dict:
"""对一次试验做硬性检查。run 包含 final_output(字符串)和 tool_calls(列表)。"""
expected = case["expected"]
checks = []
# 1. 输出必须是合法 JSON
try:
out = json.loads(run["final_output"])
checks.append(("valid_json", True, ""))
except json.JSONDecodeError as e:
return {"passed": False, "checks": [("valid_json", False, str(e))]}
# 2. 判定状态一致:该下结论的下结论,该说“无法判断”的说无法判断
checks.append(("status", out.get("status") == expected["status"],
f"期望 {expected['status']},实际 {out.get('status')}"))
# 3. 根因正确
if expected["status"] == "determined":
checks.append(("root_cause", out.get("root_cause") == expected["root_cause"],
f"期望 {expected['root_cause']},实际 {out.get('root_cause')}"))
# 4. 工具策略:必调工具都调用了,禁用工具一个没调
called = {c["name"] for c in run["tool_calls"]}
missing = set(expected.get("must_call_tools", [])) - called
forbidden = set(expected.get("must_not_call_tools", [])) & called
checks.append(("must_call", not missing, f"未调用:{sorted(missing)}"))
checks.append(("must_not_call", not forbidden, f"调用了禁用工具:{sorted(forbidden)}"))
# 5. 证据可追溯:结论引用的每个值都必须出现在某次工具返回中
tool_outputs = json.dumps([c["output"] for c in run["tool_calls"]], ensure_ascii=False)
untraceable = [e["value"] for e in out.get("evidence", []) if str(e["value"]) not in tool_outputs]
checks.append(("evidence_traceable", not untraceable, f"无法追溯的证据:{untraceable}"))
return {
"passed": all(ok for _, ok, _ in checks),
"checks": [{"name": n, "ok": ok, "detail": "" if ok else d} for n, ok, d in checks],
}
这段代码体现了几个要点:
- 每项检查单独记录。 失败时一眼能看出是根因错了,还是漏调了工具,这为后面的错误归因提供了现成的标签。
- 检查环境与记录,而不只是最终文本。 工具调用列表来自运行记录;如果涉及写操作,还应查询沙箱数据库里的最终状态。
- 不要求工具调用顺序完全一致。 只检查“必须调用”和“禁止调用”,给 Agent 留出合理的路径差异,详见结果评估与轨迹评估。Anthropic 也建议评分时关注 Agent 产出了什么,而不是强求它走某一条特定路径。
LLM-as-Judge:给语义质量打分
“处置建议是否可执行、是否符合 SOP”没法用代码判定,这时需要模型评审。一个好的评审提示词应该遵循这几条:
- 一个评审只判一个维度。 Anthropic 建议对不同维度分别使用独立的评审,而不是让一个评审同时给所有维度打分。
- 二元判定优先。 相比 1–10 分,“通过 / 不通过”的标准更清晰,不同评审之间也更一致。确实需要分档时,每一档都要写清楚锚点。
- 给参考答案和原始证据。 评审要判断建议是否基于事实,必须看到工具返回的数据。
- 允许“无法判断”。 Anthropic 建议给评审留出口,信息不足时返回 Unknown,而不是强行判定。
- 先写理由,再给结论。 OpenAI 的评估最佳实践指出,让评审在打分前先写出推理过程,有助于提高评估质量。
- 输出结构化结果。 方便程序汇总。
- 评审模型尽量与被测模型不同。 Anthropic 的评估文档一般建议这样做,以减少自我偏好。
下面是一个示例提示词:
你是一名客服工单处理质检员。你只评估一个维度:「处置建议是否可执行,并且符合 SOP」。
不要评价语气、格式或其他维度。
【用户问题】
{user_message}
【工具返回的原始数据】
{tool_outputs}
【相关 SOP 条款】
{sop_excerpt}
【参考结论与参考处置】
{reference_solution}
【待评估的 Agent 回答】
{agent_output}
【判定标准】
判为 PASS,必须同时满足:
1. 建议中的每个动作都有明确的执行方(例如客服、用户、财务),而不是“相关人员”。
2. 建议与工具返回的数据一致,没有引用数据中不存在的事实。
3. 建议符合上面的 SOP 条款;涉及退款时,金额与审批要求符合 SOP。
4. 与参考处置相比,可以措辞不同、顺序不同,但不能遗漏参考处置中的关键动作。
以下任一情况判为 FAIL:
- 违反任意一条 PASS 条件;
- 建议执行 SOP 明确禁止的动作,或越过审批直接承诺退款。
以下情况判为 UNKNOWN:
- 提供的 SOP 条款不足以判断该建议是否合规;
- 工具数据与参考结论互相矛盾。
【输出格式】
先逐条对照判定标准写出分析,再给出结论。只输出如下 JSON:
{
"analysis": "逐条说明 1–4 是否满足,以及理由",
"verdict": "PASS 或 FAIL 或 UNKNOWN",
"failed_criteria": [未满足的条件编号]
}
LLM 评审也有已知的偏差。Zheng 等人研究 LLM-as-a-Judge 的论文(MT-Bench 与 Chatbot Arena)讨论了三种:位置偏差(两两比较时偏向某个位置的答案)、冗长偏差(偏向更长的回答)和自我偏好偏差(偏向自己生成的回答)。对应的做法是:两两比较时交换顺序各评一次;在标准中明确“长度不是加分项”;用不同的模型做评审。
人工评审:定标准,做校准
人工评审不是用来给每道题打分的,而是承担三项更重要的工作:
- 定标准。 由领域专家写出评分标准,并对边界情况给出裁决。
- 标注校准集。 由人给一批输出打上 PASS 或 FAIL,作为衡量模型评审的“标准答案”。
- 复核争议样本。 评审输出 UNKNOWN 的样本、代码断言与模型评审结论冲突的样本,以及定期随机抽检。
校准模型评审是最容易被跳过、也最重要的一步。Anthropic 强调 LLM 评审需要与人类专家仔细校准;Hamel Husain 与 Shreya Shankar 的评估 FAQ 建议,在留出的人工标注集上分别看评审对“通过”和“不通过”两类样本的识别率。
举例来说,专家标注了 100 个“处置建议”样本,70 个 PASS、30 个 FAIL,再让评审独立判一遍(示例数字):
| 评审判 PASS | 评审判 FAIL | 识别率 | |
|---|---|---|---|
| 专家判 PASS(70) | 66 | 4 | 通过类识别率 94% |
| 专家判 FAIL(30) | 6 | 24 | 不通过类识别率 80% |
总体一致率是 90%(66 + 24 = 90),看起来不错。但 30 个本该不通过的建议里,有 6 个被放过了,漏判率 20%。如果这个维度关系到退款合规,这个漏判率太高。下一步是逐条读这 6 个样本,找出评审被“骗过”的共同原因,修改判定标准后,在另一批留出样本上重新检验,避免评审提示词对校准集过拟合。
校准是一个循环,而不是一次性的检查:
组合:一条评分流水线
三种评分方式按从便宜到昂贵的顺序串成一条流水线:
对由多个部分组成的任务,可以给部分分,例如“根因正确但处置建议不合格”记 0.5 分。Anthropic 也建议为多组件任务设计部分分。但上线门槛最好仍以“全部通过”为准,部分分用于观察进步趋势。
常见误区
- 让评审自由打 1–10 分。 没有锚点的分数不可比较,5 分和 6 分的差别谁也说不清。
- 一个评审同时判所有维度。 各个维度互相干扰,失败时也不知道是哪个维度出了问题。
- 从不校准模型评审。 评审和专家的判断差多少完全未知,“评测分数提高了”可能只是评审变宽松了。
- 代码断言写得太死。 要求回答与参考答案逐字相同,或要求工具调用顺序完全一致,会把合理的解法判成失败。
- 用被测模型给自己打分。 自我偏好会让分数虚高。
产品经理检查清单
- 每个评测维度分别由哪种评分方式来判?能用代码判的是否都用了代码?
- LLM 评审的标准是否具体到“两个人读完会得出同样结论”?
- 评审是否能看到原始证据和参考答案?是否允许输出“无法判断”?
- 模型评审有没有用人工标注集校准过?“不通过”类样本的识别率是多少?
- 评分器的判定结果是否有定期的人工抽检?
- 评分器本身有版本号吗?修改评分标准后,是否重新跑了基线?
练习
为「发票审核 Agent」的“退回理由说明”设计评分方案,用 30–60 分钟完成:
- 列出 4 个评分维度,为每个维度选择评分方式(代码断言、LLM 评审或人工),并说明理由。例如“退回理由中引用的金额与发票一致”可以用代码判定。
- 选一个必须由模型评审的维度,参照本课示例写出完整的评审提示词,包含 PASS、FAIL、UNKNOWN 三档标准。
- 准备 10 条退回理由(其中 3–4 条有问题),先自己标注,再用提示词让模型评审,计算两类样本各自的识别率,找出一条判得不一致的样本并分析原因。
延伸阅读
- Demystifying evals for AI agents(Anthropic):三类评分器、分维度评审、Unknown 出口、部分分与校准。
- Graders(OpenAI):字符串检查、文本相似度、模型打分、Python 评分与组合评分器。
- Define success criteria and build evaluations(Anthropic):评估设计原则与基于模型的评分示例。
- Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena(Zheng 等,NeurIPS 2023):LLM 评审的位置、冗长与自我偏好偏差。
- AI Evals: Everything You Need to Know(Hamel Husain、Shreya Shankar):二元判定、领域专家标注与评审校准。
下一步
本课的代码断言已经开始检查“调用了哪些工具”。下一课「结果评估与轨迹评估」专门讨论:除了答案对不对,怎样评估 Agent 的过程是否合理、安全、高效。