# Eval 自动化与回归测试

Eval 是 evaluation 的简称，意思是评估。对提示词工程来说，Eval 不是“让 AI 自己夸自己好不好”，而是把 AI 输出质量变成可重复测试、可比较、可回归检查的工程流程。

没有 Eval，提示词优化很容易变成凭感觉改来改去：这次看起来更好，下一次换一批输入又坏了。Eval 的目标是让你能回答三个问题：

1. 这个 prompt、RAG 或 Agent 到底好不好？
2. 新版本是否比旧版本更好？
3. 哪些失败样例必须进入长期回归测试？

截至 2026-06-14，OpenAI 官方文档提示旧 Evals 平台有退场时间：现有内容会保留过渡期，平台计划在 2026-10-31 对现有用户变为只读，并在 2026-11-30 关闭。因此，本讲重点不绑定某个平台，而是讲稳定的工程方法：评估集、评分器、运行器、报告、阈值、失败样例和持续回归。

本讲常见术语先按下面理解，详细定义和更多例子见 [00.5-术语词典与最小用例](00.5-术语词典与最小用例.md)。

| 术语 | 直觉解释 | 最小例子 |
| --- | --- | --- |
| Eval | 可重复评估 AI 输出质量的流程 | 50 个样例每次改 prompt 后都跑一遍 |
| Evaluation set | 固定测试样例集合 | 普通、长文档、缺失信息、对抗样例 |
| Rubric | 评分标准 | 忠实性 0-5 分，格式 0-2 分 |
| LLM-as-judge | 用另一个模型按标准评分 | 让评分模型判断回答是否引用充分 |
| Gold set | 人工确认过的高质量标准答案集 | 法务确认过的 30 个合格摘要 |
| Regression test | 防止旧错误复发的测试 | 曾经漏掉金额的案例永久加入测试集 |

## 一、直觉

传统代码测试像这样：

```text
输入 -> 函数 -> 输出 -> 断言是否等于预期
```

LLM 评估像这样：

```text
输入样例 -> AI 系统 -> AI 输出
-> 评分器检查输出是否满足标准
-> 汇总分数、失败原因、成本、延迟
-> 决定是否接受新版本
```

区别在于：AI 输出经常不是唯一正确答案。所以 Eval 不总是检查“是否完全相等”，更多时候检查“是否满足任务标准”。

例如总结任务不应该只问：

```text
输出是否等于参考答案？
```

更应该问：

```text
是否忠实于原文？
是否覆盖关键事实？
是否遗漏重要数字？
是否把不确定信息说成确定？
是否符合输出格式？
```

Eval 的本质是：把“好不好”拆成可检查的维度。

## 二、Eval 系统的基本结构

一个成熟 Eval 系统通常包括：

| 组件 | 作用 |
| --- | --- |
| 评估目标 | 说明要验证什么能力 |
| 评估集 | 一批固定测试样例 |
| 被测系统 | prompt、RAG、Agent、模型版本或完整应用 |
| 运行器 | 批量执行测试样例 |
| 评分器 | 判断输出质量 |
| 指标 | pass rate、平均分、严重错误数、成本、延迟 |
| 报告 | 比较版本、列出失败样例和原因 |
| 阈值 | 决定是否允许上线 |
| 回归集 | 从历史失败中沉淀的长期测试集 |

可以用一条链路表示：

```text
目标 -> 数据集 -> 运行 -> 评分 -> 报告 -> 错误分析 -> 修改 -> 回归
```

这条链路比某一个工具更重要。工具会变，但链路应该长期保留。

## 三、评估集怎么建

评估集不是随便找几条样例。它要代表真实任务。

最小结构：

```json
{
  "id": "case_001",
  "task": "summarize_document",
  "input": "...",
  "expected": {
    "must_include": ["关键事实A", "金额B"],
    "must_not_include": ["原文未出现的结论"],
    "format": "markdown_sections"
  },
  "rubric": "按忠实性、完整性、精确性、格式评分",
  "tags": ["normal", "long_document"]
}
```

评估集至少要覆盖：

- 普通样例：最常见输入。
- 长输入样例：测试上下文管理。
- 信息缺失样例：测试拒答和不确定性。
- 边界样例：测试规则边界。
- 格式混乱样例：测试鲁棒性。
- 对抗样例：测试提示注入和越权请求。
- 历史失败样例：防止同样错误再次出现。

