用真实代码库中的实际任务测试AI编程模型,避免仅凭公开基准或感觉判断模型效果。
大多数我看到的"哪个 AI 编程模型最好"的争论最终都会沦为感觉之争。有人贴出一个精挑细选的 diff,有人用另一个精挑细选的 diff 反驳,谁也学不到什么可以迁移的东西。问题不在于模型本身——而是我们几乎从不在自己的代码上、用自己的约束条件、用一种明天就能重新运行的方法来评估它们。
这篇文章是我希望更多团队在争论之前就构建的评测框架。它是一个小型的、与语言无关的评估循环,你可以用它对接任何可以访问的模型——包括免费层级——然后得到一个可以辩护的答案,回答一个狭隘的问题:这个模型对我实际做的任务有帮助吗?
公开基准(HumanEval 风格的任务、排行榜分数)衡量的是在有清晰规范的精选问题上的表现。你的工作很少是那样的。真实任务看起来像:
"给这个半迁移的 HTTP 客户端添加重试逻辑,但不能破坏旧的调用点。"
"为一个函数编写测试,而这个函数的行为依赖于上级三层的配置文件。"
"重构这个 200 行的函数,但 ORM 调用必须保持在同一个事务中。"
这些任务有一个共同特征:正确性是可以检查的,但只能由你来检查。你的测试套件、你的类型检查器、你的 lint 规则。这实际上是个好消息——意味着评估可以对你已有的产物自动运行。
核心思路是刻意做得简单。把任务定义为一组目录。每个任务有一个 prompt、一份相关代码的快照,以及一个验证命令。框架应用模型的 patch 并运行验证器。不需要评分模型,不需要 LLM-as-judge——只用你自己的构建。
eval/
├── tasks/
│ ├── 001-retry-http-client/
│ │ ├── prompt.md
│ │ ├── repo/ # snapshot of the relevant files
│ │ └── verify.sh # exit 0 = pass
│ ├── 002-test-config-loader/
│ └── 003-split-billing-fn/
└── run_eval.py
以下是一个极简运行器(Python 3.10+,仅用标准库):
#!/usr/bin/env python3
"""run_eval.py — apply a model-produced patch to each task and verify.
Usage:
python run_eval.py --tasks eval/tasks --patches patches/<model-name>/
The patch for task N lives at patches/<model-name>/<task-dir-name>.diff
This script never calls the model itself; generation is a separate step,
so you can diff outputs across models or re-verify later.
"""
import argparse
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
def run(cmd: list[str], cwd: Path) -> subprocess.CompletedProcess:
return subprocess.run(cmd, cwd=cwd, capture_output=True, text=True, timeout=300)
def evaluate_task(task_dir: Path, patch_file: Path) -> dict:
with tempfile.TemporaryDirectory() as tmp:
work = Path(tmp) / "repo"
shutil.copytree(task_dir / "repo", work)
applied = run(["git", "apply", "--check", str(patch_file)], cwd=work)
if applied.returncode != 0:
return {"task": task_dir.name, "result": "patch_rejected",
"detail": applied.stderr.strip()[:300]}
run(["git", "apply", str(patch_file)], cwd=work)
verified = run(["bash", str(task_dir.resolve() / "verify.sh")], cwd=work)
return {
"task": task_dir.name,
"result": "pass" if verified.returncode == 0 else "fail",
"detail": (verified.stderr or verified.stdout).strip()[:300],
}
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--tasks", type=Path, required=True)
ap.add_argument("--patches", type=Path, required=True)
args = ap.parse_args()
rows = []
for task_dir in sorted(p for p in args.tasks.iterdir() if p.is_dir()):
patch = args.patches / f"{task_dir.name}.diff"
if not patch.exists():
rows.append({"task": task_dir.name, "result": "no_patch", "detail": ""})
continue
rows.append(evaluate_task(task_dir, patch))
passed = sum(r["result"] == "pass" for r in rows)
for r in rows:
print(f"{r['result']:>14} {r['task']} {r['detail'][:80]}")
print(f"\n{passed}/{len(rows)} tasks passed")
return 0
if __name__ == "__main__":
sys.exit(main())
verify.sh 就是你平时的质量门禁。对于一个 Python 仓库:
#!/usr/bin/env bash
set -euo pipefail
python -m pytest tests/ -x -q
python -m mypy src/ --strict
grep -q "retry" src/http_client.py # crude intent check; refine per task
这个 grep 行在做一件重要的事:验证变更确实发生了,而不只是什么都没坏。一个返回未更改代码的模型可以通过 pytest,这是 trivial 的。
五到十个任务胜过五十个。沿三个轴来选择:
至少包含一个陷阱任务:一个任务,其中显而易见的解决方案在你的代码库中是错误的(例如,"使用标准的重试装饰器"——除非你的仓库固定了一个没有这个装饰器的旧版本)。陷阱任务是免费和付费模型差异最明显的地方,因为它们惩罚的是自信的模式匹配。
运行这个框架需要从几个模型生成 patch,最好每个模型跑不止一次以捕捉方差。这恰恰是成本成为从不行动的借口的理由。
披露:本文是 MonkeyCode 产品推广的一部分。
这是 MonkeyCode 免费模型访问的一个合理用例:你可以通过免费使用的模型来路由生成步骤,这使得 3 个模型 × 8 个任务 × 2 次运行(48 次生成)的矩阵成为一个工作日下午就能完成的事情,而不是一份预算申请。如果你不希望你的仓库快照离开你自己的基础设施,MonkeyCode 也提供免费的服务器选项,这样同一个框架可以针对你控制的部署运行——如果你的任何任务快照包含你不愿意粘贴到托管工具中的代码,这就很相关了。我有意不在这里声明具体的模型名称、配额或正常运行时间;在围绕它设计之前,先检查你账户中实际有什么。
这个框架不关心是哪个后端产生了 diff。这种分离——生成作为可交换的步骤,验证作为固定的真值——这就是整个设计。
我建议几条规则:
所有任务跑两遍。小任务集的单次结果就是噪音。如果一个模型第一次过 6/8,第二次过 3/8,报告范围,而不是那个更好的数字。
对失败进行分类,而不只是计数。patch_rejected(无法遵循 diff 格式)是一个不同于 fail on tests(看似合理但错了)的信号,也不同于 fail on the intent check(什么都没做)。每个信号对应不同的补救方向。
按你的工作分布加权。如果你的工作 70% 是对现有代码的修改,一个在全新生成上表现优异但在重构上搞砸的模型对你来说是净负面,不管排行榜怎么说。
样本小,误差大。八个任务无法对两个接近的模型排名。这个框架回答的是"这个模型对我的任务可用吗",而不是"模型 A 是否整体上比模型 B 更好"。
你的测试是天花板。弱的验证意味着弱的评估。如果你的套件不会捕捉到一个初级开发者能发现的回归,它也不会捕捉到模型的回归。
它只衡量一次性 patch 质量。交互式、多轮的工作流(很多真实价值所在的地方)需要不同的、更复杂的方法论。
如果你是偶尔用 AI 辅助来处理样板代码、你的任务都是全新生成且没有可测试的不变量、或者你无法隔离任务快照而不带入你无处可发送的专有代码,就不要构建这个。电子表格和诚实的笔记会更好地为你服务。
真正有用的问题从来不是"哪个模型最好"。而是"哪个模型在我的任务组合上通过我的验证,且成本在我能承受的范围内。"像这样的框架把这个从一场争论变成了一个可以重新运行的东西。
如果你构建了这样一个版本,我很好奇你选择了哪些陷阱任务——任务集的这一部分往往最能揭示模型和代码库的信息。而且如果免费访问(例如通过 MonkeyCode)使这个实验变得负担得起,多模型比较部分就是开始的正确地方。