用固定任务集、相同环境、相同时间预算和自动评分脚本,公平比较不同编程 Agent/模型的真实能力。
Agent 评测本周遍地开花,但大多数都无法与任何东西做对比。某个厂商发一个解决率,某篇博客发另一个,两边都没告诉你用的什么模型、重试策略是多少、Token 预算多少,以及 Agent 有没有被允许运行它声称修复的那些测试。
我不想再为那些无法复现的数字争辩了,于是我写了一个小型评测框架,用同一套固定任务集、同一时间预算、同一评分脚本给任意编程 Agent 打分。本文会带你走一遍这个框架,这样你就可以用它来评测你拥有的任意 Agent 或模型——包括免费层——至少能得到在你自己的环境里有意义的数字。
让数字可比的规则
Agent 之间的对比(或者同一个 Agent 背后不同模型的对比),只有在以下条件下才是公平的:
任务集相同且冻结。把任务写下来,固定在某个仓库里,实验过程中绝不修改。
环境相同。相同的容器镜像、相同的依赖锁文件、相同的网络访问(最好是无网络)。
预算相同。每个任务相同的墙上时钟限制、相同的最大工具调用次数。
评分器相同。自动化脚本,而非你肉眼观察。pytest 的退出码比感觉靠谱。
N > 1 次运行。Agent 是不确定性的。一次运行只是孤例。
大多数已发布的 Agent 数字至少违反了这三条中的三条。
产物:一个约 60 行的 Python 脚本,把任务目录跑一遍 Agent CLI,捕获补丁,然后评分。它与 Agent 无关——你只需要提供一个 shell 命令模板。
#!/usr/bin/env python3
"""agent-harness.py — minimal reproducible coding-agent scorer.
Usage: python agent-harness.py --cmd "myagent run {task_file}" --tasks tasks/ --runs 3
"""
import argparse, json, shutil, subprocess, tempfile, time
from pathlib import Path
def run_task(cmd_tpl: str, task_dir: Path, timeout: int) -> dict:
workdir = Path(tempfile.mkdtemp(prefix="agent-run-"))
shutil.copytree(task_dir / "repo", workdir / "repo")
task_file = workdir / "TASK.md"
shutil.copy(task_dir / "TASK.md", task_file)
t0 = time.monotonic()
proc = subprocess.run(
cmd_tpl.format(task_file=task_file, workdir=workdir / "repo"),
shell=True, cwd=workdir / "repo", capture_output=True, text=True,
timeout=timeout,
)
elapsed = time.monotonic() - t0
grade = subprocess.run(
["bash", str(task_dir / "grade.sh")],
cwd=workdir / "repo", capture_output=True, text=True, timeout=120,
)
result = {
"task": task_dir.name,
"passed": grade.returncode == 0,
"elapsed_s": round(elapsed, 1),
"agent_exit": proc.returncode,
}
shutil.rmtree(workdir, ignore_errors=True)
return result
def main():
p = argparse.ArgumentParser()
p.add_argument("--cmd", required=True, help="command template with {task_file} and {workdir}")
p.add_argument("--tasks", default="tasks")
p.add_argument("--runs", type=int, default=3)
p.add_argument("--timeout", type=int, default=600)
args = p.parse_args()
results = []
for task_dir in sorted(Path(args.tasks).iterdir()):
if not task_dir.is_dir():
continue
for i in range(args.runs):
r = run_task(args.cmd, task_dir, args.timeout)
r["run"] = i
results.append(r)
print(json.dumps(r))
passed = sum(r["passed"] for r in results)
print(f"\npass rate: {passed}/{len(results)} = {passed/len(results):.1%}")
if __name__ == "__main__":
main()
每个任务都是一个目录:
tasks/
fix-off-by-one/
TASK.md # Agent 看到的提示词
repo/ # 坏代码 + 隐藏的测试期望
grade.sh # 运行测试,exit 0 = 通过
#!/usr/bin/env bash
python -m pytest tests/ -q --tb=no
关键是,tests/ 存在于任务目录中,且在 Agent 完成后才被复制进去——这里为了保持片段简短没有展示这点,但要点不变:Agent 永远不会看到评分测试。这一处细节就杜绝了最常见的 Benchmark 作弊方式。
免费模型和免费服务器的位置
每个任务跑 3-5 遍,再乘以像样的任务集规模,Token 消耗很大——这正是大多数人从不做对照比较的原因——他们试了一个 Agent 两次就得出了结论。
披露:本文是 MonkeyCode 产品推广的一部分。
MonkeyCode 目前提供免费模型访问和免费服务器选项,正是这个组合让这套框架对我有了实用价值:我把 --cmd 模板指向 MonkeyCode 驱动的运行,让免费服务器吸收那些重复执行,把付费用量留到最终确认轮次。我有意不在这里引用模型名称、配额或吞吐量,因为那些会变,等你读到本文时我写的任何数字都已经过时了——设计架构前请先查看当前的 offerings。
一个效果不错的工作流:
如果免费层结果和生产层结果不一致,那个不一致本身就是一个发现——通常意味着该任务对模型能力敏感,你应该同时报告两个数字,而不是取平均。
局限性,以及谁不该折腾这个
通过/失败掩盖了质量。一个 Agent 可以用一份不可维护的补丁通过测试。如果这对你重要,可以用 lint 门槛或 diff 大小上限来扩展 grade.sh。
你的任务集就是你的偏见。十道你自己出的题目测的是你关心的东西,而不是通用 Agent 能力。这没问题——只是别把数字当作通用指标来发布。
免费层会变。把免费访问当作迭代方法论的方式,而不是依赖的基础设施。如果你的对比管道在免费选项消失时就坏了,那管道才是产品,而不是对比本身。
如果你只有一个 Agent 和一个模型,跳过这个框架。你不需要统计数据来回答"这对我有用吗",你需要的是一周的真实使用。
这个练习真正有用的输出不是排行榜——而是一条个人基准线:给定我的任务、我的预算、我的评分,哪个配置真正能把活干完?如果你从你实际踩过的 Bug 出发构建自己的任务集,我真的很想听听你的通过率是多少、Agent 栽在了哪里。