Skip to content

第 21 章 评估体系 ​

本章要解决的问题

第 19 章解决的是"程序不坏":失败能恢复、状态能记账。本章回答另一个问题:Agent 到底把任务做对了没有。"模型说完成"不算完成——它可能没跑测试、只做了一半、或者把测试改绿了。

方法论只占本章一小部分;主要篇幅给落地:判定怎样分层的、用例集从哪里来、看哪些指标、怎样接进 CI 自动跑。评估的工程设施在四个参考实现里只有 pi 公开(第 21.3 节解剖),其余三家依赖内部体系——这本身就是这个主题难落地的一个证据。

21.1 问题:状态正确不等于任务做对 ​

先分清评估对象。评估的从来不是模型,而是完整的 Agent:模型、系统提示词、工具、权限、验证方式都是被评的一部分。同一个模型,换一套工具和提示词,就是另一套系统——所以"换了个更强的模型,我们的 Agent 就会变好"不总成立,瓶颈可能在你自己的 Harness 里。

再分清三种测试,它们互相不能替代:

层运行条件回答的问题什么时候跑
确定性可靠性回归输出固定的假模型服务 + 故障注入状态机和副作用处理会不会坏每次提交必跑(第 19 章的注入套件)
离线任务质量 eval真实模型 + 可重置环境 + 固定版本在目标任务分布上做得多好改提示词/工具/模型前后
线上小流量试运行少量真实流量 + 安全限制 + 快速回滚离线收益能否迁移到真实用户发布阶段(第 22 章)

单元测试检查代码必须始终满足的规则;eval 衡量概率系统的实际表现。eval 的随机波动很大,单次分数没有意义——这是后面所有统计规则(21.6)的根源。

21.2 社区思路:公开基准与评估框架 ​

不用从零发明。公开世界已经有三类成熟做法,各自的机制和边界如下。这里借的是判定与运行机制,不是把任何榜单当成永久有效的真理。

思路一:SWE-bench Verified——判定交给测试,不交给另一个模型。 它从真实 GitHub issue 中筛选并经人工核验出 500 道:每道题给定仓库的固定起始镜像,Agent 产出补丁后,评估 harness 按两条测试清单判定:FAIL_TO_PASS(原故障对应的测试必须由失败转通过)和 PASS_TO_PASS(既有测试不得被改坏)。这让“模型自报完成”失效,也能发现只修新行为却破坏旧能力的补丁。局限同样明显:题目集中在 Python 仓库的单 issue 修复,离真实工程任务(跨模块重构、需求实现)有距离。更重要的是,截至审阅时 OpenAI 已停止把它用于前沿模型发布评估:对困难样本的复核发现测试缺陷,并观察到训练数据污染。它仍适合讲解可复现 harness,却不应单独承担前沿能力排名。

思路二:Terminal-Bench / Harbor——任务与 harness 分离,一套基础设施跑多个基准。 它把评测做成容器里的真实终端任务(配置服务器、修数据管道等),任何 Agent 只要接一次 harness 就能跑。Harbor 进一步用 registry 与 adapter 把既有基准搬进统一任务格式;adapter 不能只做格式转换,还要用同一 Agent、模型、提示和配置在原 harness 与新 harness 上重复运行,以分数区间重叠证明 parity。它解决的问题是真实的:此前接一个基准要克隆一个仓库、学一套框架,现在可以复用同一套运行、隔离和报告设施。代价是 adapter 本身也成了要版本化和验证的代码。

思路三:把评估塞进测试框架。 以 vitest-evals 为代表:评估不是独立脚本,而是测试套件——用例仍是 Vitest 测试,框架提供 harness 适配器(把真实 Agent 运行时接进来)、确定性或模型 judge、归一化轨迹、报告和 CI 门禁。好处是评估和代码共用同一套工具链与流程,工程师不需要学第二套系统。

三类思路共同回答了"判定从哪来":能由原始程序判定的,就由原始程序判定。但它们都回答不了另一个问题——公开基准上的名次,不等于在你的代码库上的表现。公开题的语言、仓库类型、任务长度和工具权限都可能与你的生产流量不同,而公开题还会随时间出现污染与饱和。这就是每家都要自建评估体系的原因。

21.3 各家实现:pi 解剖,其余三家的现状 ​

先如实交代现状,这本身就是重要信息:任务质量评估的设施,四家里只有 pi 随源码公开。

  • codex、grok-build:源码中没有任务质量评估设施。codex 仓库里只有 CLI 的端到端性能基准;两家产品级评估都在公司内部完成,不随源码发布。
  • dsh:根目录一页 3 行的 BENCHMARK.md,指引"用 Python SDK 跑 jsonrpc-agent 最小变体,为独立基准任务分配独立工作区和会话 ID"——能跑基准任务,但没有评估框架。
  • pi:packages/evals 是一套完整、可运行的评估设施,也是本节的解剖对象。它基于 vitest-evals,附录 B 有源码入口。

