
“我的工作是编写循环。”
这是Boris Cherny,他是Anthropic Claude Code的负责人。他说他不再直接提示Claude,目前花时间设计为他提示Claude的循环[1]。这句话,以及其他类似的话,引发了今年一波循环工程解释文章[1][2]。我读了六篇。然后我构建了一个。
具体来说,是两个部分——每个解释者都提到但几乎没有人实际运行的两个部分。运行直到完成:将模型自己的真实测试失败反馈给它,而不是要求它再次猜测。制造者/检查者:不要让编写代码的模型决定代码是否正确。
我从零开始构建了两个部分,大约600行Python,连接到 claude-opus-4-8,针对MBPP+进行评分[3]。本文中每个实验的总花费:不到两美元。而第二部分——每篇文章都认为是安全的部分,由于它“实际运行测试”而不是仅仅信任模型的话——在我的数据中做了那些解释者都没有警告我的事情。
TL;DR:循环工程的两个核心部分很容易连接,也容易悄悄出错。我的“真实反馈”循环看起来与随机重试完全一样,直到我发现自己测试工具中的错误。我的“安全”测试运行验证器比仅仅询问模型信心的检查器有更高的错误接受率。构建循环是简单的20%。
1、将循环连接到无
关于循环工程的大部分出版物止步于接线图。触发器、可验证目标、工具、状态、停止规则——五个框,每个之间一个箭头,完成。暗示是一旦框连接起来,循环就能工作。
这与安装烟雾探测器并称房屋安全是同样的逻辑。探测器在天花板上。它已接线。没有人检查里面是否有电池。
我在“真实反馈”循环中遇到了这种确切的失败。它连接到实际的测试输出,而不是通用的重试提示。理论上,它应该明显优于仅由“那是错误的,再试一次”驱动的循环。我的第一次运行却不然。
2、评估评估器的评分器
在接触循环之前,我构建了其他一切依赖的东西:一个评分器,它在具有硬超时的隔离子进程中运行候选代码,并根据隐藏测试对其进行评分。
我不信任它,直到它评估自己。给它一个已知良好的解决方案——它必须通过。给它一个已知错误的——它必须失败,并附带断言错误。给它一个无限循环——它必须被超时杀死,而不是永远挂起。
def scorer_selftest() -> None:
tests = ["assert add(2, 3) == 5", "assert add(-1, 1) == 0"]
good = run_tests("def add(a, b):
return a + b", tests)
assert good["all_pass"]
bad = run_tests("def add(a, b):
return a - b", tests)
assert not bad["all_pass"] and "AssertionError" in bad["stderr"]
loop = run_tests("def add(a, b):
while True:
pass", tests, timeout_s=3)
assert loop["timed_out"]
def scorer_selftest() -> None:
tests = ["assert add(2, 3) == 5", "assert add(-1, 1) == 0"]
good = run_tests("def add(a, b):
return a + b", tests)
assert good["all_pass"]
bad = run_tests("def add(a, b):
return a - b", tests)
assert not bad["all_pass"] and "AssertionError" in bad["stderr"]
loop = run_tests("def add(a, b):
while True:
pass", tests, timeout_s=3)
assert loop["timed_out"]
然后我针对75个MBPP+参考解决方案验证了整个管道。所有75个都通过了。只有在那之后,我才信任循环产生的一个数字。
3、看起来很好但并非如此的循环
循环本身超级简单。生成解决方案,评分,失败时将实际stderr反馈回来——不是“再试一次”,而是实际错误——最多三次尝试。
我还构建了一个控制组,由于我不想在没有控制组的情况下信任一个头条数字:运行一样的循环,但用通用的“那是错误的,写一个不同的解决方案”替换实际错误。如果真实反馈没有明显优于那个,那么接线中的某些东西就坏了。
第一次运行,35个问题:
single-shot (pass@1) 32/35 91.4%
loop, real feedback 32/35 91.4% ← identical
loop, generic feedback 32/35 91.4%
single-shot (pass@1) 32/35 91.4%
loop, real feedback 32/35 91.4% ← identical
loop, generic feedback 32/35 91.4%
一样。所有三个组。这不是循环工作,这是穿着循环衣服的危险信号。
我深入研究失败而不是头条数字,发现了它:MBPP+的隐藏测试工具因裸 AssertionError 而失败——没有失败输入,没有预期值,没有实际值。“真实反馈”在信息上与“再试一次”一样,由于其中没有任何模型可以操作的内容。
我为工具添加了报告失败输入、预期输出和代码实际返回的内容。同样的35个问题,第二次运行:
single-shot (pass@1) 32/35 91.4%
loop, real feedback 33/35 94.3%
loop, generic feedback 32/35 91.4%
single-shot (pass@1) 32/35 91.4%
loop, real feedback 33/35 94.3%
loop, generic feedback 32/35 91.4%
真实反馈恢复了通用组无法解决的问题,整个运行大约增加了2500个额外的输入令牌。循环从未崩溃。它连接的信号是空的,只有控制组显示了这一点——头条指标永远不会显示。
4、验证器以理论未预测的方式失败
循环需要一个停止规则,“模型说它完成了”不是一个。所以我构建了一个检查器,它根据规范编写自己的测试——从未见过隐藏测试,从未见过自己解决方案的代码——然后实际运行它们。只有在全部通过时才接受。默认拒绝。
我将它与三个较弱的检查器进行了比较,针对我的循环产生的41个候选,33个正确,8个错误,测量错误接受率:每个检查器有多少次通过实际上损坏的代码。
我预计运行测试的检查器在错误接受率上会明显胜出。它没有。它让通过的错误代码比例比任何一个基于意见的检查器都高。
缘由比数字更重大。所有8个错误候选都来自三个具有真正模糊规范的问题。检查器和修复器是同一个模型阅读同一个模糊句子——所以检查器自己编写的测试编码了错误代码已经具有的一样误解,而错误代码完全清除了它们。基于意见的检查器通过普遍犹豫“赢得”了比较,这也正是它们错误拒绝四到五倍更多正确代码的缘由。
运行测试不是错误接受的免费通行证。它是一种不同类型的证据——带有特定输入、预期值和实际值,而不是一种感觉。这就是为什么它的3%错误拒绝率可以作为真正的关卡使用。一个拒绝15%良好工作的检查器会在拯救你免受错误合并之前将你埋在重试中。
5、将两个部分组合在一起
最终组合第一运行反馈循环;如果失败,它会采样新的候选,并让验证器——而不是模型的信心——决定提交什么,如果没有达到标准,则有明确的放弃路径。
在构建上述任何内容时未接触的MBPP+保留切片上进行评估,独立于验证器的决定根据隐藏测试进行评分:
single-shot 29/35 82.9%
loop only 34/35 97.1%
loop + verifier 34/35 97.1%
single-shot 29/35 82.9%
loop only 34/35 97.1%
loop + verifier 34/35 97.1%
循环基本上完成了所有工作:+14.2分,六个单次失败中恢复了五个,大约额外十次API调用。验证器阶段仅在循环无法解决的一个问题上触发——它的第一个采样候选清除了自己编写的测试,但依旧未能通过隐藏测试。一个实时的错误接受,正是上表预测的失败模式。它被捕获是由于运行器根据验证器从未见过的真相来源对提交进行评分。如果验证器自己的判断是最终决定,那个错误就会被发布。
此阶段的总成本:45次调用,大约十三美分。
6、这里哪里会出错
当目标真正可测试时,这有效——具有隐藏测试用例的函数、验证或不验证的模式、循环无法绕过的oracle。当规范本身模糊时,这无效,由于同模型检查器继承了生成器一样的误解。如果你遇到这种情况,修复不是更机智的检查器——而是更清晰的规范,或来自完全不同模型系列的独立oracle。
我还只在小型、独立的MBPP+函数上测试了这一点。这里没有涉及具有跨文件依赖关系的大型代码库,我也没有构建或测试工作树隔离以并行运行多个循环——这是一个真正的问题,只是不是本实验测量的问题。
而这个循环止步于“已验证”。它不决定是自动应用更改还是将其升级给人类,这是一个单独的、更难的问题,一旦其中任何内容触及具有真正写访问权限的东西。
7、从这里开始
从评分器开始。编写自测试——已知良好、已知错误、无限循环——在编写一行循环逻辑之前。这是一个五分钟的脚本,它是你和虚构头条数字之间的唯一障碍。
如果你有一个甚至只有少量单元测试的代码库,接下来连接反馈循环,并从第一天开始构建通用重试控制组。在你看到它击败“再试一次”之前不要信任改善。
然后构建第二个检查器并将其放在第一个旁边。当你的错误接受表与理论告知你的预期不一致时——它很可能会——你就会理解为什么循环比底层的模型更重大。
原文链接:循环工程简明教程 – 汇智网





