构建一个小模型也能跑完的智能体循环

Builder.io Blog 2026-10-02T05:53:47.922569

我们让一个小而便宜的编程智能体完成了一件看起来超出它能力范围的工作。接着我们又从零搭了第二个循环,用一个你自己也能试的小任务,来看看到底哪些部分才是关键。

智能体循环(agent loop)让编程智能体反复尝试同一个任务,一次接一次,直到某个东西判定它已经完成。循环本身很简单,大部分功夫都在那个「某个东西」上:一个智能体能自己跑的检查,用来判断每次尝试是否离目标更近。如果你试过 Ralph loop 或 /goal,你已经见过智能体一直干下去的那部分。Geoffrey Huntley 把另一半叫做「背压」(back pressure):测试、构建、类型检查,以及任何能拒绝一次糟糕尝试的东西。我们这两个任务都没有现成的测试套件,所以背压得自己搭。

第一个任务是修复 Agent-Native 里的 Figma 和幻灯片导入。Agent-Native 是我们免费开源的框架和应用集合。有一张幻灯片经过导入器之后坏得很彻底,88% 的像素跟原图对不上。用一个周末,OpenAI 的一个较小模型把这个数字降到了 2%。下面就来完整走一遍这个循环。

第一个循环:修好导入功能

我们的导入功能一开始很糟。用户想把手里的 Figma 文件导入 Agent-Native Design,把演示文稿导入 Agent-Native Slides。结果图表散成了一堆零散图形,图标不见了,有一张幻灯片导进来直接变成一块黑色矩形。于是我们看结果、描述问题、让智能体去修,然后再看一遍。这个循环的速度上限,就是人看幻灯片的速度;而且智能体只知道有人记得告诉它的那些问题。我们自己就是验证器,一遍又一遍地在聊天框里敲「还是不对,标题又移位了」。

把「看着对了」变成一个数字

比较视觉效果感觉像是一种主观判断,很难写成单元测试或者浏览器测试。但每个导入的文件本来就自带正确答案:原文件。所以我们把原文件和转换后的版本都渲染出来,用像素差异(pixel diff)做对比。像素差异就是把两张图放一起,标出每一个不同的像素。pixelmatch 和 ImageMagick 的 compare 这类工具开箱就能做这件事。

我们的检查机制给智能体提供了两样东西:一个数字,和一张用红色标出不一致处的图片。数字告诉智能体某次改动有没有帮助,红色标记告诉它下一步该看哪里。

检验检查机制本身

智能体只要盯着数字看,就会相信数字,所以数字必须准确。一开始并不准确。字体没能加载,导致某次导出看起来偏了 17%,而实际差异只有 0.19%。我们翻看那些运行记录,在检查机制里找出了四个类似的 bug:「保真度数字是对转换器的一种声称,所以测试框架至少要和对它评分的对象一样可靠。」

我们还得决定哪些差异不算数。字体渲染的差别不值得追,就告诉智能体忽略它们。

检查还需要真实样本。如果只用几个规整的样例,智能体很容易蒙混过关,所以我们让它收集了各种社区文件,涵盖 .fig 文件、Figma API、PowerPoint、PDF 和 Google Slides。每次改动都拿整个集合来评判。笔记里记录了两处修复,「让框架变好了,但语料库变差了」,最后都回滚了。

让小模型持续打磨

检查机制就位后,循环还需要一个愿意不停尝试的模型。它不必是最好的模型。一个能衡量每次尝试的智能体,不需要对自己产出的东西有非凡的判断力。它只需要做出改动、跑检查、看数字有没有下降,然后重试。

我们用的是 Luna,OpenAI 较小的模型之一,通过 ChatGPT Pro 套餐拉到最高设置。粗略估计,它能达到接近 Opus 级的效果,价格却只有十分之一到二十分之一,而且在这个套餐下可以连续跑好几天不触发限制。我们让它跑了一整个周末。

一开始差异在 20%、40% 甚至 50% 的,后来都降到了个位数,常常只有百分之零点几。导入导出问题本来是我们收到最多的反馈,每天几十条。循环上线后,这些反馈不再来了,后来人们发现的边缘情况也很好修。

一个被告知「不达目的不罢休」的智能体就会一直跑下去,Codex 里的 /goal 或 Claude Code 也是一样。

第二个循环:从截图重建页面

第一个导入循环花了 Luna 一个周末,但真正费脑子的部分,在她动手之前就已经完成了。为了把这一点看清楚,我们从头搭了第二个循环,挑了一个谁都跑得动的任务:只凭截图,把一篇博客文章的开头部分重建成静态 HTML 和 CSS 页面。智能体拿到三种屏幕宽度下的截图、字体和图片,还有页面文字。检查环节会把它的页面放进无头浏览器渲染,再逐张和截图比对。

这一次,我们故意把活儿分开了:由一个强模型来搭这个循环,再让一个小模型去跑它。

搭建检查环节

我们用 Claude 一起把这个提示词捋了一遍:

我想让一个更小的智能体一直做这件事,直到做完:[描述任务]

在任何人开始动手修之前,先把它要跑的循环搭起来:

