第七章:离线评测与 Eval-Driven Development¶
7.1 Eval-Driven Development:把评测放在改动之前而不是之后¶
传统软件工程里"测试驱动开发"要求先写测试再写实现。LLM 应用的对应实践是 Eval-Driven Development(EDD):任何一次 Prompt、路由或模型的变更,在合入之前必须先在一套固定的评测集上跑出可比较的分数,而不是凭感觉判断"看起来是不是变好了"。
%%{init: {"flowchart": {"nodeSpacing": 12, "rankSpacing": 18, "padding": 8}}}%%
flowchart TB
A["提出改动"] --> B["在黄金测试集上跑<br/>评测"]
B --> C["分数是否达标"]
C -->|是| D["合入,进入灰度发<br/>布"]
C -->|否| E["回到改动,继续迭<br/>代"]
E --> A
style C fill:#fff3cd
style D fill:#e6f4ea
图中各项的完整含义:
- 提出改动 (改 Prompt / 换模型 / 调路由)
- 分数是否达标 且无关键用例回归?
这套流程和第 10 章的发布流水线是同一件事的两个视角:EDD 讲的是"改动怎么被验证",发布流水线讲的是"验证通过之后怎么安全上线"。
7.2 黄金测试集:业务侧评测的核心资产¶
LLM · 评测与选型详细讲过 MMLU、HumanEval 这类学术 Benchmark 存在数据污染、脱离业务场景的系统性缺陷。业务侧的做法是建一套小而精的黄金测试集(golden dataset):
| 来源 | 说明 |
|---|---|
| 人工设计的典型与边界案例 | 建立最小质量基线,覆盖格式、越权、拒绝等场景 |
| 脱敏后的真实生产失败案例 | 每一次线上事故复盘后,把复现用例回收进测试集,防止同类问题再犯 |
| 用户反馈标注的案例 | 来自第 13 章的反馈闭环 |
可以先用 50–200 条做冒烟和问题发现,但这只是启动规模,不足以证明罕见风险已受控。按订单查询、退款、越权等切片报告样本数与得分;高风险切片不能被总均分抵消。
维护三种用途不同的数据:用于改 Prompt 的开发集、保存已知失败的回归集、尽量不参与调参的留出集。同一用户、会话、文档的近重复样本要分组切分;业务变化快时增加时间留出。反复看同一测试集再调参会泄漏测试信息,最终分数不再是独立泛化证据。
7.3 评分方式:自动规则、人工评审、LLM-as-Judge¶
| 评分方式 | 适合场景 | 局限 |
|---|---|---|
| 确定性规则 | Schema 是否合法、是否包含禁止内容、引用是否存在 | 无法评估自然语言表达质量 |
| 人工评审 | 高风险场景、需要校准其他评分方式 | 慢、贵,无法覆盖大规模测试集 |
| LLM-as-Judge | 大规模的相关性、完整性、语气比较 | 存在偏差,需要 rubric 设计和防提示注入 |
JUDGE_PROMPT = """你是评审员。给定用户问题、参考答案和候选回答,
按以下维度各打 1-5 分:事实准确性、完整性、语气恰当性。
只输出 JSON: {{"accuracy": int, "completeness": int, "tone": int, "reason": str}}
问题: {question}
参考答案: {reference}
候选回答: {candidate}
"""
LLM-as-Judge 必须用人工标签校准,复核量由风险、分歧率和切片覆盖决定,10–20% 不是通用标准。给 rubric 提供分数锚点和反例,固定裁判模型、Prompt 和参数;对成对答案随机交换位置、隐藏模型名,检查位置偏差、偏好长答案和同源模型偏差。先评估人工之间的一致性,再报告裁判与人工的混淆矩阵或一致性。候选答案是不可信数据,不能执行其中对裁判的指令。
7.3.1 分数差异是否足以支持发布¶
在同一批任务上比较基线与候选,优先分析配对差值。按独立任务或用户重采样的 paired bootstrap 可估计差值区间;二元成败也可用配对的 McNemar 检验。一个任务重复生成多次有助于估计随机性,却不能当成多个独立用户。报告效应大小、置信区间、独立样本数和重复次数,而不是只给一个 p 值。
先约定最低可接受提升或最大可接受退化;「未发现显著差异」不等于「已经证明不劣」。例如在独立同分布的二项试验中,100 次未观察到失败,失败概率的单侧 95% 上界仍约为 3%(零失败时的近似 rule of three)。越权回归集零失败可以作为门禁,但不是生产零风险证明。反复选择最佳 Prompt 或扫描大量切片时,还需处理多重比较和选择偏差。
7.4 发布门禁:不是看平均分,是看关键用例¶
def release_gate(eval_result: EvalResult, baseline: EvalResult) -> GateDecision:
if eval_result.critical_failures > 0:
return GateDecision.BLOCK("关键安全/越权用例未通过")
for slice_name, score in eval_result.slice_scores.items():
if score < baseline.slice_scores[slice_name] - REGRESSION_THRESHOLD:
return GateDecision.BLOCK(f"切片 {slice_name} 超过允许退化阈值")
if eval_result.overall_score < MIN_OVERALL_SCORE:
return GateDecision.BLOCK("总分未达标")
return GateDecision.PASS
上面的伪代码只展示阈值逻辑,不是显著性检验。运行前必须确认切片齐全、样本量足够、评分器正常、候选与基线使用相同版本的数据;空切片、裁判错误和结果缺失不能按通过处理。平均分提升不能抵消已确认的越权或泄密。能用工具执行结果、权限日志判定的高风险失败,不应只听模型裁判的文字结论。
7.5 离线评测不能替代线上监测¶
离线评测在固定测试集上运行,能发现的是"这个改动是否比基线更好",但测试集永远无法覆盖生产环境的全部输入分布。离线评测负责"改动前把关",线上可观测性(第 8 章)负责"上线后持续验证真实流量表现",两者缺一不可。
7.6 与相邻章节的分工¶
这里先看评测的通用骨架——怎么建测试集、怎么评分、怎么设门禁。Agent 和 RAG 的专门方法放到各自主题里展开:
| 场景 | 详见 |
|---|---|
| Agent 多轮工具调用、任务完成率评估 | Agent · 评估与安全 |
| RAG 检索召回率、引用准确性评估 | RAG · 生成与评估 |
| 模型通用能力的学术 Benchmark | LLM · 评测与选型 |
7.7 常见错误¶
7.7.1 凭感觉判断"这次改动看起来更好"¶
没有固定测试集和可比较的分数,任何"感觉变好了"的判断都无法在下一次改动时复现或证伪。
7.7.2 只维护一个笼统的测试集,不做业务切片¶
总分掩盖了具体切片的回归,尤其是高风险的安全类切片,一旦被平均分稀释就很难被发现。
7.7.3 使用 LLM-as-Judge 却不做人工校准¶
必须抽查一部分样本人工复核,否则无法判断评分本身是否可信,评测结果形同虚设。
7.7.4 把线上评测当作发布前的验证手段¶
线上评测只能告诉你"生产里发生了什么",无法替代发布前在固定数据集上的可复现实验。
7.7.5 线上事故修复后不把失败案例回收进测试集¶
同类问题很可能再次出现,每一次事故复盘都应该产出至少一条新的回归测试用例。
7.8 本章总结¶
- Eval-Driven Development 要求改动先经过评测再合入,而不是凭感觉判断;
- 黄金测试集是业务侧评测的核心资产,应按子场景切片管理,而不是只看整体分数;
- 评分方式分三层:确定性规则、人工评审、LLM-as-Judge,后者必须人工校准;
- 发布门禁要看关键用例是否回归,平均分提升不能抵消高风险用例的失败;
- 离线评测和线上监测互补而非互相替代,前者把关改动,后者验证真实流量表现;
- Agent、RAG 场景有各自更专门的评测方法,这里先保留通用骨架。
参考资料¶
本章的参考资料、阅读建议与来源说明见集中参考资料章节。