§05生成器与评判器:为什么写代码的AI不能给自己打分
Generator and Evaluator
让写代码的agent评价自己的产出,它多半会夸。这一章讲怎么在循环里放一个独立的评判器,以及Claude Code和Codex已经把它做成了哪个命令。
它总夸自己
你让一个agent写完一段代码,再让它评价自己写得怎么样。它会怎么回答?
Anthropic Labs团队的Prithvi Rajasekaran在一篇官方工程博客里写过他的观察,场景是做长时间运行的应用:
原文:when asked to evaluate work they've produced, agents tend to respond by confidently praising the work—even when, to a human observer, the quality is obviously mediocre.
让agent评价自己产出的东西,它往往会自信地夸一通,哪怕在人看来质量明显很一般。
写代码的那个上下文里,已经塞满了它为什么这么写的理由。它回头看自己的代码,看到的是一路推导过来的自我说服。自己写的文章回头读,也是越读越顺。
放到loop里,这个毛病会被放大。loop值钱,是因为它能无人值守地跑很多轮。如果每轮这一版行不行都由刚写完的agent自己拍板,跑得越久,攒下的自我表扬越多,离真实质量越远。
改一个怀疑论者,比改一个谦虚的作者容易
Rajasekaran的结论是:
原文:tuning a standalone evaluator to be skeptical turns out to be far more tractable than making a generator critical of its own work.
调一个独立的评判器,让它去怀疑,比让生成器自我批判要容易得多。
在prompt里加一句请严格一点,作者也跳不出自己的视角。换一个agent,给它完全不同的指令,让它从零开始看这段代码,它没参与写,就没有那套自我说服。
这套思路他说借鉴的是GAN:
原文:I designed a multi-agent structure with a generator and evaluator agent.
生成对抗网络(GAN)里,一个网络负责造,一个网络负责挑刺,两个对着干,造的那个越来越像真的。搬到agent上,就是一个generator写,一个evaluator审,结构上把写和判断写得好不好分开。Addy那句「写代码的模型给自己批作业太客气」,说的是同一件事(原话见§04零件五)。
评判器要会动手,不只是会读
光换个agent还不够。evaluator如果只是把代码读一遍,它判断的还是代码看起来对不对,没碰到跑起来对不对。
Rajasekaran给evaluator接上了Playwright MCP,让它像真人QA那样去用这个东西。前端实验里,评判器自己导航页面、截图;后面的全栈实验里,它像用户一样把应用点一遍,测UI功能、API端点和数据库状态。
判断的依据就从读代码觉得没问题,变成了点过按钮、看过页面、截了图。
写evaluator指令时,社区里常见一种往最坏处校准的说法:默认「这段代码是坏的,除非被证明能跑」
(assume the code is broken until proven otherwise)。这句是社区里的说法,不是官方的,方向和Rajasekaran一致:评判器的默认态度是怀疑。
落到产品:/goal把评判器用在了停止条件上
Claude Code里有一个命令把这个结构做成了产品原语,叫/goal,2.1.139(2026年5月)加进来的。
用法是给agent一个条件,让它一直跑到条件满足为止。关键在由谁来判定满足了没有。官方文档的写法是:
官方文档:After each turn, a model checks whether the condition holds. If the model judges it not yet met, Claude starts another turn instead of returning control to you. … completion is decided by a fresh model rather than the one doing the work.
每跑完一轮,一个模型来检查条件成立没有。它判断还没满足,Claude就再跑一轮,不把控制权交还给你。是否完成,由一个全新的模型判定。这个模型默认是你配置里那个小而快的模型,底层实现是一个只在本会话生效、基于prompt的Stop hook。
10月我在一个小仓库里实际跑了一次。仓库是让agent搭的练手仓库,里面只有一个calc.py ,留了两个bug:
加法写成了减法,判断偶数写成了判断奇数。测试文件有三条测试,跑起来两条失败。然后我输入:
/goal python3 -m unittest -v 显示全部测试通过(OK),并且只改 calc.py、不改测试文件,or stop after 6 turns Claude Code 2.1.287,模型Sonnet 5.5。回车后先回显一行Goal set,右下角出现◎ /goal active和计时,它立刻开始第一轮:先跑测试看现状。中间空白的终端行我裁掉了。
24秒,一轮就结束了。它改了calc.py 里的两处,跑测试,3条全过,然后汇报git status里只有calc.py是被追踪文件的改动。最后一行是评判器给的结论:Goal achieved。
Goal achieved (24s · 1 turn · 731 tokens)这一行出自评判器。
按Ctrl+O打开详细记录,能看到评判器给出的理由:
测试输出里有Ran 3 tests … OK,git status里只有M calc.py。两个条件,它各找了一条证据。
理由的第一个词是Transcript。评判器自己不跑命令、不读文件,它只能看干活的agent在对话里亮出来的东西。文档原话是「It doesn't run commands or read files independently, so write the condition as something Claude's own output can demonstrate」。
所以条件要写成对话里能被证明的样子。我写了只改calc.py,它就去跑了一次git status把证据摆出来;你要是写「代码质量要好」,评判器在对话里找不到能证明它的东西,就只能凭感觉。
文档里还写了几条:
三种判定:没满足(继续跑)、满足(清掉目标,记一条achieved)、不可能(评判器认定条件永远达不到,自动清掉并写明原因)。
给它一个刹车:在条件里直接写or stop after 20 turns 这样的轮数或时间条款,评判器每轮按对话判断。我上面那条写的是6轮。
手动停:/goal clear ,别名stop、off、reset、none、cancel都行;条件最长4,000字符;恢复会话时还没达成的目标会跟着恢复。
评判的花费很小:我这次实测的/usage里,Sonnet干活花了0.59美元,小模型那一行是0.054美元。小模型那一行里不只有评判器,Claude Code的一些后台杂活也走它,单独拆不出来。
这次/goal加/loop实测的整场会话账单。Total cost是按API价折算的参考数,我用的是订阅。
maker-checker(生产者-检查者)是银行的老规矩:大额转账,录入的人和复核的人得是两个人。经手的人会信任自己的操作,复核才有意义。/goal把这条规矩塞进了agent的循环里,每一轮干完,都得过一道不是它自己的检查。
Codex也有/goal,命令名一样。它是0.128.0(2026年4月30日)加进来的,现在支持设置、查看、edit、pause、resume、clear,我本机跑codex features list ,goals一项标着stable。
Codex CLI 0.163.0里直接输入/goal,会打印用法:设置目标,或者clear、edit、pause、resume。
别把/loop当成这个。/loop是按时间间隔重跑,属于调度,跟跑到条件满足为止是两码事。