用 SWE-bench 思路、golden tasks、repo fixtures、工具调用评估和回归测试,建立能判断 coding agent 是否真的变强的评测体系。
Agent 评测不是问模型“答案看起来对不对”,而是把真实工程任务变成可重复执行、可复盘、可回归的测试集。一个生产级 coding agent 至少要同时评估四件事:任务是否完成、证据是否可信、工具调用是否合理、失败是否可分类。
SWE-bench 的核心启发是:让 agent 在真实仓库里修真实问题,然后用测试和 diff 证明结果。课程里不直接复刻 SWE-bench,而是先做自己的 golden tasks:每个任务固定仓库 fixture、初始状态、用户 prompt、允许工具、成功标准和判分证据。这样你才能比较两个 prompt、两个模型、两套工具权限,哪一个真的让 agent 更强。
阶段四你已经能让 agent 处理权限、worktree、subagent、PR 和 code review。现在的问题变成:
如果今天改了 system prompt、工具描述或 planner 策略,怎么证明 agent 变好了,而不是只是在一个 demo 上表现更顺?
这节课的入口不是“写一个排行榜”,而是建立最小评测闭环:准备一批稳定任务,运行 agent,收集命令证据,按成功标准判分,再把失败沉淀成下一轮改进输入。
评测体系可以分成五层:
第一层是任务层。GoldenTask 描述用户想要什么,最好来自真实 issue、失败测试、代码迁移或文档修复,而不是人工编造的谜题。
第二层是环境层。repo fixture 固定初始仓库、依赖锁文件、测试命令和允许修改范围,保证今天和下周跑的是同一个问题。
第三层是执行层。agent 可以自由读文件、调用工具、编辑代码、运行测试,但每一步都要留下事件、命令和 diff 证据。
第四层是判分层。不要只看最终回答。要检查测试是否通过、diff 是否存在、是否改了禁止路径、工具调用是否满足预期、是否伪造成功。
第五层是回归层。每个线上失败、危险工具调用、错误修复,都应该变成新的 golden task 或 regression task。
1. 从 SWE-bench 抽象出可落地结构
Section titled “1. 从 SWE-bench 抽象出可落地结构”SWE-bench 的思想可以简化成:
真实仓库 + 真实问题 + agent 修改 + 测试验证 = 可比较的任务结果在自己的课程项目里,第一版不要追求大规模。先建 20 个真实工程任务,每个任务只回答一个问题:这个 agent 是否能在受控仓库里交付可验证结果。
2. 定义 golden task
Section titled “2. 定义 golden task”一个任务至少包含:
id:稳定标识,例如fix-login-test。prompt:交给 agent 的用户任务。fixture:仓库初始目录或可 checkout 的 commit。allowedTools:本任务允许的工具集合。successCriteria:机器可检查或人工可复核的成功标准。evidenceRequired:必须收集的命令、测试输出、diff 或日志。
课程 lab 的起点是 /Users/ryanchen/codespace/pi-agent-course-lab/src/17-benchmark-runner.ts,里面已经有 GoldenTask、RunResult 和 grade() 的最小形状。
3. 固定 repo fixtures
Section titled “3. 固定 repo fixtures”repo fixture 解决的是“评测环境漂移”问题。不要在开发者当前工作区直接跑 benchmark,否则依赖版本、未提交改动、缓存和本地配置都会污染结果。
推荐结构:
benchmarks/ fixtures/ fix-test/ repo/ task.json expected.patch? # 可选,只用于人工对照,不作为唯一判分每次运行前复制 fixture 到临时目录或 worktree。agent 只能在临时目录里工作,跑完后收集 git diff、测试输出、trace 和命令日志。
4. 把工具调用也纳入评估
Section titled “4. 把工具调用也纳入评估”coding agent 的成败不只来自最终 diff。工具调用质量会暴露很多问题:
- 是否先读上下文再修改。
- 是否运行了相关测试。
- 是否使用了禁止工具。
- 是否在失败后继续声称成功。
- 是否反复调用昂贵工具但没有新增信息。
Pi 的低层 agent loop 会发出 tool_execution_start、tool_execution_update、tool_execution_end 等事件。评测 runner 可以从事件里聚合工具序列,而不是靠解析终端文本猜测发生了什么。
5. 设计成功标准
Section titled “5. 设计成功标准”成功标准要分层:
must:必须满足,例如npm test exit 0、git diff非空、禁止路径未改。should:强烈期望,例如先读测试文件、失败后重跑目标测试。diagnostic:不直接判失败,但帮助解释,例如 token、cost、工具调用次数、耗时。
第一版 grade() 可以像 starter 一样用字符串包含判断;生产版应该把 evidence 设计成结构化记录。
6. 把失败变成回归测试
Section titled “6. 把失败变成回归测试”每次 benchmark 失败后,不要只改 prompt。先给失败分类:
task_understood_wrong:任务理解错。bad_context:没读关键文件。bad_tool_choice:工具选择或参数错误。test_not_run:没有验证。false_success:没有证据却声称完成。unsafe_action:越权修改或危险命令。
然后决定:这是 prompt 问题、工具描述问题、权限问题、fixture 问题,还是 agent loop / runtime 问题。只有能复现的失败,才是有价值的改进输入。
本课实践基于课程 lab:
cd /Users/ryanchen/codespace/pi-agent-course-labsed -n '1,220p' src/17-benchmark-runner.ts先不要急着接真实模型。第一步是把 starter 里的单个 task 扩展成数组,并把 evidence 从字符串数组升级成结构化证据:
type Evidence = | { kind: "command"; command: string; exitCode: number; output: string } | { kind: "diff"; filesChanged: string[] } | { kind: "tool"; name: string; isError: boolean };然后让 grade() 只依赖 evidence,而不是依赖 agent 的最终自然语言回答。这样可以避免“回答写得很像完成了,但测试没有通过”的假阳性。
课堂代码推演
Section titled “课堂代码推演”课堂上会把 benchmark runner 拆成三段变量流:
const task = loadGoldenTask("fix-test");const fixtureDir = copyFixtureToTemp(task.fixture);
const trace = await runAgent({ cwd: fixtureDir, prompt: task.prompt, tools: task.allowedTools,});
const evidence = collectEvidence({ trace, cwd: fixtureDir, testCommand: task.testCommand,});
const result = grade(task, evidence);这里有三个控制点。
第一个控制点是 copyFixtureToTemp()。它保证 agent 的输入环境固定,避免污染真实仓库。没有这一层,benchmark 结果不能比较。
第二个控制点是 runAgent()。它不直接返回“通过/失败”,只返回 trace、命令和 diff。agent 负责尝试完成任务,runner 负责判分。
第三个控制点是 grade()。task.successCriteria 从任务定义流入判分函数,evidence 从执行过程流入判分函数。判分函数不应该重新调用模型,否则评测会变成另一个不可控的 agent。
可以从 starter 的最小版本演进:
const passed = task.successCriteria.every((criterion) => evidence.some((line) => line.toLowerCase().includes(criterion.toLowerCase())),);再升级成结构化判分:
const hasPassingTest = evidence.some( (item) => item.kind === "command" && item.command === task.testCommand && item.exitCode === 0,);const hasDiff = evidence.some( (item) => item.kind === "diff" && item.filesChanged.length > 0,);
return { taskId: task.id, passed: hasPassingTest && hasDiff, evidence };这段推演的重点不是代码量,而是职责边界:agent 产生行为,trace 记录行为,evidence 摘取证据,grader 按任务标准判分。
/Users/ryanchen/codespace/pi-agent-course-lab/src/17-benchmark-runner.ts:课程 lab 的 benchmark runner starter,包含GoldenTask、RunResult和grade()的最小实现。/Users/ryanchen/codespace/external-sources/pi/packages/coding-agent/test/test-harness.tsat61babc2:Pi 测试 harness,提供 faux model、事件捕获和一键创建AgentSession的测试环境,可作为 golden task runner 的测试设计参考。/Users/ryanchen/codespace/external-sources/pi/packages/coding-agent/test/suite/harness.tsat61babc2:新测试套件的本地 harness,展示如何用 faux provider、临时目录、工具和事件数组构造稳定测试。/Users/ryanchen/codespace/external-sources/pi/packages/coding-agent/test/suite/regressions/2835-tools-allowlist-filters-extension-tools.test.tsat61babc2:工具 allowlist 的回归测试,说明“允许工具是否生效”可以成为 agent 评测的一部分。/Users/ryanchen/codespace/external-sources/pi/packages/agent/src/agent-loop.tsat61babc2:低层 loop 发出tool_execution_start、tool_execution_update、tool_execution_end和 tool result message,是工具调用评估的事件来源。/Users/ryanchen/codespace/external-sources/pi/packages/agent/src/types.tsat61babc2:定义beforeToolCall、afterToolCall、ToolExecutionMode等工具执行控制点,可用于实现评测期间的阻断、记录和判分。/Users/ryanchen/codespace/external-sources/pi/packages/coding-agent/test/agent-session-stats.test.tsat61babc2:围绕 session token、context usage 和统计信息的测试,可作为评测指标测试的参考。
学完本课后,你应该能做到:
- 解释 SWE-bench 思路为什么适合评估 coding agent,而不是只评估问答质量。
- 设计一个
GoldenTask,包含 prompt、fixture、允许工具、成功标准和证据要求。 - 说明 repo fixture 为什么必须固定初始状态,并能把执行目录和真实仓库隔离。
- 把测试输出、diff、工具调用、失败类型转成可判分 evidence。
- 区分任务成功、工具调用质量、成本指标和诊断指标。
- 把一次线上失败沉淀成 regression task,而不是只靠临时 prompt 修补。
- 实践题:扩展
/Users/ryanchen/codespace/pi-agent-course-lab/src/17-benchmark-runner.ts,把单个task改成 3 个GoldenTask,并让grade()输出通过率和每个任务缺失的 success criteria。 - 思考题:如果一个 agent 最终让测试通过了,但它没有先读相关文件、调用了多次无关命令、还修改了无关目录,这个任务应该算完全成功、部分成功,还是失败?请写出你的判分规则。