系列 《评测、可观测与可追溯:怎么知道改了之后更好了》 第 1 / 1 篇 进阶
系列总览 — 为什么这样组织、读它需要什么、读完能做什么
学习地图 — AI 应用工程师学习地图 第 5 / 7 个系列
- 评测集——AI 应用的单元测试
上一个系列:工具、Agent 与 harness
前四层每一篇都在说”在评测集上证明”。这一篇讲评测集本身:它是 AI 应用的单元测试——但与单元测试有三点不同:期望是分布不是值(同一输入跑十次结果不同,《模型作为组件(01):失效模式——非确定性、幻觉、上下文与越界,把「模型会出错」拆成七条可检测的性质》),用例从生产流量来而不是写出来(你想不出用户会怎么问),依赖会在你不动的时候变(模型升级)。这三点决定了评测集怎么建、多大、怎么维护。
一个数字说明这一层的现状:LangChain 2026 年的报告里,89% 的团队接了可观测,只有 52% 接了评测;质量是 agent 部署的最大障碍(32%)。有 trace 没评测集的团队,每次改动靠看几个例子上线——这是”看得见”与”能判断”之间的差距。
本篇要回答的核心问题是:
评测集从哪来、每条长什么样、多大够用?1 怎么防过拟合、怎么随时间维护、怎么与版本绑定?2 合成数据能用吗,标注怎么组织?3
一、总览
1. 与单元测试的对照
%%{init: {"flowchart": {"wrappingWidth": 200}}}%%
%% 图:评测集的生命周期:流量与红队 → 标注 → 分层 → 门禁 → bad case 回流
flowchart TB
L["生产流量(trace)"] -- "按<b>类型</b>采样,不按流量比例" --> S["候选用例"]
R["红队 / 安全用例<br/>(流量里不会自然出现,专门写)"] --> S
S --> A["标注:期望是<b>分布</b>不是值<br/>通过率 ≥ 80%、必含 / 必不含、schema"]
A --> T{"分层"}
T --> T1["冒烟集:几十条,每次改动都跑"]
T --> T2["全量集:几百到几千条,发布前跑"]
T --> T3["hold-out:不给调 prompt 的人看,防过拟合评测集"]
T1 & T2 --> G["门禁(第四篇)<br/>绑定 prompt 版本 + 模型快照"]
G -- "线上 bad case" --> S
G -. "模型升级 → 依赖变了,全量重跑" .-> T2
| 单元测试 | 评测集 | |
|---|---|---|
| 用例从哪来 | 开发者写 | 生产流量采样 + 少量手写的边界 |
| 期望 | 一个确定的值 | 精确值 / 可接受集合 / 评分标准 / 验收脚本;按分布看(跑 k 次) |
| 通过标准 | 全过 | 指标不低于基线减噪声阈值;逐条 diff |
| 依赖 | 固定 | 模型会变(升级、静默变更)→ 要定期重跑 |
| 维护 | 随代码改 | 随 bad case 长;hold-out 防过拟合 |
| 规模 | 越多越好 | 几十条开始有区分力,几百条看趋势;每条 k 次的成本要算 |
| 判分 | 断言 | 规则 → judge → 人(第二篇) |
2. 本文的章节安排
第二章来源;第三章每条的结构;第四章规模与分层;第五章维护、hold-out、版本绑定;第六章合成数据;第七章标注的组织;第八章实践建议。
二、来源
1. 真实流量
评测集的第一来源是日志里的真实输入。三类都要:
| 类 | 作用 | 怎么采 |
|---|---|---|
| 高频 | 决定平均质量;改动的主要影响面 | 按类型聚类后每类抽样 |
| 边界 | 暴露脆弱点 | 长输入、多轮、含数字 / 日期 / 专名、多语言、格式异常、歧义 |
| 失败过的 | 最有信息量 | 点踩、重试、修改后重提、放弃、转人工、追问——每条带 trace(第六篇的反馈绑定) |
采样要覆盖类型而不是按流量比例——按比例采会让 90% 的用例是最简单的那类,改动对难例的影响看不见。
%% 图:按流量比例采 vs 按类型采(100 条评测集)——流量里 90% 是简单的高频问法,按比例采得到 90 条简单题、边界与失败过的各不到 5 条,改动对难例的影响看不见;按类型采:高频 40、边界 25、失败过的 25、红队 10,每一类都有足够的样本量能单独报告
%%{init: {"xyChart": {"width": 760, "height": 340, "plotReservedSpacePercent": 60}, "themeVariables": {"xyChart": {"plotColorPalette": "#c0392b, #5b8fd6, #4d9a5c"}}}}%%
xychart-beta
title "100 条评测集的构成:按流量比例采 vs 按类型采"
x-axis ["高频", "边界", "失败过的", "红队 / 安全"]
y-axis "条数" 0 --> 100
bar [90, 5, 4, 1]
bar [40, 25, 25, 10]
红柱按流量比例,蓝柱按类型。红柱上「失败过的」只有 4 条——一次改动让这类掉 50% 也就是 2 条,在噪声里;蓝柱的 25 条能分出方向。
2. 上线前
没有日志时:内部试用的记录、客服工单里的真实提问、竞品的公开案例、领域专家写的”用户一定会问的”——都比工程师坐在那想强。上线后第一件事是换成真实流量。
3. 红队与安全
注入、越界请求、敏感信息、边缘的伦理场景——这些不会自然出现在流量里(或出现了也不该等它出现),要专门写。《检索与知识接入(07):检索评测与运营》的”不该看到”查询集、《工具、Agent 与 harness(09):可靠性、评测与运营 agent》的注入用例、L6 的红队集都属于这里。
三、每条的结构
1. 四个字段
input: 用户输入 + 必要的上下文与状态
- 单步:输入文本(+ 系统提示的版本引用)
- RAG:查询 + 固定的检索结果(评生成)或标注的相关块(评检索)
- agent:任务 + 环境快照(容器镜像 / 仓库提交 / 数据库状态)+ 可用工具
- 多轮:对话历史 + 当前轮
expected: 期望,四种形态之一
- 精确值(分类标签、抽取的字段)
- 可接受集合(同义答案;数值容差)
- 评分标准(rubric:judge 或人按它打分)
- 验收脚本(测试通过、断言、输出 schema)
meta: 类型 · 难度 · 来源(流量 / 手写 / 红队)· 采样日期 · 标注者 · 关联的 bad case id
score_how: 用哪种评分(规则 / judge + rubric 版本 / 人)
2. 期望要可检验
《模型作为组件(05):选型——榜单的失真、自己的评测集与供应商的弃用周期》的原则:没有可检验期望的用例不是评测用例。”回答要好”不可检验;”回答必须引用第 3.2 条且给出金额 500 元”可检验;”按 rubric(含五条判据)由 judge 打 1–5 分,基线 3.8”也可检验(judge 校准过——第二篇)。写期望时问:两个人独立判会一致吗?不一致就把判据写细。
%% 图:期望的四种形态与它对应的评分方式——精确值用规则比对(分类标签、抽取字段);可接受集合用规则(同义答案列表、数值容差);评分标准 rubric 由校准过的 judge 或人按判据打分;验收脚本直接跑(测试通过、schema 校验、断言);从上到下自动化程度递减、成本递增,能用上面的不用下面的
flowchart LR
Q["一条用例的 expected"] --> E1["精确值<br/>「退款」· 金额 500"]
Q --> E2["可接受集合<br/>{「两年」「24 个月」}· 数值 ± 1%"]
Q --> E3["评分标准 rubric<br/>五条判据,1–5 分,基线 3.8"]
Q --> E4["验收脚本<br/>测试通过 · schema 校验 · 断言"]
E1 --> S1["规则比对:免费 · 确定"]
E2 --> S1
E3 --> S3["judge(校准过)或人<br/>贵 · 有偏差要管"]
E4 --> S4["直接跑脚本:免费 · 确定"]
X["✗ 「回答要好」:两个人独立判不一致<br/>→ 不是评测用例,把判据写细"] -.-> Q
classDef q fill:#eef6ff,stroke:#5b8fd6,color:#222
classDef e fill:#fff7e0,stroke:#c98a00,color:#222
classDef ok fill:#eefaf0,stroke:#4d9a5c,color:#222
classDef exp fill:#fff3e0,stroke:#c98a00,color:#222
classDef bad fill:#fdecea,stroke:#c0392b,color:#222
class Q q
class E1,E2,E3,E4 e
class S1,S4 ok
class S3 exp
class X bad
3. 状态是输入的一部分
RAG 的检索结果、agent 的环境、多轮的历史、当前日期——都决定输出,都要固定进用例,否则同一用例每次跑的条件不同,分差不知道是改动造成的还是环境造成的。agent 用例最贵(要可重现的环境——容器快照),所以先几十条。
四、规模与分层
1. 多少条
统计上:要区分两个版本 5 个百分点的差异、噪声(每条 k 次的方差)中等,几十到一百条能给出方向;几百条能看趋势与分层;上千条是成熟产品的规模。先几十条上线,再长——等评测集”完善”再用是最常见的拖延。
2. 每条几次
模型非确定(《模型作为组件(01):失效模式——非确定性、幻觉、上下文与越界,把「模型会出错」拆成七条可检测的性质》),一条用例跑一次的结果是一个样本。\(k = 3\) 到 5 让你看到一致率与方差;门禁比较时用均值加噪声阈值。成本:100 条 × 5 次 × 每次几千 token,Sonnet 5 量级几美元——比一次线上事故便宜。
3. 分层报告
一个总分掩盖问题:改动让高频类涨 3 分、让多轮类掉 10 分,总分涨了。按类型(高频 / 边界 / 失败过的 / 红队)与按业务场景分层报告,门禁按层设阈值。
%% 图:一个总分掩盖问题——某次改动让高频类从 92 涨到 95、边界类 85 → 86,多轮类却从 88 掉到 78;按流量加权的总分从 90.6 涨到 91.9,看起来是进步;分层报告里多轮类掉 10 个点一眼可见,门禁按层设阈值才拦得住
%%{init: {"xyChart": {"width": 760, "height": 340, "plotReservedSpacePercent": 60}, "themeVariables": {"xyChart": {"plotColorPalette": "#c0392b, #5b8fd6, #4d9a5c"}}}}%%
xychart-beta
title "改动前后各层的通过率(%):总分涨了,多轮类掉了 10 个点"
x-axis ["高频", "边界", "多轮", "红队", "总分(加权)"]
y-axis "通过率(%)" 70 --> 100
bar [92, 85, 88, 96, 90.6]
bar [95, 86, 78, 96, 91.9]
红柱改动前,蓝柱改动后。
五、维护
1. 随 bad case 长
评测集是活的:每周从负反馈(第六篇的队列)里挑 5–10 条、标注、加入。一个失效模式被修好后,它的用例留下作为回归。这是评测集覆盖”你想不到的问法”的唯一途径。
2. hold-out
反复对着同一批用例调 prompt,会调出只对这批好的 prompt(《Prompt 与上下文工程(06):prompt 当代码管——版本、评测、A/B,以及上下文 vs 检索》)。留一部分(20–30%)不参与迭代、只在门禁与阶段评估时跑;hold-out 与开发集的分差变大,说明过拟合。定期轮换。
3. 版本绑定
评测集要与它评的东西的版本绑在一起:prompt 版本、模型快照、工具集版本、检索配置(分块策略变了 L3 的标注块 id 会失效)、rubric 版本、judge 版本。评测结果的记录是一个六元组加分数——否则三个月后不知道”那次 0.87 是在什么配置下测的”。评测集本身进版本库或注册表。
%% 图:一次评测结果绑定的六元组——评测集版本、prompt 版本、模型快照、工具集 / 检索配置版本、rubric 版本、judge 版本,任何一个变了分数就不可比;记录是六元组 + 分数(含每条 k 次的分布),评测集本身与 prompt 一样进版本库或注册表
flowchart TB
R["评测结果:0.87 ± 0.02(100 条 × 5 次)"]
R --> V1["评测集 v14<br/>(含 hold-out 划分)"]
R --> V2["prompt v12"]
R --> V3["模型快照<br/>claude-sonnet-5-20260630"]
R --> V4["工具集 v4 · 检索配置 v3<br/>(分块策略变 → 标注块 id 失效)"]
R --> V5["rubric v2"]
R --> V6["judge:Opus 5 快照 + judge prompt v3"]
V1 & V2 & V3 & V4 & V5 & V6 -.-> N["六个里任一变了 → 与旧分数不可比<br/>「三个月前那次 0.87 是什么配置」要能查出来"]
classDef r fill:#eef6ff,stroke:#5b8fd6,color:#222
classDef v fill:#fff7e0,stroke:#c98a00,color:#222
classDef n fill:#fdecea,stroke:#c0392b,color:#222
class R r
class V1,V2,V3,V4,V5,V6 v
class N n
4. 过期
产品变了、政策变了、用户问法变了——旧用例的期望可能已经错误。定期(季度)审一遍:期望还对吗?这类问题还有人问吗?删或改,记原因。
5. 依赖会变
模型升级或供应商静默变更(第四篇)让同一评测集的分数变化——这不是评测集的问题,是它的作用:定期重跑(每天一个小探针集、每周全量)才能发现。
六、合成数据
1. 能做什么
让模型生成用例:读每个文档块生成”这块能回答的问题”(RAG)、按模板变换已有用例(改数字、换实体、加噪声)、按类型枚举边界情形。便宜、覆盖全、冷启动快。
2. 陷阱
- 问法不像真人:合成问题贴合文档措辞,词法与向量检索都容易命中,高估 recall(《检索与知识接入(07):检索评测与运营》);真人的问法有省略、错字、口语、指代。
- 难度分布偏:模型倾向生成中等难度、表述完整的问题;真实流量里有大量极短或极乱的输入。
- 期望可能错:让模型同时生成问题与答案,答案是它的幻觉时用例就是错的——期望要人核。
- 自我一致:用模型 A 生成用例评模型 A,会偏向它自己的输出风格(第二篇的自我偏好)。
3. 位置
冷启动(上线前没流量)、覆盖检查(哪些文档块 / 功能从未被任何用例命中)、变换扩增(在人标的种子上做同义改写与实体替换,期望不变)。决策用真实流量。
%% 图:合成数据在评测集里的三个位置——冷启动时让模型读文档块生成问题(期望要人核);覆盖检查找出从未被任何用例命中的文档块与功能;变换扩增在人标的种子上做同义改写与实体替换、期望不变;上线后决策一律用真实流量,合成的只作补充
flowchart TB
subgraph SYN["合成数据能做的"]
direction TB
C1["冷启动:读每个块生成「这块能回答的问题」<br/>问法贴文档、高估 recall → 期望人核"] ~~~ C2["覆盖检查:哪些块 / 功能从未被用例命中<br/>→ 针对性补"] ~~~ C3["变换扩增:人标种子 → 改数字 · 换实体 · 加错字<br/>期望不变"]
end
SYN --> EVAL["评测集"]
REAL["真实流量(上线后)<br/>省略 · 错字 · 口语 · 指代 · 极短极乱"] ==>|"决策用这个"| EVAL
classDef syn fill:#fff7e0,stroke:#c98a00,color:#222
classDef real fill:#eefaf0,stroke:#4d9a5c,color:#222
classDef n fill:#eef6ff,stroke:#5b8fd6,color:#222
class C1,C2,C3 syn
class REAL real
class EVAL n
七、标注的组织
1. 谁标
领域专家标期望与 rubric(他们知道什么是对的),工程师标格式与结构类的,用户反馈直接作为弱标注。judge 可以先标再人核(第二篇)——省人力,但要抽检一致率。
2. 标注指南
标注的标准本身要写下来并版本化:什么算”正确”、边界情形怎么判、多个可接受答案怎么列。两个标注者独立标一批算一致率(kappa),低于 0.6 说明指南不清,先修指南再标。
3. 分歧
分歧不是噪声,是信息:它标出了任务里模糊的地方——这些地方 judge 也会不稳,模型也会不稳。分歧用例单独标记,或由第三人裁决并记录理由。
4. 成本
一条用例从采样到标注完成,简单的几分钟、agent 的几十分钟(要建环境)。100 条的第一版评测集是一两个人几天的工作——与它防住的事故比很便宜,但要排进计划而不是”有空再做”。
八、实践建议
- 今天就从日志采 50 条:按类型聚类抽样,加 10 条失败过的、10 条边界;每条写可检验期望;两周内上线第一版。
- 每条跑 k = 3,报均值与一致率;分层报告。
- 留 20% hold-out,只在门禁跑。
- 记录六元组:评测集版本 × prompt × 模型快照 × 工具集 × 检索配置 × rubric / judge 版本 → 分数。
- 接 bad case 队列:每周补 5–10 条;修好的失效模式留用例。
- 合成只做冷启动与覆盖检查,期望人核。
- 写标注指南,两人标一批算 kappa,低于 0.6 先修指南。
九、本文小结
- 评测集是 AI 应用的单元测试,三点不同:期望是分布(跑 k 次)、用例从生产流量采、依赖会变(定期重跑)。
- 来源:真实流量按类型覆盖采样(高频、边界、失败过的);上线前用试用 / 工单 / 专家;红队与安全用例专门写。
- 每条四字段:input(含状态——检索结果、环境快照、历史)、expected(精确值 / 集合 / rubric / 验收脚本,必须可检验)、meta、score_how。
- 规模:几十条起、几百条看趋势;k = 3–5;分层报告不看总分。
- 维护:随 bad case 每周长、20–30% hold-out、与六个版本绑定、季度审过期、定期重跑发现依赖变化。
- 合成数据:便宜覆盖全,但问法不像真人、难度偏、期望可能错、自我偏好——只做冷启动、覆盖检查、变换扩增;决策用真实流量。
- 标注:专家标期望、指南版本化、双标算 kappa、分歧是信息、成本排进计划。
十、自测
-
一个团队按流量比例随机采了 200 条建评测集,90% 是”查订单状态”。改动后总分从 0.91 涨到 0.93 上线,多轮退款场景投诉激增。问题在哪?
-
“回答应该专业、有帮助”作为期望有什么问题?改成什么?
答案
不可检验——两个人独立判会不一致,judge 也不稳。改成可检验的形态:精确值 / 可接受集合(若答案确定)、或一份 rubric(含具体判据:是否引用了正确条款、是否给出金额、是否在三句内、是否有拒答出口的正确使用),由校准过的 judge 或人按 rubric 打分,并记 rubric 版本。详见第三章。
-
为什么 agent 用例要固定环境快照?不固定会怎样?
答案
agent 的输出取决于环境(仓库状态、数据库内容、可用工具、日期);不固定则同一用例每次跑的条件不同,分数差异无法归因到改动。固定为容器镜像 / 仓库提交 / 数据库快照,让评测可重现——这也是 agent 用例贵、先几十条的原因。详见第三章。
-
开发集分数三个月从 0.80 涨到 0.92,hold-out 从 0.79 涨到 0.81。说明什么?怎么办?
答案
过拟合:prompt 被反复对着开发集调,只对那批用例好。真实提升接近 hold-out 的 +2 分而不是 +12 分。办法:以 hold-out 为门禁与汇报的依据、轮换 hold-out、把开发集里被过度针对的用例特征泛化(加同类但不同表述的用例)、从真实流量补新用例。详见第五章。
-
用模型对每个文档块生成问题建了 2,000 条 RAG 评测集,recall@5 = 0.95;上线后用户反馈”找不到”很多。解释并给出正确用法。
答案
合成问题贴合块的原文措辞,检索容易命中,高估 recall;真人的问法有省略、口语、指代与词汇不匹配。正确用法:合成集做冷启动与覆盖检查(哪些块从未被命中),决策与门禁用真实查询采样的评测集(几十条起,人标相关块),并把”找不到”的反馈作为 bad case 补进去。详见第六章。
-
来源:真实流量按类型覆盖采样(不按比例)——高频决定平均质量、边界暴露脆弱点(长、多轮、数字 / 专名、多语言、歧义)、失败过的最有信息(点踩、重试、修改、放弃、转人工,带 trace);上线前用内部试用、客服工单、竞品案例、专家;红队与安全用例专门写。每条四字段:input 含必要状态(RAG 固定检索结果或标注块、agent 环境快照与工具、多轮历史)、expected 必须可检验(精确值 / 可接受集合 / rubric / 验收脚本)、meta(类型、难度、来源、日期、标注者、bad case id)、score_how(规则 / judge + rubric 版本 / 人)。规模:几十到一百条能分方向、几百条看趋势;每条 k = 3–5 次看分布;分层报告不看总分;先几十条上线再长。详见第二章到第四章。 ↩
-
过拟合:留 20–30% hold-out 不参与迭代,只在门禁与阶段评估跑,开发集与 hold-out 分差变大即过拟合,定期轮换。维护:每周从 bad case 队列补 5–10 条并标注,修好的失效模式留用例做回归;季度审过期(期望还对吗、还有人问吗);定期重跑(每天小探针集、每周全量)发现模型升级与静默变更。版本绑定:评测结果记为六元组——评测集版本 × prompt 版本 × 模型快照 × 工具集 × 检索配置(分块变了标注块失效)× rubric / judge 版本——加分数;评测集本身进版本库或注册表。详见第五章。 ↩
-
合成数据能做冷启动(上线前无流量)、覆盖检查(哪些块 / 功能从未被命中)、变换扩增(在人标种子上同义改写与实体替换、期望不变);陷阱是问法不像真人(贴合文档措辞高估 recall)、难度分布偏中等、模型生成的期望可能是幻觉(要人核)、用模型 A 生成评模型 A 的自我偏好——决策用真实流量。标注:领域专家标期望与 rubric、工程师标格式类、用户反馈作弱标注、judge 先标人核并抽检;标注指南写下来并版本化,两人独立标一批算 kappa,低于 0.6 先修指南;分歧是任务模糊处的信息,单独标记或第三人裁决记理由;成本简单的几分钟、agent 的几十分钟,100 条第一版是一两人几天,排进计划。详见第六章、第七章。 ↩
系列 《评测、可观测与可追溯:怎么知道改了之后更好了》 第 1 / 1 篇 进阶
系列总览 — 为什么这样组织、读它需要什么、读完能做什么
学习地图 — AI 应用工程师学习地图 第 5 / 7 个系列
- 评测集——AI 应用的单元测试
上一个系列:工具、Agent 与 harness
去 Series 挑下一个系列
本文由 arganzheng 创作,采用 CC BY 4.0 许可协议。在保留原文作者、署名以及完整原文链接(https://arganzheng.life/eval-sets-the-unit-tests-of-ai-applications.html)的前提下,欢迎各种形式的转载、翻译或商业引用。
COMMENTS
评论存放在 GitHub Discussions, 用 GitHub 账号登录即可发表,支持 Markdown。 想针对正文某句话说?选中那段文字,点浮出的「评论」即可划线评论;觉得哪里写错了,发表时勾上「同时提交 Issue」。 有人回复你时 GitHub 会按你的通知设置发邮件,不用守在这里。