评估集不要只包含“模型容易答对”的样例。真正有价值的是能暴露失败的样例。

## 四、评分器的层级

评分器越简单、越确定，越应该优先使用。

### 1. 程序评分

最可靠，适合明确格式和确定答案。

例子：

- JSON 是否可解析。
- 必填字段是否存在。
- 分类结果是否等于标准答案。
- 输出是否包含指定引用编号。
- 数字、日期、ID 是否匹配。
- 工具调用参数是否符合 schema。

优点：快、便宜、稳定。

缺点：不擅长判断语义质量。

### 2. 规则评分

适合半结构化任务。

例子：

- 输出必须少于 300 字。
- 每个结论必须包含来源标注。
- 不允许出现“根据常识可知”。
- 不能包含外部资料中的提示注入指令。

优点：清楚、可解释。

缺点：规则写不全时会漏判。

### 3. LLM-as-judge

让另一个模型按评分标准评价输出。

适合：

- 总结质量。
- 语气风格。
- 复杂推理是否合理。
- 回答是否真正解决用户问题。
- 多个答案之间哪个更好。

使用 LLM judge 时要注意：

- 给明确 rubrics，不要只说“评估好坏”。
- 让它输出结构化分数和失败原因。
- 尽量使用二分类、三档或 1-5 分，不要只要长篇评论。
- 用人工标注的小样本校准 judge。
- 对重要任务保留人工复核。
- 不要让 judge 看到候选版本名称，避免偏见。

### 4. 人工评分

最贵、最慢，但在高风险任务里不可替代。

适合：

- 建立第一版 gold set。
- 校准 LLM judge。
- 复核严重失败。
- 判断业务上真正能不能上线。

## 五、普通 Prompt 的 Eval

普通 prompt 的评估重点是输出质量和稳定性。

常见指标：

- 忠实性：是否只基于输入材料。
- 完整性：是否覆盖关键要求。
- 精确性：数字、日期、名称是否正确。
- 格式：是否符合结构。
- 拒答：信息不足时是否不编造。
- 风格：语气、长度、专业度是否符合要求。
- 稳定性：多次运行是否大体一致。

最小评估表：

| case_id | 输入类型 | 预期 | 实际输出 | 分数 | 严重错误 | 是否通过 |
| --- | --- | --- | --- | --- | --- | --- |
| p001 | 普通 | 覆盖 A/B/C |  |  |  |  |
| p002 | 信息缺失 | 应标注不足 |  |  |  |  |
| p003 | 对抗输入 | 不执行注入指令 |  |  |  |  |

Prompt 改版时，不要只看平均分。必须单独看严重错误数。一个版本平均分提高，但新增了高风险幻觉，仍然不能上线。

## 六、RAG 的 Eval

RAG 评估要分两层：

```text
检索是否找到了正确资料？
生成是否忠实使用了正确资料？
```

检索层指标：

- context recall：该找出的关键资料是否被找出。
- context precision：检索结果中噪声多不多。
- rank quality：正确资料是否排在前面。
- metadata filtering：权限、日期、版本过滤是否正确。

生成层指标：

- groundedness：回答是否有资料支持。
- citation accuracy：引用是否真的支持结论。
- answer completeness：是否答全。
- abstention：资料不足时是否拒答。
- conflict handling：来源冲突时是否说明冲突。

RAG 失败分析要先问：

1. 正确资料有没有进入检索结果？
2. 正确资料有没有进入模型上下文？
3. 模型有没有使用正确资料？
4. 引用是否支持结论？
5. 如果资料不足，模型是否承认不足？

不要一看到答案错就立刻改 prompt。RAG 的错误常常出在 chunk、metadata、召回、重排或上下文打包。

## 七、工具调用和 Agent 的 Eval

Agent 不能只评最终答案，因为它可能中途做错事。

Agent Eval 要看 trace：

```text
用户目标
-> Agent 计划
-> 工具选择
-> 参数
-> 工具结果
-> 下一步决策
-> 停止条件
-> 最终输出
```

关键指标：

- 工具选择是否正确。
- 参数是否完整、合法、来自可信上下文。
- 是否优先使用只读工具。
- 高风险操作前是否请求确认。
- 工具失败时是否降级或停止。
- 是否把工具结果忠实解释给用户。
- 是否执行了外部内容中的恶意指令。
- 是否在完成后停止。

Agent 评估样例一定要包含：