pi 的五个设计决定 ​

① 跑真实的 AgentSession,不是简化复制品。 每个用例启动的是产品同款的会话对象,完整走真实循环、工具和扩展加载;区别只在环境:临时目录里现建一个隔离的 workspace 和 agent 目录,不碰任何真实项目。隔离性不是靠自觉,而是断言出来的——会话启动后检查扩展列表必须为空,否则直接报错。这条对自建的启示最大:评估跑的路径和产品差一点,结论就迁移不回来。

② 每次运行留三样东西:归一化轨迹、用量、原生会话产物。 轨迹被归一成统一形状(用户消息、助手消息、工具调用、工具结果),judge 和报告都吃这一份;用量不止 token 总数,还包括缓存读/写命中和估算成本(模型有定价时);同时把本次运行的原始会话 JSONL 作为产物留存、以 runId 索引。分数可疑时,能打开原始会话逐步核对——评估系统自己的可观测性。

③ 判定优先用普通代码,而不是再调一次模型。 官方示例里那个"让 Agent 给自己写一个扩展"的用例,判定逻辑全是程序检查:生成的扩展源码是否引入了规范的包名(不许引旧包名)、扩展是否真的被加载、注册的工具是否出现、对工具的调用是否返回了预期的逐字结果、最终回复是否精确。全部通过得 1 分,否则 0 分,失败原因拼成一条理由。注意这里连"回复内容"都是精确匹配——因为题面就要求逐字输出。需要主观判断的可读性、文风类用例,vitest-evals 才提供 LLM judge(可以指定一个独立的评审模型)。

④ baseline/candidate 是一等公民。 用例可以挂两张对照表:同一道题,分别跑"去掉文档段的系统提示词"和"完整系统提示词"两个 harness,配对报告通过率、token、延迟、成本的差异。第 6 章"系统提示词要像代码一样测试"落地就是这种形态——改一版提示词,就多一个 candidate,和 baseline 跑同一批题。

⑤ 评估可中断、可清理。 取消信号直接接到会话的中止接口;运行结束无论成败,临时环境都删除,清理失败会聚合上报而不是悄悄吞掉。

这五个决定没有一处依赖 pi 的特殊性:隔离环境、产物留存、代码优先的判定、对照表、可取消,任何 harness 都能照做。用例生命周期、调参集与留出集分离这类纪律(21.5),pi 的设施没有替你做——自建时要自己补上。

21.4 判定怎样分层 ​

判定的核心原则一句话:越确定、越便宜的检查越先做;模糊、昂贵的检查放最后兜底。

判定金字塔:从确定性检查到人工抽检

四家与社区在"确定性优先"上完全一致,无需对比;真正需要设计的是第 4 层——AI 评审——因为它会出错且错得隐蔽。把它的卫生规则合并成一张清单:

规则防的是什么
评分规则拆成必要项、加分项、一票否决项;数据外泄、虚构证据、越权一票否决严重问题被其他项目的高分"平均"掉
隐去方案名,配对比较时交换左右顺序位置偏差和品牌偏好
只评审可观察证据(渲染截图、diff、会话轨迹),不评隐藏思维评审无据可依,凭语气打分
检查分数是否只随回答长度上升"写得多 = 分数高"
用人工标注样本定期校准,检查误判率评审模型漂移
评审基础设施故障标为 evaluator_error,既不算成功也不算失败把自家系统的 bug 记成 Agent 的罪状

21.5 用例集:从哪里来,怎样保持有效 ​

用例五要素:任务指令、固定起始环境(初始 commit / 容器镜像)、允许与禁止的动作、检查方法、预算(轮数 / token / 墙钟)。修复类任务至少同时检查五项:

text
FAIL_TO_PASS   原故障真的消失
PASS_TO_PASS   旧能力没有被破坏
workspace      用户原有未提交修改没丢、没被卷进提交
policy         没改测试期望、没新增 skip、没碰禁改文件
artifact       最终产物正确,而不是 Agent 自报"完成"

静态题测不出"信息不足时会不会问"。补一类可交互用例:用一个小状态机扮演用户,隐藏事实只有 Agent 问到才给;没问就不能编。除了产物,还要检查——该问的问了没有、高风险动作前确认了没有、带着错误假设跑了多远。

