Agent 评测:用可复跑的状态断言代替“看起来不错”
给多轮 Agent 设计一套能复跑的评测:用状态检查代替话术检查,把确定性断言、模型评分和人工校准分开,并让每次失败都能定位到具体一步。
本页目录
目标:让“这版比上版好”变成可复现的结论
Agent 的失败很少是“答错了”这么干净:它可能在第 3 步读错文件、第 7 步用了过期 API、第 12 步把测试改成通过。只看最终回答,你既不知道它错在哪,也不知道换个模型会不会更差。
本文搭建一套最小可用的评测:固定任务集 → 状态断言 → 分级评分 → 人工校准,并保证同一份输入今天跑和下周跑结论可比。
前置条件
- 有可重复执行的任务(同一输入能得到可比较的产物)
- 产物可检查:文件内容、测试结果、数据库记录、HTTP 响应之一
- 能保存每次运行的完整轨迹(步骤、工具调用、参数、返回摘要)
- 一个能跑的基线版本,用于对比
原则一:优先检查状态,而不是检查说法
Anthropic 在评测方法文章里的建议很直接:能查结果就查结果——测试是否通过、记录是否改对、浏览器状态是否正确、工具的副作用是否符合预期。文本相似度或“读起来对不对”应该是最后手段。
同一个任务,两种判分方式差别很大:
任务:修复 parseDuration 对 "1h30m" 的解析错误。
弱检查:最终回复里是否包含"已修复"、"1.5 小时"字样。
强检查:
- node --test 退出码为 0
- parseDuration("1h30m") === 5400000
- 原有 6 条用例仍通过
- 未修改 src/parseDuration.test.js(防它改测试)
强检查能抓住“改测试让它过”这种最常见的作弊路径,弱检查反而奖励话术。
原则二:任务集要小、要覆盖失败类型
起步 10~20 条就够。关键是每条对应一类你已经见过的失败,而不是凑数量:
| 编号 | 任务 | 主要考察 | 判定方式 |
|---|---|---|---|
| T01 | 修一个单文件 bug | 定位与最小改动 | 测试通过 + diff 只含目标文件 |
| T02 | 新增一个小函数 | 契约理解 | 签名匹配 + 边界用例通过 |
| T03 | 跨 3 个文件重构 | 影响面控制 | 全量测试 + 无新增依赖 |
| T04 | 需求有歧义 | 是否先问再动手 | 轨迹中出现澄清,且未提前改代码 |
| T05 | 工具持续报错 | 失败预算 | 在预算内停止并说明原因 |
| T06 | 无关任务 | 不越界 | 未修改禁区目录、未执行网络写操作 |
T04、T05、T06 这类“负向任务”最容易被忽略,却是上线前最容易出事的场景。
原则三:三条评分通道分开记录
单一评分通道都会系统性偏差,实践中要分开记:
- 确定性检查(权重最高):退出码、断言、文件哈希、schema 校验。可复现、无争议。
- 模型评分(用于主观维度):是否可读、解释是否准确。必须固定评分提示词与模型版本,并给出结构化输出(分数 + 理由 + 证据位置)。
- 人工校准(抽样):从模型评分里抽 10% 复核,记录不一致的样本。不一致率持续偏高时,说明评分提示词需要改,而不是“模型不行”。
关键纪律:不要让同一个模型既生成又评分,也不要用评分模型的结论覆盖确定性断言的结论。
一份可复跑的运行器
type Case = { id: string; task: string; verify: () => Promise<boolean>; forbid?: string[] };
async function runSuite(cases: Case[], agent: (task: string) => Promise<Run>) {
const rows = [];
for (const c of cases) {
const run = await agent(c.task);
const passed = await c.verify();
const touchedForbidden = (c.forbid ?? []).some((p) => run.touchedPaths.includes(p));
rows.push({
id: c.id,
passed: passed && !touchedForbidden,
steps: run.steps,
stopReason: run.stopReason,
tokens: run.usage.totalTokens,
durationMs: run.durationMs,
tracePath: run.tracePath,
});
}
return rows;
}
每次运行都要同时记录 步数、停止原因、token、耗时、轨迹路径。只看通过率会漏掉“通过了但花了 40 步、改了 12 个文件”的退化。
结果怎么读
- 通过率:主线指标,但必须和上一条一起看。
- 退化项:这次通过、上次通过、中间改过提示词或换过模型 → 逐条回放轨迹定位。
- 失败分类:把失败归到“定位错”“契约误解”“工具误用”“预算耗尽”“越界”五类,连续两轮同一类占比最高,就优先修那一类。
- 波动:同一版本连跑 3 次,通过率波动超过 15% 说明任务本身不稳定(依赖网络、时间、随机数据),先把评测环境固定下来。
验收标准
- 同一版本连跑 3 次,通过率差异在可接受范围内,且失败项一致或能解释;
- 每条失败都能给出轨迹路径和首个错误步骤,而不是只有一句“未通过”;
- 人为注入一个明显 bug(比如把断言写反),评测能把它抓出来——如果抓不出,说明评测本身失效;
- 有人工抽样记录,并统计了与模型评分的不一致率。
常见错误
- 用最终答案评分。 多轮任务的失败发生在中间步骤,最终答案可能掩盖它。
- 任务集只放成功案例。 没有负向任务,就测不出越界与乱试。
- 改评测让它通过。 评测和实现必须分开评审,改断言要写明理由。
- 忽略成本与延迟。 通过率一样但成本翻倍,也是退化。
- 一次跑一条就下结论。 单次结果受采样影响,结论要靠重复。
评测稳定后,可以把它挂到流水线上自动跑:/articles/automation-pipeline/。理解循环本身为什么需要预算,读 /articles/agent-tool-loop/。
参考来源
- Anthropic《Demystifying evals for AI agents》(2026-01):优先结果与状态检查;确定性检查、模型评分与人工校准结合 https://www.anthropic.com/research/demystifying-evals-for-ai-agents
- Anthropic《Building effective agents》:agent 与 workflow 的边界、评测驱动迭代 https://www.anthropic.com/research/building-effective-agents
- 本文代码为结构性示例,评分与轨迹字段需按你的运行时调整