一个可复现的小型测试工具:在代码模型接触你的代码库之前先对比
问题
大多数 AI 编码模型的对比都是道听途说。有人贴出一条提示词,瞟一眼输出,然后写下结论。这几乎说明不了任何问题——同一个模型在全新的代码片段上可能表现惊艳,一放到你真实的代码库里就溃不成军。更好的做法是:在把任何模型接入你的日常工作流之前——更别提给它一个仓库的写权限——先用一组小而固定的任务配上自动化检查跑一遍。这篇文章带你搭一个可以直接复制、运行和扩展的测试 harness。它刻意做得无聊。无聊才能让结果可复现。
这个 harness 做什么
思路分三块:
- 一个固定的任务文件——几条提示词,每条都带一个机器可检查的成功条件(用测试命令,而不是凭感觉)。
- 一个运行脚本——把每个任务发给模型,将生成的代码写到临时目录,运行检查,记录通过/失败和延迟。
- 一个决策表——让对比结果真正驱动你的选择,而不是沦为博客里的个人观点。
所有操作都在临时目录中进行,不接触你真实项目的网络。最后这一点很重要:模型评测绝不应该和你在意的代码共享同一个工作目录。
任务文件
# tasks.yaml
tasks :
- id : fizzbuzz-edge
prompt : >
Write a Python function classify(n) that returns 'fizzbuzz' for multiples of 15, 'fizz' for multiples of 3, 'buzz' for multiples of 5, and the number as a string otherwise. Handle 0 and negatives.
check : python test_fizzbuzz_edge.py
- id : json-repair
prompt : >
Write a Python function repair_json(s) that accepts JSON with trailing commas and returns a parsed dict, raising ValueError on anything still invalid.
check : python test_json_repair.py
每个 check 都是一个小的 pytest 文件,你要在看任何模型输出之前写好它。先写测试才是关键——这样你就不会因为偏爱某个模型而悄悄降低标准。
运行脚本
这是刻意保持最小化的——把 call_model 存根换成你实际用的任何客户端:
# runner.py — pseudocode-level skeleton, adapt the client call
import subprocess , time , json , yaml , pathlib
def call_model ( prompt : str ) -> str :
""" Replace with your model client.
返回生成的代码文本。
"""
raise NotImplementedError
def run_task(task, workdir: pathlib.Path) -> dict:
code = call_model(task["prompt"])
(workdir / "solution.py").write_text(code)
start = time.time()
result = subprocess.run(
task["check"].split(),
cwd=workdir,
capture_output=True,
text=True,
timeout=60,
)
return {
"id": task["id"],
"passed": result.returncode == 0,
"latency_s": round(time.time() - start, 2),
"stderr_tail": result.stderr[-500:] if result.returncode else "",
}
def main():
tasks = yaml.safe_load(open("tasks.yaml"))["tasks"]
workdir = pathlib.Path("./scratch")
workdir.mkdir(exist_ok=True)
results = [run_task(t, workdir) for t in tasks]
print(json.dumps(results, indent=2))
if __name__ == "__main__":
main()
每个模型把整套任务完整跑三遍,重点看结果的一致性,而不只是单次最好成绩。一个任务两次通过、一次失败的模型,和三次全部通过的模型,在实际使用中完全是两回事——即使单看最好成绩两者一模一样。
把输出变成决策
| 问题 | 阈值 | 理由 |
|---|---|---|
| 三次运行中的通过率 | 每个任务 ≥ 80% | 低于这个线,反复重试会把你省下的时间全吃掉 |
| 失败模式 | 语法错误 vs. 隐蔽的逻辑错误 | 语法错误很容易抓出来;但那些看着像模像样、实际是错逻辑的问题,代价就高得多 |
| 延迟波动 | 要稳定,不只是快 | 忽快忽慢的延迟比一直慢更打断节奏 |
| 失败运行的成本 | 接近于零 | 如果一次失败会消耗真金白银或配额,阈值就该往上调 |
最后一行才是真正让人注意到免费额度价值的地方。失败运行不花钱,就可以用更大的任务集、跑更多轮,而这恰恰是评测结果可信的关键。我正是在这个背景下关注到 MonkeyCode 的——它提供免费的模型访问,还支持在免费服务器上运行,上面这套评测工具可以尽情跑,不用担心每次重试都在烧账单。
信息披露:本文是 MonkeyCode 产品推广活动的一部分。
```
具体来说,这套流程是这样的:把 call_model 指向你想评估的模型,tasks.yaml 和预先写好的检查脚本保持不变,然后每个模型各自输出一份 JSON 结果。因为任务和检查项从不改动,结果上的任何差异都只能归因于模型本身——而不是你今天发挥得好不好。免费服务器选项还有一个不太显眼的价值:它杜绝了“为了省钱而缩小任务集”的冲动,而大多数自建评测项目恰恰就是死在这上面。
局限——这段请认真看
这套框架测的是玩具级任务,不是你的真实代码库。一个能轻松修好 repair_json 的模型,到了四千行老旧的遗留模块上照样可能搞得一团糟。建议把它当做一个过滤器:用来筛掉明显不合适的模型,而不是证明某个模型真的够格。下一步应该是用同一套框架,指向你自己代码里抽出来的三个真实函数。
另外记住,跑三次只是下限,不是上限。采样存在不确定性,样本太少容易得出误导性结论。如果两个模型结果接近,先多跑几轮再下结论。
免费额度是会变的。任何免费访问方式——包括 MonkeyCode 在内——都可能随时调整限制、排队机制或服务条款。设计框架时,最好把替换客户端做成五行的改动,并且在正式依赖它跑 CI 之前重新确认当前条款。
不要让生成代码在无沙箱环境里运行。上面那个临时目录只是图个方便,不是安全边界。如果执行模型输出而你还没过目,务必放在容器或虚拟机里跑,不要挂载任何凭证。最近关于 AI 代理越权问题的讨论这么火,不是没有原因的。
谁不需要这套东西
如果你只是拿 AI 助手回答些零散问题,从不让它的输出在任何地方运行,那这套框架纯属多余——直接用工具就行。同样,如果团队已经有现成的评估管线,用它就好;这套框架的价值,是为那些目前没有任何可复现对比方式、只能靠感觉选模型的人和小团队准备的。
如果你正好属于后者,那就从自己一周的工作里挑五个任务,先把检查写出来,再把框架跑起来——MonkeyCode 的免费档是个低门槛的起点,不过这框架适用于任何你能塞进 call_model 的客户端。
关键在于这个工具本身:一旦运行器搭好,以后再评估任何模型,都只是一下午的活儿,而不是一次凭感觉的赌注。