site logo

Marico's space

设计你自己的多AI编程流水线:可移植参考架构

AI技术与应用 2026-07-09 15:02:10 7

核心问题从来不是"更多智能体就能更聪明"。关键在于:不能让同一个智能体同时扮演正确性的"作者"和"裁判"。这篇文章是写给想在自己技术栈上构建这套东西的人——一个可移植的参考架构,拿走就能用,不是我的机器日志。

你大概有过这种经历:让一个编程智能体从规划一路做到实现。能用——但你心里清楚风险:同一个智能体定义了什么是"正确",又自己判断是否达到了正确。绿灯是自己给的,diff审查也是自己做。绝大多数时候没问题——直到那一次,它报告"成功"却跑在错误的目录里,或者某个迁移被守卫语句悄悄跳过了,测试却依然绿着。

把工作分给多个AI,不会让你拥有"三个大脑"。它让你打破那个自我背书。这篇文章不是教你安装某个特定工具;而是一个可移植到你自己技术栈上的参考架构——哪些角色、哪些契约、哪些检查点。末尾有个案例框讲了我自己是怎么连接的,但那只是一种实现方式,不是重点。

核心主张:没有任何智能体能同时定义正确性、又判断自己是否正确。因此每个产物——计划、测试、"发现"、那个"成功了"、那个绿色构建——都必须独立对照可验证的基准事实来检查(源代码、冻结的测试、schema、真实数据库状态),而不是因为听起来"很有信心"就信任它。

01 · 不变式:每个AI输出都是提议,不是定论

先把最容易被跳过的部分敲定:在这条流水线里,智能体产出的所有东西都是提议,不是结论。计划是提议。测试是提议。"这里有个bug"是提议。甚至绿色构建也是提议。它们共同的特征:每个都必须对照不是LLM的东西来检查——代码真的长那样吗?这个API真的存在吗?这个测试失败的原因真的是它应该失败的原因吗?diff真的只改动了应该改动的地方吗?

这个不变式之所以重要,是因为它甚至覆盖了"验证者也是LLM"的情况。你的验证器——不管是什么模型——不是因为被冠以"审查者"的角色就可信。它可信是因为它对照的东西是可验证的。拿走那个可验证的目标,整个系统就会塌陷成"一群LLM互相说服"——那才是真正无底的状态。

02 · 参考架构:四个角色,一条铁律

把流水线想象成一套职责,不是一套品牌。一个工具可以承担多个角色。但有一条铁律不能打破:

在单次运行中,一个工具不能既产出一个断言、又充当该断言的最终裁判。作者和裁判必须是不同的运行(最好是不同的模型/不同的上下文)。

流程结构(→表示"进入下一步";检查点位于步骤之间):

  1. 规划者 → 产出变更计划+测试计划(哪些文件、边界、哪些不测试)。
  2. [循环 ①] 规划者 ⇄ 计划验证者 → 对照现有代码检查计划引用的类型/API/schema;循环直到双方达成一致。
  3. 实现者 → 依据冻结的计划编写测试和实现,让测试变绿。
  4. [关卡 ②] 测试验证 + 最终验证 → 测试在冻结成契约前要审查;实现后运行测试、审查diff、检查副作用——不要信任实现者的自我报告。
  5. 发现(发现循环) → 审计已在运行的代码,提出发现——同样要对照源码检查后才能算数。

三个检查点:①计划循环 · ②实现关卡(测试审查+最终验证)· 发现循环由审计者运行。每个都是同一原则——对照可验证目标检查提议——落在不同的地方。

角色 → 职责 → 禁止行为

这张表是整篇文章最具可移植性的东西。按职责把你的工具(Cursor、Aider、某个CLI、某个智能体框架)映射上去,不是按品牌:

