一个熟悉的故事
假设你做了一个问答机器人。你在本地试了 10 个问题,回答都像模像样。你兴奋地发布,一周后,用户反馈堆积如山:
- "问它今天星期几,它说'我无法访问实时信息',但三天前明明能回答。"
- "同一个问题,早上问和下午问答案不一样。"
- "产品上线后成本翻倍,没人知道为什么。"
- "新换的模型版本在某个细分场景突然全线崩溃。"
这不是技术不够好,这是你在盲飞。你没有仪表盘,也没有刻度——这正是 Evals 要解决的问题。
什么是 Evals
Evaluation(评估)
对 AI 系统输出进行系统化、可重复的质量度量。它不是"感觉还行",而是"在 500 条测试集上,正确率从 82% 提升到 87%,置信区间 ±1.2%"。
Eval Set(评估集)
精心挑选的输入-期望输出对。是你的回归测试基线,每次改 prompt、换模型、调参数都要跑一遍。
Scorer(打分器)
把一次输出变成一个可比较的数字。可以是精确匹配(是/否)、相似度(0-1)、LLM-as-Judge(模型打分),或业务规则(JSON 合法性)。
Experiment(实验)
一次完整的"跑评估集 → 得到分数"的过程。你对比的是实验 vs 实验,不是"感觉 A 比 B 好"。
Vibe Check 为什么不够
Vibe check(凭感觉测)是每个 LLM 项目的起点,也是很多团队的终点。它之所以致命:
❌ Vibe Check 的问题
- 样本量小:试 5 条 ≠ 代表 5 万条
- 记忆欺骗:你会忘记昨天试过什么
- 没法对比:改完 prompt 之后怎么知道"真的"变好了
- 无法回归:旧问题偷偷复发
- 无法共享:你的"感觉"传不给团队
✅ Evals 解决什么
- 覆盖度:一次跑 500 条,覆盖长尾
- 可重复:今天跑和明天跑是同一把尺子
- 可对比:A 实验 82%,B 实验 87%,硬数据
- 可回归:CI 上自动跑,退步立刻报警
- 可沟通:给老板看曲线,不是看"感觉"
Evals 和传统软件测试的本质差异
传统测试是确定性的:assert add(2, 3) == 5。LLM 是概率性的:同一个输入可能生成 5 种合理的回答,也可能生成 1 种看似合理实则错误的回答。这带来 3 个根本区别:
| 维度 | 传统软件测试 | AI Evals |
|---|---|---|
| 断言方式 | == 精确匹配 | 语义相似 / LLM 评判 / 多维度评分 |
| 结果类型 | 通过 / 失败(二元) | 分数 / 分布 / 置信区间(连续) |
| 失败含义 | Bug 确定存在 | 可能是坏案例,也可能是评估集偏差 |
| 回归目标 | 100% 通过 | 关键指标不下降(pass@k、p95) |
| 更新频率 | 每次 PR | 每次 prompt / 模型 / 参数变更 |
| 数据成本 | 写几行 assert | 构建和维护 golden set(贵) |
核心心智
不要把 Evals 当成测试的"加强版",而要当成一种全新的工程范式。你测的不是"这段代码对不对",而是"这个模型在这个场景的分布表现如何"。
评估金字塔:从单元到生产
完整的 Evals 体系是分层的,每一层解决不同的问题。最底层快、便宜、覆盖广;最顶层慢、贵、最接近真实。
┌──────────────────┐
│ 生产监控 │ 真实用户流量 + 隐式/显式反馈
│ (online) │ 延迟/成本/用户满意度
└──────────────────┘
┌────────────────────────┐
│ A/B 实验 │ 线上分流对比两个版本
│ (shadow / canary) │ 统计显著性
└────────────────────────┘
┌──────────────────────────────┐
│ 场景端到端评估 │ 完整工作流
│ (E2E evals) │ 100-1000 条 golden set
└──────────────────────────────┘
┌────────────────────────────────────┐
│ 组件级评估 │ 单个 prompt/工具/retriever
│ (component evals) │ 几十到几百条,CI 跑
└────────────────────────────────────┘
┌──────────────────────────────────────────┐
│ 单元级评估 │ 格式校验、JSON schema
│ (unit evals, 秒级) │ 边界输入、注入攻击
└──────────────────────────────────────────┘
单元级 Evals(毫秒~秒)
最便宜的检查。JSON 是否合法、函数调用参数是否完整、是否泄露了系统 prompt、是否触发了禁词。每次 prompt 改动都跑,CI 强制通过。
组件级 Evals(秒~分钟)
测单个子系统。单独测 retriever 的召回率、单独测 query 改写、单独测某个 tool 的 schema 合规性。发现问题能精准定位。
端到端 Evals(分钟~小时)
测完整用户流程。"用户问 X,系统应返回 Y"。贵、慢,但最接近真实。发布前必跑。
在线评估(持续)
生产流量的采样评估 + 用户反馈信号聚合。离线分高 ≠ 线上真的好,只有在线数据能告诉你真相。
什么时候开始写 Evals
一个流传甚广的错误观念:"等我做完 MVP 再做 Evals"。事实是:
经验法则
写第一个 prompt 之前就写第一条 eval。哪怕只有 5 条,也比 0 条强一个数量级。
原因:没有 evals,你无法知道自己的 prompt 改动到底改好了还是改坏了。凭感觉迭代 = 随机游走。
原因:没有 evals,你无法知道自己的 prompt 改动到底改好了还是改坏了。凭感觉迭代 = 随机游走。
更务实的推进节奏:
- Day 1: 写 5-10 条手工 eval,覆盖你最在乎的 happy path 和 1-2 个边界。用 Python assert 或 pytest 就够。
- 第 1 周: 扩到 30-50 条,引入一个 LLM-as-Judge 作为辅助指标。开始用 Promptfoo / Braintrust。
- 第 1 月: 从真实日志采样构建 golden set(100+ 条),分层(easy/medium/hard)。CI 集成,PR 自动跑回归。
- 上线后: 接入生产 trace,做采样评估;用 thumbs up/regenerate 作为隐式信号;跑 A/B 实验。
- 长期: 评估集和模型一起进化。新 bug 先进 eval set,再修代码(TDD 思维)。
一个最小可行的 Evals 例子
先感受一下"最朴素的 eval"长什么样。下面是一个情感分类任务的 10 行代码评估:
# eval_sentiment.py — 最小可行 eval from openai import OpenAI client = OpenAI() # 评估集:输入 + 期望标签 cases = [ ("这部电影太棒了,值回票价", "positive"), ("浪费了两个小时,剧本稀烂", "negative"), ("还行吧,没什么惊喜也没什么雷点", "neutral"), # ... 假设共 50 条 ] def classify(text): rsp = client.chat.completions.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": "只返回一个词:positive/negative/neutral"}, {"role": "user", "content": text}, ], ) return rsp.choices[0].message.content.strip().lower() correct = sum(classify(t) == label for t, label in cases) print(f"accuracy = {correct}/{len(cases)} = {correct/len(cases):.1%}")
这 20 行代码已经战胜了 vibe check。你可以:
- 改 prompt,重跑,看准确率变化
- 换模型(
gpt-4ovsgpt-4o-mini),直接对比 - 发现 bad case,加进评估集,永不再犯
进阶预告
这个朴素版本有 3 个大问题:① 只能测精确匹配,测不了"生成式"任务;② 没有统计显著性;③ 结果没沉淀,跑完就没。后续章节会逐个解决。
本章小结
- Evals = 系统化、可重复的质量度量,不是 "感觉还行"
- LLM 的概率性决定了它和传统测试有本质差异,需要新的工程范式
- 评估体系是分层的:单元 → 组件 → 端到端 → 在线
- 从写第一个 prompt 起就写 eval,哪怕只有 5 条
- "没有 evals 就不要发布"——这是 OpenAI、Anthropic、Google 等头部团队的共识
下一章我们进入指标设计:准确率不够用时,该怎么测"生成式"任务的好坏?