把它写成一份简报,让小一点的智能体不用问问题就能照着做。Claude 搭好了检查、给 Luna 的一页简报,以及一组用来测试检查的页面:原始页面渲染两次、我们已知有问题的页面,以及我们能接受的页面。这个检查两次都没通过那项测试。第一个版本把不匹配的像素算成整张截图的占比。页面背景是深色的,所以即使页面上什么都没有,大多数像素也是匹配的。空白页也能拿到 3% 到 5%。我们把评分改成只统计可见内容,空白页就升到了 57% 或更高。第二个版本又太严格了。在最窄宽度下,把整个页面下移一个像素就能拿到 31%。没人会注意到一个像素的位移,所以我们让检查放过这种情况。在那之后,分数和肉眼看到的情况对上了:每个宽度下,通过线都是可见内容的 2%,差不多就是一个按钮偏了 4 个像素会得到的结果。我们还留了一些例子没给。Luna 用的是 390、768 和 1280 像素宽的截图。我们把 1024 和 1440 留给自己,这样才能判断它是真的解决了页面,还是只是记住了这些截图。事实证明,这很重要。跑这个循环时,Luna 收到了这样一条提示:按照 [file] 里的循环简报来做。一次只改一处,运行检查,并在下一次尝试前读它的输出。不要改检查、例子或容差。如果你觉得其中某个不对,就停下来并解释为什么。

继续做,直到检查在每个示例上都通过,或者你用完了预算,

要么卡住不动。跑一遍留出集,汇报结果。提示词里写明这个检查脚本不许碰。一个拼命想降低数字的智能体,改测量标准往往比改代码省事。四轮下来,Luna 从没碰过它。每轮结束,我们先看 Luna 交出来的东西,再看分数。用可视化复盘的图过一遍,这一步会快很多。

第一轮。我们给 Luna 用高配置,预算 90 分钟。它第一次尝试拿到 41.5%,34 分钟后就卡在 16.7%。但它的页面看起来和原版几乎一模一样。循环卡住的时候,人很容易怪模型不行,换个更大的模型。这里换也没用。是检查出错了:我们的一个像素容差只容忍整像素的偏移,所以偏了半个像素的文字照样算通过。修好容差之后,Luna 的页面直接掉到 2% 左右。在 1024 像素宽这个 Luna 从没见过的宽度下,它的页面过早切换到完整菜单,还丢掉了左边距。

第二轮。我们在任务说明里加了一句话:页面必须在两端之间的每个宽度下都站得住。Luna 九分钟内通过了三个检查宽度,留出集还是同样地挂了。那句话它读了,但没法验证,于是它只改了检查能看到的那些。凡是重要的事,都必须写进检查里,光写在任务说明里不够。

第三轮。我们把 1024 和 1440 挪进了检查里。可这两个宽度一旦被用来做决策,就谈不上真正被藏着了,所以我们又另设了三个新宽度。Luna 五分钟通过了全部五个检查宽度,也挂了全部三个新宽度。这是四轮里通过最快的一次,也是结果最差的一次。通过快,恰恰是要更仔细查的理由。这次的毛病出在 CSS 里。它只在 1024 到 1100 像素之间显示菜单按钮,刚好盖住它唯一能看到的那个宽度。它还把某段文字的字距悄悄调了 0.03 像素,好让文字在 390 宽度下按它想要的方式换行。

第四轮。

Cheng Lou 做 Pretext 时,是拿浏览器本身当参照物来验证的:“把浏览器的真实渲染结果展示给 Claude Code 和 Codex,让它们在每一个重要的容器宽度下测量并反复调整,整个过程持续了好几周。”我们的校验同样覆盖了每一个重要宽度。给了它 28 个宽度,另外藏了 5 个。Luna 花了 59 分钟、跑了 55 次校验,才把 28 个全部通过,藏起来的那 5 个里也过了 3 个。也就是说,这个页面在几乎所有设备上看起来都没问题。但它的样式表就是另一回事了:里面有 21 条媒体查询,很多只覆盖 25 到 50 像素的区间。换成人来写,几条流式规则就够了。可校验测的是像素,Luna 自然就照着像素去凑。如果我们在意代码是怎么写的——做真实项目时确实会在意——那就得单独为它设计一套校验。四轮下来,Luna 花在反复打磨上的时间不到两小时。每一轮之间的跃升,靠的都是换校验方式,而不是换模型。一个便宜的模型能做出这么多活,只是因为它需要的边界被划清楚了。

能学到什么

两个循环教给我们的东西是一样的。如果你也想搭一个,我们建议把力气花在这几个地方:

找一个你手里已经有的正确答案。 大多数难题都有这么个东西:旧实现的输出、浏览器自身的行为、规范里的示例、线上录下来的流量,或者一个你想超越的基准。都是现成的参照物。

先验证校验本身,再去信任它。 把同一个东西渲染两次,确认它得分是零。再确认你明知很烂的结果确实得低分,你能接受的结果确实得高分。也要把“什么不算数”写清楚,否则智能体会在无关的噪声上耗掉几个小时。

用真实样本,并且留一部分藏起来。 每一次改动都要拿整套样本来评判。藏起来的那部分能告诉你:智能体到底是真把问题解决了,还是只学会了手里的这些例子。

把关键的评判标准写进校验里。 智能体只会朝着数字奖励的方向走,你怎么打分,它就怎么优化。

让校验保持不可动摇。 告诉智能体校验是固定的;如果它觉得校验有问题,就停下来解释,而不是去改校验。

把搭架子的活交给强模型,把反复打磨的活交给小模型。 上面那两个提示词换个任务也能用,把你的任务替换进去就行。

先看输出,再看分数。 卡住不动,可能是校验写错了,而不是模型不行。

一次快速通过,可能意味着 agent 抄了近路。所以这个检查要留着。把最终得分设成测试里的上限,以后某次改动一旦让效果变差,测试就会失败。检查也不一定非得靠眼睛看。P90 响应时间、错误率、能核实的用户行为、能自动化的浏览器操作流程,还有最普通的单元测试,都能给 agent 一个数字,或者一个通过/不通过的结果,让它不用问你就能照着干。如果你搭了一条自动接 bug 报告的流水线,这些检查就是 agent 判断「修完了」的依据。

那就挑一件你一直在手动检查的事:要把它量化,得付出什么?

查看原文