角色 职责 检查对象 禁止行为
规划者 产出具体的变更计划+测试计划:命名文件、API、假设、边界、以及哪些不测试 —(这是提议来源) 编写生产代码
计划验证者 在任何工作开始前,对照现有代码检查计划引用的类型/模块/schema/命令路径——确认它引用的东西确实存在 现有源码、schema 橡皮图章式盖章
实现者 依据冻结的计划编写测试和实现;优化目标是执行,不是判断 冻结的计划 实现开始后编辑冻结的测试
测试验证者 测试成为契约前,检查它们是否表达了计划、是否因可诊断的原因失败 计划、测试语义 放行信息量不足的RED
最终验证者 运行测试、审查diff、检查数据库/schema/运行时副作用 测试结果、diff、真实状态 信任实现者报告的"成功"
发现智能体 审计已在运行的代码,提出发现——它的输出不是事实,进入发现循环 源码、真实行为 把"发现"当定论
适配器/封装层 稳定化工具调用:吸收CLI变化、模型特定参数、沙箱、工作目录、上下文打包、重试规则、输出规范化 —(基础设施) 假装它保证了正确性

03 · 三个检查点:不变式落地的具体位置

"每个产物都要对照可验证目标检查"落地在三个具体地方。如果你做过严格的TDD,前两个已经以萌芽形式存在了;第三个是人们容易漏掉的:

① 计划循环——对照现有代码检查,不是尚未编写的实现。一个常被混淆的点:对于新功能,计划在前,代码在后。规划时没有实现可以对比;能检查的只是当前状态是否如计划所引用——这个类型在吗?这个模块的接口真的是那个形状吗?(只有修复现有代码时才有旧目标可对比。)而且计划不是"一个AI交办、另一个AI一次性审查"——而是两个AI迭代,直到双方都同意。规划者可以是作者,但是否正确必须由另一个角色验证,双方点头后才能算数。

② 实现关卡——冻结测试作为契约,让实现去追赶它们。测试在成为契约前要审查(它们是否反映了计划?是否因可诊断的原因失败?)。一旦冻结,实现者只有一件事:让冻结的测试通过——只改实现,不改测试。最终验证运行测试并审查diff——但不信任实现者说"成功了"。这是一个关卡,不是循环:失败了就走回头路。

③ 发现循环——这条流水线也能发现已在运行的代码是错的。很多人只把它看成"让新代码正确"的工具。但同样的不变式对现有代码也成立:派一个独立的、对抗性的角色去看那些看起来能工作的代码,问一句"它真的正确吗?"——纯粹是诊断性的。它挖出来的东西不是新写的bug;是一直在运行的bug(见末尾案例)。它的"发现"同样是提议——对照源码检查后才能算数。

04 · 映射到你的技术栈

你不需要我的工具。你需要回答几个设计问题:

  • 谁来做规划者?选一个擅长读懂你的代码库并产出结构化计划的模型/工具。它只输出计划,不碰生产代码。
  • 谁来做计划验证者?一个能运行工具、读源码、检查引用的不同角色(通常是你的主要智能体环境)。它和规划者循环迭代直到双方一致。
  • 你的契约是什么?冻结的测试是首选。没有能充当契约的测试,基础就不稳固。
  • 谁来做最终验证者?一个能运行测试、审查diff、检查真实副作用的角色——而且不是写实现的那次运行。
  • 谁负责调用层?见下一节——这通常决定了"我能运行它"vs"别人也能照着运行它"的区别。

拿上面的角色表,把每行填上你自己的工具,这就是你的"职责分配"。唯一不能打破的一条:单次运行不能既是断言的作者,又是它的最终裁判。

05 · 把"工具调用"层分离出来——这是架构决策,不是打杂

这是最容易被认为是纯管道工作而被忽视的部分,但实际上它决定生死。你选的CLI/智能体工具都是不断变化的移动靶:参数变、模型参数变、沙箱模式变。手把手记住然后让编排器"凭记忆"调用,迟早会遇到"参数漂移烧掉整个回合,推理还没开始就结束了,一个文件都没写"——这跟模型有多聪明一点关系都没有。纯粹是调用层的问题。

解决办法不是"编排器多记几个参数"。而是在适配层里封装调用,固定每个工具的正确当前用法——参数、运行模式、工作目录、上下文打包、重试规则、输出规范化。你的上层逻辑调用适配器,从不直接调用CLI。这一层不保证正确性(别当它是魔法);它只保证调用是稳定的——而这常常正是让流水线从"我能跑通"变成"别人跟着文档也能跑通"的关键一步。

06 · 什么时候跑完整流程,什么时候不跑

