25 · 评测驱动:把「够好」写进架构
一句话点题:17 章 抽走了「同样输入 → 同样输出」这块地基,
assert result == expected失效了。怎么防止「换个模型、改句提示,质量就悄悄变差」?把「够好」量化成一套评测(eval),当 CI 门禁——让质量从「靠投诉发现」变成「可量化、可守门」。
🤝 AI 协同设计篇第 3 章 · 本章只练一件事
24 章 靠人逐条审,但「答案质量稳不稳」人查不过来。本章把关从「人工逐条」升级成「机器持续」。这是 20 章 ADR-005 和 22 章 反复点到的 eval 门禁的完整展开,也是非确定性(17 章)唯一靠谱的解法。
一、为什么传统测试在 AI 系统上失效
传统系统的测试,建立在确定性上:
传统: assert summarize(x) == "预期的那一句" ← 二元:对 / 错,能精确断言
LLM: summarize(x) 这次输出 A,下次可能输出 A' ← 温度、模型版本、上下文一变就不同
两次都「对」,但字面不同 → assert == 直接挂17 章 说过:LLM 把确定性地基抽走了。于是传统测试遇到两个死局:
- 断言不了:输出不固定,
== expected必然误报。 - 退化无声:你把模型从旧版升到新版、或改了句系统提示,某类问题的答案悄悄变差了——没有任何测试会红,你只能等用户投诉(20 章 那个「两周后才从投诉发现」的坑)。
核心转变(来自 17 章):从「断言单条正确」转向「衡量一个质量分布」。 不再问「这一条对不对」,而问「这一批代表性输入,整体质量分够不够好、有没有比上一版退」。这就是 eval。
二、eval 的三件套
┌─────────────┐ ┌──────────────┐ ┌─────────────────────┐
│ ① eval 集 │──▶│ ② 评分 │──▶│ ③ 门禁(CI) │
│ 代表性输入 │ │ 规则/模型/人工 │ │ 分数低于基线 → 拦截 │
│ + 期望要点 │ │ 给每条打分 │ │ 不许上线 │
└─────────────┘ └──────────────┘ └─────────────────────┘① eval 集:测试集的 AI 版
一批代表性输入 + 每条的期望要点(注意:是「要点 / 判据」,不是「逐字的标准答案」)。
例(AI 客服):输入「我上周买的鞋开胶了能退吗?」→ 期望要点:
①引用了质保政策 ②给出明确的能/否 ③没有编造不存在的政策 ④口吻得体。评的是这几个要点中了几条,不是逐字比对。
② 评分:谁来判「够不够好」
| 评分方式 | 适合 | 代价 / 风险 |
|---|---|---|
| 规则 / 程序判定 | 有客观判据(含不含某关键词、格式对不对、有没有引用) | 便宜、稳定,但只能判「硬」标准 |
| LLM-as-judge(模型当裁判) | 主观质量(答得好不好、相不相关) | 灵活,但裁判本身也非确定、也会错、也烧 token |
| 人工抽样 | 校准前两者、兜底高价值场景 | 最准,但慢、不可规模化 |
实务:规则能判的先用规则(便宜可靠),主观的用 LLM-as-judge,再定期人工抽样校准裁判——别盲信模型裁判,它也是个会出错的模型。
③ 门禁:让 eval 真正「守门」
光跑 eval 不够,要接进 CI 当门禁(这正是 20 章 ADR-005):换模型、改提示、改检索策略前,CI 自动跑 eval;整体分数低于基线,一律拦截、不许上线。
改提示 / 换模型 ──▶ CI 跑 eval ──┬─ 分数 ≥ 基线 → 放行
└─ 分数 < 基线 → 🔴 拦截(防悄悄退化)三、怎么建第一个 eval(别想一步到位)
新手一听「评测集」就想攒几千条——然后永远没开始。正确姿势是从小、从真实失败长出来:
- 种子来自线上真实 bad case:每一条用户投诉、每一个「答错了」的截图,都是最值钱的 eval 样本——它们是真实失败,不是你想象的失败。先攒十几二十条,就能跑起来。
- 离线 + 在线两条腿:
- 离线 eval:固定数据集,进 CI,把关「上线前」(防退化)。
- 在线 eval:对真实流量抽样打分 / 影子运行打分(正是 21 章 的影子流量!),把关「上线后」(真实分布永远比你的数据集刁钻)。
- 持续加 case:每次发现新的失败模式,就把它固化成一条 eval——和写回归测试一个道理。eval 集是「活」的,随系统一起长大。
这套「从真实失败攒起、小步快跑、持续扩充」的节奏,和 21 章 GitHub Scientist 的「用真实流量当裁判」、07 章 的「架构是迭代出来的」完全同源——别等完美的评测集,先用粗糙的把循环跑起来。
四、eval 不替代传统测试,是新增一层
别误会成「有了 eval 就不写单测了」。一个 AI 系统里,确定性的部分照样用传统测试,eval 只管「非确定性输出的质量」:
╱╲ eval 层(新增)
╱ ╲ ── 非确定性输出的「质量分布」:答得好不好、退没退化
╱────╲
╱ E2E ╲ 传统金字塔(照旧)
╱──────────╲ ── 确定性逻辑:退款幂等、状态机、鉴权、API 契约
╱ 集成 / 单测 ╲ 这些有唯一正确答案 → 照样 assert ==
╱────────────────╲回到 19 章 那个 AI 客服:退款服务是确定性的 → 用单测断言幂等、断言金额校验(assert == 照样有效);模型生成的答案是非确定的 → 用 eval 评质量分布。两套并存,各管一段。「把不确定性挡在副作用之外」(19 章)的好处在这又兑现了一次:确定的部分还能用确定的方法测。
五、eval 的成本与陷阱(它不是免费的)
把 eval 当架构组件,就要像对待任何组件一样看它的代价:
- 跑 eval 烧钱、耗时:每条样本都要真实调用模型,LLM-as-judge 更是「评一次 = 又一次模型调用」。eval 集越大、跑得越勤,成本越高——要在覆盖度和成本之间权衡(又一次 06 章 的取舍)。
- 裁判会错:LLM-as-judge 本身非确定、有偏见(比如偏好长答案)。要用人工抽样校准它,别把它的分当圣旨。
- 过拟合与老化:盯着固定 eval 集调久了,会「为了考高分而优化」,对集外样本未必好;且业务变了,旧 eval 集会过时。要持续更新(同 23 章 AGENTS.md「过时比没有更糟」)。
架构智慧:eval 是 AI 系统的「质量适应度函数」——它之于答案质量,正如 14 章 的适应度函数之于架构边界:都是「把你在乎的东西,变成一道会失败、能卡 CI 的自动检查」。区别只是:架构边界能精确断言,答案质量只能评分布。把「够好」写进 eval、接进门禁,你才敢放心地升级模型、迭代提示——否则每一次「升级」,都是闭着眼睛赌它没变差。
🎯 随堂检验
- A跑一遍单元测试,断言输出字符串等于预期答案,通过就升级
- B维护一个代表性「输入→期望要点」的 eval 集,升级前用规则 + LLM-as-judge 打分,整体分数低于基线就在 CI 拦截
- C先上线,等用户投诉多了再回滚
- A有了 eval 就不用写单元测试了,AI 系统全靠 eval
- Beval 不替代传统测试,而是新增一层:确定性逻辑(如退款幂等、鉴权)照样用 assert == 单测,eval 只管非确定性输出的质量分布
- C两者完全一样,只是名字不同
本章小结
- 传统测试在 AI 上失效:输出非确定,
assert ==误报;更糟的是质量退化无声,只能等投诉。 - 从「断言单条」转向「衡量分布」:这是 17 章 的核心转变,落地就是 eval。
- eval 三件套:① eval 集(代表性输入 + 期望要点)② 评分(规则 / LLM-as-judge / 人工抽样)③ 门禁(进 CI,低于基线就拦,即 20 章 ADR-005)。
- 从真实失败小步攒起:种子来自线上 bad case;离线(CI)+ 在线(影子,21)两条腿;持续加 case。
- eval 不替代传统测试:确定性逻辑照样单测,eval 只管非确定输出的质量分布。
- eval 不免费:烧钱、裁判会错、会过拟合老化——要权衡覆盖与成本、持续校准维护。它是 AI 系统的「质量适应度函数」(14)。
承上启下:到这儿,AI 协同的三件武器齐了——规格(23)给约束、清单(24)审产出、eval(25)守质量。但什么时候用哪件、什么时候干脆放手 vibe、什么时候必须 spec-first?AI 协同设计篇最后一章 26 · 协作决策树:何时 vibe、何时 spec-first 把这三件武器收成一套可照着走的 workflow。
相关链接
- 理论本体:17 · 大模型时代的架构判断(非确定性 → 评测驱动)
- 同源案例:20 · 演进剧本(ADR-005 eval 门禁) · 22 · AI 原生系统设计 · 21 · 影子流量(在线 eval)
- 配套:14 · 适应度函数(架构的自动检查)、06 · 质量属性与取舍(覆盖 vs 成本)
💬 评论