一个行之有效的 AI 结对编程的确定性工作流
AI 编码器功能强大,但若没有关卡就会漂移。这里是封装它的“计划、构建、验证、交付”流水线,带有状态文件和计划冻结。
将一个编码任务交给 AI 助手并附上“搞定一切”的指令,其失败的方式是可以预见的。模型是很有能力的,在它面前的狭窄领域里,其能力往往超出你的预期,但它不知道工作的边界在哪里。它会在步骤之间丢失上下文。它会偏离最初的目标,去修复它注意到的其他问题。它会编写看起来正确的代码,并在编写和部署之间不做任何测试就直接发布。当出现问题时,你无法判断是最近的四十次编辑中的哪一次导致的,也无法干净地撤销。
解决方案不是一个更好的提示(prompt),而是一个约束框架(harness)。你将 AI 的工作包裹在一个由人类设计和控制的确定性流水线中:审查、实现、验证、记录、部署,每个阶段都有一个明确的门控,在失败时会停止运行。模型在每个阶段内部提供创造力,而流水线则在其周围提供纪律性。这篇文章描述了如何构建这个约束框架,为什么每个阶段都需要一个门控,以及在何种特定情况下完全跳过它是正确的选择。
为何无人引导的 AI 会漂移
其失败模式相当一致,足以命名。理解这些模式,你才能知道该构建何种门控。
首先是上下文丢失。AI 助手在有限的近期对话窗口内工作。在执行长任务时,这个窗口会被填满,最早的决策会从中脱落。最初同意了一个狭窄范围的模型,在深入代码时,已不再记得那个范围。当它与早前的决策相矛盾时,它并不是在对你说谎。它的确是再也看不见那个决策了。
其次是范围蔓延。你让模型修复一个错误,它通常会注意到三个可以改进的相邻问题,并着手改进它们,因为每个改进在局部看来都是合理的。每一项单独的更改都是站得住脚的。结果是,你得到的差异(diff)是你要求大小的四倍,触及了你从未打算打开的文件,而真正的修复则深埋其中。
第三是缺失验证,这也是伤害最大的。如果置之不理,AI 会编写代码,并根据代码“看起来”正确来报告成功,而不是根据代码“运行起来”正确。“看起来正确”和“实际正确”之间的差异,恰恰是那些有意思的错误(bug)所在之处:差一错误、错误的过滤器、一个在本地成立但在部署环境中失效的假设。如果没有一个能实际执行代码的门控,模型的自信就与现实脱节了。
可逆性差将其他问题联系在一起。当工作成果以一个庞大且未经区分的变更形式交付,并且没有记录决策内容或原因时,回滚就意味着解开一个死结。你无法撤销一个你从未记录下来的决定。
这些都不是不信任模型原始能力的理由。它们是你不应信任非结构化授权的理由。这种能力是真实存在的。指引和门控才能将其转化为你能够负责的、已交付的工作。
流水线的五个阶段
这个工具链是一系列固定的阶段。这个顺序并非装饰性的。每个阶段都会产出下一个阶段所需的输入,并且每个阶段都以一个关卡结束,必须通过该关卡才能继续运行。
| 阶段 | AI 的工作 | 关卡的检查项 | 失败时 |
|---|---|---|---|
| 审查 | 读取上下文,编写计划,指出风险 | 计划具体,范围有界,人类批准 | 停止,修订计划 |
| 实现 | 仅根据已确定的计划编写代码 | 代码编译通过,类型检查通过 | 停止,修复或回滚 |
| 验证 | 运行测试,检查不变量 | 测试通过,不变量成立 | 停止,不继续部署 |
| 归档 | 记录变更内容和原因 | 变更日志与实际差异一致 | 停止,进行核对 |
| 部署 | 通过常规发布路径发布 | 部署后健康检查通过 | 回滚 |
“审查”是进行思考的地方,也是发现错误成本最低的环节。模型会读取相关代码,写下它打算做什么,并列出可能出错的地方。人类会阅读该计划,然后批准或将其发回。批准一个计划远比审查一个完成后的 diff 成本更低,因为计划简短而 diff 冗长,此时发现一个错误的计划只会花费几分钟,而不是导致一次回滚。
“实现”阶段将已批准的计划转化为代码,并且只转化已批准的计划。“验证”是将此流水线与无指导的委托区分开来的关卡:它会执行代码,如果测试失败则拒绝继续。“归档”会留下持久的记录,以便下一个人或下一个 AI 会话可以重现其推理过程。“部署”会通过与人类使用的相同发布路径进行发布,进行相同的部署后检查,并在失败时回滚,而不是留下半部署状态。
状态存在于文件中,而非模型的内存中
AI 助手的工作内存是易失的。关闭会话、超出上下文窗口,或遇到强制重启的错误,模型所持有的所有内容都会消失。如果你的流水线的状态只存在于该内存中,重置就意味着从头开始,而在一个部署了一半的任务上从头开始是危险的。
因此,流水线将其状态写入文件。当前阶段、已做出的决策、已完成的项目、剩余的项目,所有这些都存在于磁盘上,并随着工作的进行而更新。模型在每一步开始时读取此状态,并在结束时将其写入。易失的上下文成为持久状态的缓存,而非事实的来源。
pipeline-state:
task: "add rate limiting to the public API"
stage: verify # review -> implement -> verify -> document -> deploy
plan_frozen: true
decisions:
- "token bucket, 100 req/min per key, chosen over sliding window for simplicity"
- "limit stored in existing cache layer, no new dependency"
progress:
- [x] middleware written
- [x] unit tests for bucket refill
- [ ] integration test under concurrent load
failures:
- none
回报会在最糟糕的时刻显现出来。一个会话在任务中途死掉。一个新的会话启动,没有任何先前的上下文。它读取状态文件,并确切地知道工作进展到了哪里:计划已冻结,实现已完成,还剩一个验证步骤。它从那里继续,而不是猜测,或者更糟的是,重做已完成的工作并撤销正确的东西。文件中的状态是让 AI 驱动的任务能够在中断中幸存下来的原因,而 AI 驱动的任务特别容易发生这种中断。
计划冻结可阻止范围蔓延
在评审阶段产出经批准的计划后,流水线会将其冻结。冻结意味着范围被锁定:在实施过程中,AI 不得向计划中添加或删除项目。它只构建已达成一致的内容,不多也不少。
这直接针对范围蔓延这一失败模式。若没有冻结,模型注意到并修复邻近问题的习惯就会不受控制,导致差异 (diff) 膨胀。有了冻结,同样的本能就会碰壁。如果在实施过程中,模型判定需要进行另一项更改,它不会直接动手更改。该更改必须作为对计划的明确修订进行记录,以便人类可以查看并批准或拒绝。
规则并非范围永远不能更改。实际工作会揭示意料之外的情况,而拒绝适应本身就是一种失败。规则是范围的更改应该是可见且刻意的,而不是悄无声息、不断累积的。一个带有简短修订记录列表的冻结计划是可审计的。而一个意外增长的未冻结计划,只是一个无人选择的巨大差异 (diff)。
plan (frozen at review):
1. add token-bucket middleware
2. wire it into the public router
3. unit + integration tests
amendment (recorded during implement, approved):
4. add a config flag to disable the limit per environment
reason: staging load tests need it off
流水线所触及的每一项内容都可以追溯到冻结的计划或记录在案的修订。差异 (diff) 中不会出现任何未经同意的内容。
停止规则可防止故障加剧
AI 助手遇到错误时会重试。这通常是件好事。但是,如果一个助手遇到相同的错误并尝试相同的修复方法,它就会陷入循环,有时会持续很长时间,不仅白费力气,而且每次尝试都可能让情况变得更糟。流水线需要有明确的规则来规定何时停止。
第一条规则是重复失败次数的限制。如果同一步骤连续失败两次,流水线就会停止,并将问题上报给人类,而不是进行第三次尝试。两次相同的失败意味着模型没有理解问题,更多的自主尝试也无法凭空产生这种理解。它们只会无效空转。在两次失败后停止,能将一个可能的无限循环转变为一个有界的、有明确交接的循环。
第二条规则是为破坏性操作设置一个单独的关卡。删除数据库、删除表、重写历史、force-pushing,任何难以或无法撤销的操作,都不能像普通步骤那样获得同样的自动批准。它需要人类对该确切操作进行明确、具体的确认。其背后的逻辑是不对称性:一个可逆的错误代价是一次回滚,而一个不可逆的错误可能让你损失无法恢复的数据,因此它前面的关卡必须更加严格。
on step failure:
record failure in state
if failures_of_this_step >= 2:
HALT and surface to human # no third attempt
else:
retry with adjusted approach
before any destructive action:
require explicit human approval for THIS specific action
# never covered by a blanket "yes, proceed"
这些规则共同限制了“爆炸半径”。AI 可以在安全、可逆的中间工作环节自主行动,而流水线则在自主性最危险的两个关键点上强制人类介入:当它卡住时,以及当它即将执行无法撤销的操作时。
为什么包装器必须是确定性的
处于此流水线中心的 AI 本质上是非确定性的。向它发出两次相同的请求,你可能会得到两个不同的答案、两种不同的实现、两种对相同步骤的不同排序。这种可变性不是缺陷。它正是使模型变得有用的创造力的另一面。你需要它探索解决方案空间。
围绕它的流水线则必须相反。各个阶段按固定的顺序运行。关卡每次都应用相同的检查。状态以相同的方式记录在相同的位置。给定相同的状态文件,流水线会从同一点恢复并执行相同的操作。尽管其核心是随机的,但这正是使整个安排值得信赖的原因。
包装器的确定性带来了三个具体的好处。可复现性:你可以重新运行该过程,并使该过程的行为保持一致,即使模型在某个阶段内的输出有所不同。可审计性:因为每个决策和每个阶段转换都被记录下来,所以你可以在事后准确地重建发生了什么以及为什么发生。信任:你可以让模型在一个阶段内以真正的自主性行事,这正是因为有一个可预测的关卡守在出口,在工作成果生效前对其进行检查。
这种分工就是整个理念的核心。AI 负责创造力,即从变化和探索中受益的部分。流水线负责纪律性,即绝不能改变的部分。两者都不能做对方的工作。一个有创造力的流水线将是一片混乱。一个有纪律性的 AI 将不值得使用。
何时可以跳过流水线,直接提问
这套框架并非毫无成本。它有实际的开销:编写计划、维护状态文件、通过每个关卡、记录文档。对于合适的任务,这些开销是值得的。对于不合适的任务,它就成了扼杀一个两分钟工作的官僚主义。
当任务是小型的、一次性的且易于撤销时,可以跳过流水线。在文件中重命名一个变量。草拟一个你会阅读并丢弃的一次性脚本。回答一个关于某段代码如何工作的问题。重新格式化一个代码块。对于这些任务中的任何一个,启动一个包含冻结计划和状态文件的五阶段流水线纯属浪费。直接向模型提问并读取结果即可。犯错的成本不过是看一眼和一次撤销操作。
当工作是可重复的、影响重大的且难以逆转时,尤其当它最终会进行部署时,应使用完整的流水线。任何交付到生产环境的东西。任何触及数据库模式或迁移数据的东西。任何团队成员将在此基础上继续构建的东西。任何你以后需要解释或审计的东西。在这里,开销根本不算开销。它是可用的最廉价的保险,可以防止无指导的委托所带来的那些确切的失败、上下文丢失、范围蔓延、未经核实的部署以及不可逆的变更。
决定性的问题很简单。犯错的代价是什么?如果答案是看一眼和一次撤销操作,那就跳过这套框架,让模型自由发挥。如果答案是一次生产事故、数据丢失,或花一个下午通过无法追踪的差异进行取证考古,那就把它包裹起来。让流程的重量与后果的重量相匹配,在风险允许的范围内,让 AI 尽可能地快速和灵活。