Agent 进阶 约 4 分钟

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 这类“负向任务”最容易被忽略,却是上线前最容易出事的场景。

原则三:三条评分通道分开记录

单一评分通道都会系统性偏差,实践中要分开记:

  1. 确定性检查(权重最高):退出码、断言、文件哈希、schema 校验。可复现、无争议。
  2. 模型评分(用于主观维度):是否可读、解释是否准确。必须固定评分提示词与模型版本,并给出结构化输出(分数 + 理由 + 证据位置)。
  3. 人工校准(抽样):从模型评分里抽 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% 说明任务本身不稳定(依赖网络、时间、随机数据),先把评测环境固定下来。

验收标准

  1. 同一版本连跑 3 次,通过率差异在可接受范围内,且失败项一致或能解释;
  2. 每条失败都能给出轨迹路径和首个错误步骤,而不是只有一句“未通过”;
  3. 人为注入一个明显 bug(比如把断言写反),评测能把它抓出来——如果抓不出,说明评测本身失效;
  4. 有人工抽样记录,并统计了与模型评分的不一致率。

常见错误

  • 用最终答案评分。 多轮任务的失败发生在中间步骤,最终答案可能掩盖它。
  • 任务集只放成功案例。 没有负向任务,就测不出越界与乱试。
  • 改评测让它通过。 评测和实现必须分开评审,改断言要写明理由。
  • 忽略成本与延迟。 通过率一样但成本翻倍,也是退化。
  • 一次跑一条就下结论。 单次结果受采样影响,结论要靠重复。

评测稳定后,可以把它挂到流水线上自动跑:/articles/automation-pipeline/。理解循环本身为什么需要预算,读 /articles/agent-tool-loop/。

参考来源