用例集从三个来源滚动生长:

  1. 生产失败回流:每个线上事故,脱敏、人工确认、能复现之后,变成一条回归用例——这是最有价值的来源,因为它就是你自己的任务分布;
  2. 公开基准改造:从 SWE-bench Verified、Terminal-Bench 搬任务,改造成本地技术栈和真实场景;
  3. 手写边界题:典型任务之外,专门写边界任务、陷阱任务和"应该拒绝/无法完成"的任务——最后一类最常被漏掉,没有它就测不出 Agent 会不会硬编。

用例也是需要维护的资产。每条存 caseId、版本、来源、能力类别、难度、起始状态哈希、检查程序版本和负责人,状态走 draft → validated → active → quarantined/retired;含义不清或依赖不稳定的题先隔离,不能混在总分里。另外两条纪律:开发调参用的题集,和从未参与调优的留出集必须分开存放——改变文件名、路径和数据重复出题,是为了防止系统只是记住了题面。

21.6 指标与统计:成功率之外 ​

指标分三组,不要揉成一个综合分:

组最小指标解释
结果Pass@1(单次尝试成功率)、Pass^k(连续 k 次全通过)前者看能力,后者看稳定性;发布、付款类场景看后者
安全/路径严重违规率、测试篡改率、重复副作用率严重违规一票否决,不进平均
经济性成功任务成本、端到端 p50/p95、轮数与工具调用数便宜但反复重试的模型可能更贵

Pass@k(k 次至少一次成功)和 Pass^k(连续 k 次都成功)只差一个符号,含义差很远:前者适合可回滚的探索任务,后者才对应业务可靠性。k 从真实重试预算里来,不能为了汇报好看挑数。

比较两个版本时的统计纪律(这是最常被忽略的部分):

  • 同一批题、逐题配对,有随机性的题重复运行,报告范围而不是最好的一次;
  • 差异没有超过重复运行的正常波动,就没有证据支持切换;
  • 同时试了很多改动后挑出的"最好一项",要在没参与挑选的留出集上独立复跑;
  • 每次运行记录版本五元组,否则分数变化无从归因:
text
模型/provider/版本 + 推理配置
Agent 运行时 commit + 系统提示词哈希 + 工具 schema 哈希
指令文件/记忆快照哈希 + 起始环境镜像哈希
用例版本 + 轨迹引用 + 预算与停止原因

21.7 自动化:让评估自己跑 ​

评估的落地标准只有一条:改提示词、改工具、换模型时,你不需要记得"该评估了"。pi 用 npm run eval 一条命令跑全套,指定 provider 和 model 即可;每次运行输出一个产物目录,runs.jsonl 索引所有完成记录及其会话附件。vitest-evals 在此之上补齐工程链路:报告器可以按最低通过率门禁 CI(低于阈值挡住合并),GitHub Action 把分数和失败用例写进 PR 检查,工具调用可以录制重放(改 judge 逻辑时不必重烧 API 费用)。

自建时按这个顺序补齐:

text
第 1 步  一条命令能跑全部用例,输出分数 + 每次运行的会话产物
第 2 步  结果带版本五元组,入库存档
第 3 步  接 CI:改提示词/工具/模型的 PR 自动跑回归集,低于门禁挡合并
第 4 步  baseline/candidate 对照表:每次改动和上一版配对比
第 5 步  发布:回归集通过后的发布单元、灰度与回滚,见第 22 章

任务级追踪(traceId)的记录规范见第 19 章。

本章小结 ​

  • 评估对象是完整 Agent(模型 + 提示词 + 工具 + 权限 + 验证),不是模型本身;单元测试与 eval 互相不能替代
  • 社区三套思路:SWE-bench Verified 用双测试清单判定补丁,Terminal-Bench / Harbor 把任务与 harness 分离并用 parity 实验验证 adapter,vitest-evals 把评估塞进测试框架;公开基准会偏离生产分布,也会污染和饱和,所以不能替代自建评估
  • 四家里只有 pi 公开评估设施:真实 AgentSession + 隔离环境(隔离性是断言)、归一化轨迹与用量、代码优先的判定、baseline/candidate 对照、可中断可清理——五条都不依赖 pi 的特殊性
  • 判定按金字塔分层:确定性检查 → 过程检查 → 真实产物 → AI 评审 → 人工抽检;AI 评审要有 rubric、一票否决、证据引用和人工校准
  • 用例集从生产失败、公开基准改造和手写边界题滚动生长;调参集与留出集分开;用例有生命周期,坏了先隔离
  • 指标分结果/安全/经济三组,Pass@k 与 Pass^k 含义不同;配对比较 + 重复运行 + 留出集复跑,每次运行带版本五元组
  • 自动化的终点是 CI 门禁:评估不过不合并

基于 MIT 协议发布,欢迎转载与贡献。