实话实说:对于小改动,跑完整的六步纯粹是overhead。仪式感要和变更规模成正比。一个守卫语句、一行提示——拆分的来回成本比它能catch的风险还高。自己干,快得多。

值得跑完整流程的:高风险、横切、持久、改schema、难回滚的工作。判断标准不是"大小",是"出错的代价"vs"来回的成本"。

07 · 架构师会拍砖的地方(预先回答)

"这不是用速度换正确性吗?"不是。额外的是那几轮测试和跨角色审查——但那个成本本来就是任何打算维护的代码都欠着的;流水线只是让你现在就付,而不是赊账。而且这根本不是速度交换:正是因为实现者比你最强的模型写得准确度低,你才用测试给它围上更密的护栏——实现者越不稳定,周围的测试越紧。至于整体有多快,不是这里的议题。

"为什么不直接用更强的模型?"更强的模型降低错误率,但消除不了自我验证的风险——它仍然是作者和裁判。这条流水线解决的是后者,不是前者。

"LLM不可预测——怎么保证可靠性?"可靠的部分从来不是LLM;是契约:冻结的测试、源码检查、diff审查、schema检查、可复现的命令。LLM提议;契约裁判。

"这不是过度工程吗?"如果你对每个改动都跑完整流程,是的。所以认真对待§06的规则——小改动跳过它。

08 · 你真的会碰到的失败模式(摊开说)

这不是在秀"从不失败"的肌肉——恰恰相反。它的价值在于能catch住这些问题

  • 实现者谎报成功:报告"成功"却跑在错误的目录,什么都没写。信任这个报告你就提交了一个空改动。→ 最终验证必须自己运行测试、审查diff。
  • 测试全绿,但某个改动被悄悄跳过:迁移的守卫语句误触发,整个扩展被悄悄丢弃,本地测试还是绿的。→ 对照真实schema/数据库状态检查,不只是看绿灯。
  • 审计者的"发现"是误报:它自信地报告一个"高危问题"——因为只读到了一半上下文,没发现守卫其实在别处。→ 发现要对照源码检查;子智能体的发现范围精确到它读到的东西
  • 调用层在推理前就崩了:参数漂移烧掉整个回合,零文件产出。→ 那是适配器层的问题,不是方法的问题。

采纳前检查清单

  • [ ] 我们有能充当契约的规格吗?(可冻结的测试/明确的断言)
  • [ ] 测试能诊断性失败吗?(每个红都有各自的原因,不是笼统的NotImplemented)
  • [ ] 验证者真的能读源码、diff、真实副作用吗?(不只是看智能体的报告)
  • [ ] 作者和裁判是不同的运行吗?(一次运行不能既提出断言又判断它)
  • [ ] 工具调用被封装了吗?(CLI变了不用重新记参数)
  • [ ] 我们有明确的"小改动→跳过仪式"规则吗?

案例 · 我是怎么连接的(只是一种实现方式)

Codex规划 · Claude Code验证+接受 · Grok实现。

规划者 = Codex(逐任务、测试优先的计划);计划验证者 + 最终验证者 = Claude Code(对照现有代码检查计划、运行测试、审查diff);实现者 = Grok(按冻结的计划写测试+实现)。两个CLI都不是裸驱——它们是移动靶,我一直在用错,现在也还在——所以我通过Claude Code插件(适配层)调用每个工具,让插件固定"正确的当前用法"。这是调用层的问题,和方法本身无关。

样本边界:跨几个仓库(Rust + Turso CLI、Rust + Python工具、Zig项目),大约十几条观察——不是基准测试,不是统计结果。每个仓库的运行次数不多。

它发现的真实bug(全在已在运行的代码里):地图悄悄渲染成空白(活动没关联坐标,没人报警);一个命令在没传字段时写入零行却打印"✅ updated";一个导入在所有行都被跳过时打印"✅ saved"。没有一个是新写的失误——是审计+验证才把它们逼出来的。

如果只记住一句话:多AI不是为了更聪明——是为了让定义正确的人和判断正确的人不是同一个。其他一切——你怎么拆分角色、契约放哪、谁管调用——都只是那句话的具体落地。复制上面的角色表和检查清单,换成你自己的工具,你就有自己的流水线了。