- 工具返回空结果。
- 工具报错。
- 用户信息不足。
- 用户要求高风险写操作。
- 外部内容包含提示注入。
- 两个工具名称相似，容易误选。
- 已完成任务但模型可能继续调用工具的场景。

## 八、回归测试

回归测试的意思是：过去出过的错，以后不能再犯。

每次发现失败，都要判断是否加入回归集：

```text
失败样例 -> 归因 -> 修复 -> 加入回归集 -> 后续每次改动都跑
```

应该加入回归集的样例：

- 严重幻觉。
- 金额、日期、权限、工具参数错误。
- 格式破坏导致程序无法消费。
- 安全边界被绕过。
- RAG 引用错误。
- Agent 未确认就执行高风险操作。
- 用户明确指出的错误。

回归集不是越大越好。它应该代表真实风险，并保持可维护。

## 九、自动化运行

一个平台无关的 Eval 自动化流程可以这样设计：

```text
eval_cases.jsonl
-> run_candidate.py
-> outputs.jsonl
-> grade_outputs.py
-> report.md
-> fail / pass
```

触发时机：

- 修改 prompt。
- 更换模型。
- 调整温度、输出长度、reasoning effort 等参数。
- 修改 RAG chunk、embedding、重排或上下文打包。
- 修改工具定义、参数 schema 或权限。
- 修改 Agent 工作流。
- 上线前。
- 定期从生产日志抽样。

报告至少包含：

- 总通过率。
- 各维度平均分。
- 严重失败数。
- 与上一版本相比的提升和下降。
- 新增失败样例。
- 成本和延迟变化。
- 是否达到上线阈值。

## 十、上线阈值

不要用“总体 80 分”这种模糊标准上线。应该按风险拆分阈值。

示例：

```text
上线要求：
1. 严重安全失败 = 0
2. JSON 格式通过率 >= 99%
3. 忠实性平均分 >= 4.5 / 5
4. 关键字段准确率 >= 98%
5. RAG 引用准确率 >= 95%
6. P95 延迟 <= 3 秒
7. 单次平均成本不超过旧版本 120%
```

注意：平均分不能掩盖严重错误。高风险任务中，一次严重失败可能比 100 次小瑕疵更重要。

## 十一、错误分析

Eval 的价值不只是给分，而是帮助你决定下一步该改哪里。

常见归因：

| 失败类型 | 优先改哪里 |
| --- | --- |
| 输出格式不稳定 | 结构化输出、schema、少量 prompt |
| 原文没依据却编造 | 资料边界、拒答规则、RAG 引用 |
| 检索不到资料 | chunk、embedding、关键词、metadata、重排 |
| 有资料但没用对 | 上下文打包、回答规则、引用规则 |
| 工具选错 | 工具名、描述、schema、工具数量 |
| 参数填错 | 参数 schema、输入校验、澄清问题 |
| 高风险操作失控 | 权限、确认节点、审批、工具分层 |
| 成本延迟过高 | 模型选择、上下文长度、缓存、批处理 |

这就是 Eval 驱动开发：不是“分数低就继续加提示词”，而是根据失败原因选择正确工程手段。

## 十二、常见误区

### 误区 1：人工看几个样例就够了

不够。人工检查可以发现问题，但不能保证每次改动后没有回归。

### 误区 2：平均分高就能上线

不一定。平均分会掩盖严重失败。高风险任务要单独统计严重错误。

### 误区 3：LLM judge 一定客观

不对。LLM judge 也会偏、会漏、会受提示词影响。必须用人工标注样例校准。

### 误区 4：Eval 只评最终答案

不对。RAG 要评检索，Agent 要评工具调用轨迹，结构化输出要评 schema。

### 误区 5：评估集建好后就不用管

不对。生产中的新失败、新输入分布、新模型行为都要不断进入评估集。

## 十三、理解检查

请你试着回答：

1. 为什么 LLM 评估不能只用“输出是否等于参考答案”？
2. 程序评分、LLM judge、人工评分分别适合什么场景？
3. RAG 评估为什么要分成检索层和生成层？
4. Agent Eval 为什么必须看工具调用 trace？
5. 一个 prompt 改版上线前，至少应该看哪些指标？

## 十四、下一步

下一步可以继续补两个方向：

1. 多智能体协作：什么时候需要 subagents、handoff、并行评审和任务分派。
2. 成本与延迟优化：Prompt caching、上下文裁剪、批处理、模型分层和监控。
