同一批真实 edit,五个真引擎各判成什么?改完测试过不过?
这一课做什么:前四课用构造的「坑」讲清了每个引擎的规则。这一课只用真实语料:本课项目 miniagent 在 18 次运行里一共发出 33 次 edit_file,去重后 14 条不同的(改前文件 + old_string + new_string)。每一条同时交给五个引擎,看裁决,再看改完之后测试过不过。
实验
口径:引擎逻辑移植自各 pin 源码(卡片上的链接),期望输出由原仓库代码实跑得到(Python 3.12 / Node 26 / rustc 1.97,脚本 src/data/precomputed/gen_a02_fixtures.py,生成于 2026-09-26);T8 语料来自本课项目真实运行(模型 Qwen/Qwen3-8B-MLX-4bit@383413e,录制于 2026-09-26);「改后测试」是把引擎的结果写回任务仓副本、实跑 python -m unittest 的结果,浏览器里改动过的自定义输入不跑测试。Codex 一栏:apply_patch 以整行为单位,这里把 old/new 各按换行切成一个无 @@ 的 update chunk 交给它——这是构造的转写,不是模型写的 patch。
怎么读这张表
- 每一行是一条真实 edit,左边写着它出自哪次运行的第几轮(同一条 edit 在多次运行里出现时列在一起);点一行,下方展开 old_string / new_string 的原文(空格显形)和每个引擎的原话。
- 每一格是一个引擎的裁决:✓ 唯一命中已改,⚠ 多处拒绝,✗ 未命中,⊘ 护栏拒绝。格子下方是把这个引擎的结果写回任务仓副本、实跑
python -m unittest的结果(预计算):测试全过 / 仍失败 / 改坏(语法或缩进错误、或者原本通过的测试挂了)。 - 最后一列是本课项目 miniagent 当时的规则重跑的结果,它与 trace 里记录的真实结果逐条一致(生成脚本里的检查项
t8_miniagent_matches_trace)。 - 右上角的「与原实现一致 x/y」:页面加载时把 fixture 里全部 26 个输入 × 7 个引擎用浏览器移植重跑,逐条比对裁决、报错原文、结果文件的 SHA-256,共 182 项。
试三件事:① 找到出自 pd-truncate-s1 第 4 轮的那一行:learn-claude-code 与 Codex 都「改成功了」,可测试里多挂了一个 test_parse_iso——改到了 ISO 分支;② 找到 pd-v2-unlimited-s1 第 7 轮:它的改前文件测试是全过的,四个按子串匹配的引擎都照改,改完反而挂了,只有 Codex 拒绝;③ 勾上「同时显示构造坑」,对比合计行:没有一个引擎在所有行上都对。
读数
合计行里的几个数(真实语料部分):
| 引擎 | 改了 | 拒绝 | 改后改坏 |
|---|---|---|---|
| learn-claude-code s02 | 13 | 1 | 3 |
| CoreCoder v0.7.0 | 12 | 2 | 2 |
| Pi | 12 | 2 | 2 |
| OpenCode | 13 | 1 | 2 |
| Codex(整行转写) | 12 | 2 | 4 |
| 本课项目 miniagent | 12 | 2 | 2 |
「改坏」的判定:写回后出现语法或缩进错误,或者出现了改前文件里没有的失败测试。「拒绝」不算错——被拒的 edit 文件不动,模型会收到报错再试一次(代价是一轮,第 2 课算过)。
别从这张表读出「哪个引擎最好」。语料只有 14 条、单一模型、单一任务 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条项目实测,样本极小:只能证明表里的每种现象确实会在真实运行中出现,不能估计它们的频率。 出处待补 。它能说明的是:每一种设计都在某一条真实 edit 上付出了代价——只查存在会改错位置,严格唯一会多花轮次,子串匹配会接受半行,整行匹配会丢缩进。选哪条路,是在这些代价之间取舍,这正是里程碑 M2 要你拍板的决策。
自己动手
- 在项目里多跑几次任务(或换一个模型),trace 会多出新的
edit_file; - 运行
python3 src/data/precomputed/gen_a02_fixtures.py,脚本用原仓库代码重算全部期望输出和测试结果; - 刷新本页,新语料会出现在表里,右上角的一致性检查会覆盖它们。
生成脚本不改写任何算法:它从只读的 pin 副本按行号抽取原文来跑——OpenCode 与 Pi 用 Node 直接执行 TypeScript 原文,CoreCoder 按包结构加载原模块、learn-claude-code 抽出 run_edit 原函数,Codex 用 rustc 编译 seek_sequence.rs 原文件;写回任务仓副本后再跑测试。浏览器里的移植只在页面上用,它和脚本的结果逐条比对。
演进地图:你在这里
这张表同时覆盖了演进图的三代:唯一片段、容错匹配、语法约束 patch 的定位。
- 解决了模型不用复述原文,只报行号,输出最短。卡在行号是「上一次读文件时」的快照:第一处修复只要多插 2 行,第二次按旧行号 41 改,就落在
if m:上(第 1 课现算);CoreCoder README 的原话是「Line numbers are a trap」。 - 解决了不需要定位,格式最不容易错:aider 同一个 Qwen2.5-Coder-32B,whole 格式合规 99.6%,diff 格式只有 71.6%。卡在只改 1 行,也要把 44 行的文件整个再写一遍:参数 487 token,片段替换只要 54(9.0 倍,项目模型 tokenizer 实数);还会覆盖别人在此期间的改动。
- 解决了按内容定位,不怕行号过期;0 处或多处都报错、文件不动,让模型自己补上下文。卡在真实 trace pd-truncate-s1:第 4 轮 old_string 在文件里 2 处、第 5 轮缩进多 4 格 0 处,两次报错多跑了两轮,花掉 8,481 token、109.5 秒;唯一也不等于对:pd-v2-unlimited-s1 第 7 轮的 old_string 恰好是已修好那一行的前半截,计数 = 1,于是
+ 1变成了+ 1 + 1。 - 解决了模型复述时的小漂移不再白费一轮:上面第 5 轮那条缩进多 4 格的 edit,OpenCode 直接接住(LineTrimmed 级)。卡在接住 ≠ 改对:多转义一层的引号,OpenCode 的 EscapeNormalized 级照样接住,写回去的是
\",模块导入即 SyntaxError;OpenCode 的 v2(packages/core)眼下只有精确匹配,「edit fuzzy parity」还列在待移植清单里。 - 解决了语法由解码器保证,不再靠 prompt 教。对照:靠 prompt 教格式时,aider 榜上 47 个 diff 格式条目的「格式正确」比例最低只有 64.4%(官方自报)。卡在语法只管格式,定位仍是 seek_sequence:它不检查唯一、取第一处——两处相同的
return datetime.date(year, month, day)会改到第 31 行 ISO 分支,test_parse_iso 跟着坏;整行比较还会把没写缩进的 new 行原样写进去,得到 IndentationError。 - 解决了一套协议接任意外部工具;同一个 harness 给 GPT 换上 apply_patch、给 Claude 用 edit。卡在接进来的每个工具每轮都要付上下文:chrome-devtools-mcp 的 30 个工具在项目模型的 chat template 里占 7,047 token,是 miniagent 自己 6 个工具(685)的 10.3 倍。
- 解决了N 次「模型 → 工具 → 模型」往返压成一段脚本。卡在省的是往返,不省判断:真实 trace 里模型每一步都要看上一步的结果才知道下一步做什么(岔路课逐轮算)。