从 Vibe Coding 到契约驱动的 AI 开发
Vibe coding 在项目开始变大之前效果很好。了解如何把强大的 AI 模型、更便宜的模型、契约和代码生成器组合成可扩展的工作流。
从氛围编程到契约驱动的 AI 开发
如何将最昂贵的智能用于不确定性,并把重复性工作转移到契约、生成器和更便宜的模型上。
设想一次看似普通的业务转型。
一家此前销售标准产品的商店,开始提供年度数字许可证。付款入账后,用户应自动获得 365 天的访问权限。重复处理同一个 webhook 不得生成第二份许可证。
你可以把这样的需求交给一个 coding agent。
它会阅读仓库,找到结账、产品、订单和支付相关代码。它会提出数据模型,添加迁移、service、若干 endpoint 和测试。如果模型足够好,很有可能产出可运行的解决方案。
但在这里,“写代码”恰恰是任务中最不有趣的部分。
首先必须决定,许可证在这个系统中究竟是什么。
它是单独的结账类型吗?
它是产品的一种行为吗?
entitlement 是否直接在支付回调中创建?
发生故障后会怎样?
我们如何重试操作?
在哪里保证幂等性?
订单应当指向当前产品,还是保留客户实际购买内容的快照?
只有回答了这些问题,才能确定什么样的代码才有意义。
而这正是我看到的氛围编程的边界。
问题已经不再是 AI 是否能够写代码。它能。
问题在于,每当它接触系统时,我们允许它重新做出多少决策。
“Plan with the best model, execute with a cheaper one” 还不够
一种流行的模型协作策略听起来很合理:
用最好的模型规划。用更便宜的模型执行。
强模型负责设计,便宜模型负责实现。
但这两个阶段之间必须存在比 Markdown 计划更多的东西。
如果我们向第二个模型传递一份包含十几个决策的文档,它仍然需要判断哪些是需求、哪些只是建议,它们如何对应当前 codebase,以及它的自主权边界在哪里。
形式上,它拿到了计划。
实际上,它仍在完成一部分架构工作。
因此,有必要区分通常都被简单称为“编码”的三类任务。
当需要确定系统应当如何运行时,面对的是 决策问题。
当解决方案存在,但找到它需要分析依赖关系、替代方案或错误时,面对的是 推理问题。
当决策已经作出,只需要正确落实其后果时,面对的是 执行问题。
三者都可能最终形成 pull request。
但并非所有任务都需要同样级别的智能。
更好的模型和更高的推理级别解决的是不同问题
这里值得区分两条轴。
一条是模型能力。
另一条是 推理投入——即我们允许某个特定模型为问题投入多少工作。
更大的推理预算可以帮助模型检查更多方案、测试假设,或者验证自己的解决方案。然而,并不存在一条普适规则,认为较弱的模型在 max 下就会等同于较强的模型在 medium 下的表现。
这一点在当前的 OpenAI eval 中体现得很清楚。
在 SWE-Bench Pro 上,GPT-5.6 Sol 达到 64.6%,而 Luna 为 62.7%。差距不大。在 SEC-Bench Pro 上则为 71.2% 对 48.9%。在内部研究调试中,则为 68.3% 对 50.8%。
GPT-6 Astra 从另一个角度展示了类似效应:在 Terminal-Bench 4.0 上达到 57.9%,而 GPT-5.6 Sol 为 37.3%。
这并不意味着存在一个简单的“最适合编程的模型”层级。
它揭示了更有用的一点:
更强模型的优势取决于我们交给它的问题类别。
如果决策已经做出,并且任务受到严格约束,那么低成本模型可能已经足够好。
如果它还必须探索领域、发现隐含假设、设计架构并预判副作用,那么我们购买的就是能力。
因此,前沿智能最值得优先投入在仍然存在不确定性的地方。
最昂贵的是我们第二次做出的决策
假设一个应用有页面、文档和博客。
每一种类型都应具备标题、slug、语言、SEO 描述、内容、所有者,以及在指定时间发布的能力。Slug 应在同一语言范围内唯一。
我们可以将这些假设分别硬编码到多个实现中。
也可以只记录一次。
在真实的 GOAT 模型中,基础实体 content 定义了包括 title、slug、lang、publication_date、description、body、关系 owner 以及约束 unique(lang, slug) 在内的内容。
page、doc 和 blog 扩展了这一基础,并添加各自的 SSR、SEO 和 CRUD 配置。
对于传统软件工程而言,这只是一个合理的抽象。
在代码同样会由 AI 代理修改的环境中,还会产生额外效果。
下一个代理不需要根据三个相似的实现重新推导共同规则。
决策被压缩到一个单一表示中。
在本文中,我们将其称为 决策压缩:
一个领域决策以这样的形式被记录下来:使流程的后续阶段无需再次对其进行解释。
它可以是模式、类型、DSL、API 契约、策略或约束。
并非每一种此类工具都是同等强度的契约。
但方向比具体技术更重要。
文档告诉代理我们想做什么。契约应当限制它能做什么
如今的代理系统被越来越多的上下文包围。
我们有 AGENTS.md、架构文档、系统提示词、风格指南和代码示例。
这很有帮助。
但以下这句话与这一声明之间仍存在根本差异:
slug 应在同一语言范围内唯一
unique(lang, slug)
前者需要由代理解释。
后者可以由系统验证。
这并不是一个新想法。软件工程长期以来一直使用 DSL、模式、类型系统、模型驱动开发和生成器。
新的地方在于:在这样的环境中,这类形式化模型可以同时成为人类、LLM 与生成器之间的接口。
Unmesh Joshi 在 2026 年的 *DSL 使 LLM 得以可靠使用* 中描述了一个非常相似的模式。他的论点很务实:一个小型、受限的 DSL 能减少表达同一意图的可能表示数量,而解析器、类型检查器或编译器能为代理提供确定性反馈。
To znacznie ciekawsze niż po prostu „lepszy prompt”.
Prompt pomaga agentowi poprawnie zgadywać.
Kontrakt powinien sprawić, że część błędnych odpowiedzi przestaje być dopuszczalna.
契约驱动 AI 开发已经存在——而且值得直说
这个术语本身并非我们的发明。
Enrico Piovesana 于 2025 年 11 月发布了 Contract-Driven AI Development (C-DAD) 框架。它的出发点非常相似:大多数代码库以隐式方式保存意图和约束,因此 agent 被迫自行重建它们。C-DAD 提出了机器可验证的契约,其中包括前置条件、后置条件和不变量。
这里我关注的是这一理念的一个相关但更偏向 生成器优先 的变体。
不仅是:
如何让 agent 理解契约?
还包括:
我们能在不使用模型的情况下执行多少由该契约推导出的后果?
GOAT 是一个很好的案例研究,正是因为它将声明式模型与应用程序后续层的生成结合起来。
回到许可证
我们的业务需求是:
在支付
digital_license类型的产品后,用户应获得 365 天的许可证,且重试不能创建第二个 entitlement。
最糟糕的交接方式如下:
在商店中实现许可证。
这样一来,agent 同时充当业务分析师、架构师、数据库设计师、后端开发者和审查者。
更好的方案是先确定领域模型。
在实际的 GOAT 模型中,产品拥有 product_type。
shop_order_item 保存其自身的 product_type,因此行为类型可以作为交易快照保留下来。
shop_fulfillment 包含 status、product_type、尝试次数、available_at、locked_at 和 last_error。领域注释将该实体描述为支付后执行的持久化、可重试任务。
shop_license 保存用户的 entitlement 及其与 shop_order_item 的关系,同时保存状态和有效期。
因此,在作出架构决策后,流程可以如下所示:
业务意图
支付后获得年度许可证
↓
决策
digital_license 是一种通过可重试 fulfillment 实现的产品行为
↓
契约
产品类型 + 订单快照 + fulfillment + 许可证 + 关系 + 权限 + 约束
↓
生成器
可重复的应用程序结构
↓
低成本模型
本地 fulfillment 逻辑 + 测试
最重要的变化发生在生成第一行代码之前。
我们缩小了决策空间。
当我们不再要求低成本模型承担架构师的工作时,它才会变得有用
确定契约后,执行任务可以大幅收窄:
按照现有模型实现
digital_licensehandler。不要修改 schema 或 API。为正确的 order items 创建 entitlement,设置有效期,保留 retryability,并添加测试。
模型不再需要思考:
许可证应存放在哪里,
如何表示 fulfillment,
是否需要新实体,
如何将 entitlement 与订单关联。
这些决策已在更早之前完成。
剩下的是一个受限的实现问题。
这比单纯比较模型价格更重要。
我们并不是要用 Astra 替代 Luna。
我们正在尝试重构问题,使其不再需要 Astra。
生成器应接管所有我们不想再次协商的内容
LLM 和生成器各有相反的优势。
语言模型擅长处理不确定性。它能够分析不完整的需求、比较不同选项,并调整解决方案。
生成器应该很无聊。
如果我们已经确定了一种 DTO 模式,就不希望每个实体都重新解释一次。
如果每个特定类型的字段都应以相同方式运行,那么创造性在这里就是漂移的来源。
因此,这种方法最重要的原则之一是:
Use AI to decide what. Use generators to repeat how.
在 GOAT 中还存在一个有趣的第二层:AI 可以协助创建生成器自身的模板。
这改变了价值的规模。
如果一个强大的模型实现了一个功能,我们只使用了一次它的推理能力。
如果它帮助我们发现可重复的模式并将其迁移到生成器中,那么同一个决策便可以在后续数十次实现中复用。
正是在这里,AI 开始不仅帮助编写系统,也帮助改进生产该系统的机器。
但契约的价值仅取决于它实际能够强制执行什么
GOAT 自身的模型在这里提供了一个很好的反例。
product_type,它可以控制产品的可执行行为,目前是 short_text。
因此,从 schema 的角度看,以下内容同样都是有效的:
digital_license
digital-licence
license365
如果可能行为的集合是有限的,那么 enum 或显式的 handler 注册表将是更强的契约。
另一个更有意思的例子位于 shop_fulfillment 中。
注释将唯一的 order/type 对描述为确保幂等性的机制。
然而,在展示的模型中看不到实际强制该唯一性的 constraint。
这并不是值得隐藏的缺陷。
它恰恰准确展示了意图与契约之间的边界。
注释可以告诉代理,该操作应当是幂等的。
Constraint 则可以让系统拒绝违反这一规则的状态。
这也正是为什么仅拥有 DSL 还不够。
契约驱动开发始于:我们不仅描述重要的 invariants,还能够强制执行它们。
不过,并非所有内容都应进入 DSL
还存在相反的风险。
最初,DSL 只有几个简单声明。
随后我们需要例外。
添加条件。
Hook。
before。
after。
retry。
unless。
custom_handler。
过一段时间后,我们会发现自己构建了一门编程语言,只不过更糟。
Joshi 指出的正是这种张力:DSL 只有在保持受限且清晰划定边界时,才能帮助 LLM。
因此,实用规则很简单:
将可重复的内容形式化。将例外情况编码。
数据、关系、constraints、标准 permissions 或 CRUD 都是适合形式化的候选项。
非典型算法和一次性行为通常仍然应保留在代码中。
这条边界会随着系统的成熟而移动。
这很好。
AI 漂移最大的难题未必与幻觉有关
设想另一个需求:
用户可以编辑自己的个人资料。
实现看起来很好。
随后出现了进一步说明:
但不能更改 email。
Security 又增加了一条规则:
更改 email 必须经过单独的验证流程。
管理后台仍然使用旧的 CRUD。
一个 API 使用较新的 permissions 模型,另一个则使用较旧的模型。
Agent 无需凭空编造任何东西,也可能犯错。
只要它正确地解决了问题的一部分就足够了。
随着 codebase 的增长,需要从中重建意图的地方也会越来越多。
更大的 context window 有助于读取更多代码。
但它并不能保证 agent 能区分业务决策与偶然的实现细节。
因此,扩展 AI coding 的重要因素,不仅仅是模型接收更多上下文的能力。
还包括组织减少那些根本需要被解释的上下文数量的能力。
DECIDE → FREEZE → EXPAND → VERIFY
整个模型可以归纳为四个阶段。
DECIDE —— 强模型或人类处理我们尚不明确的部分:领域、架构、security、迁移、复杂 bug、业务影响。
FREEZE —— 当决策足够稳定时,它不再只存在于对话中。它会被写入 schema、领域模型、DSL、API contract、policy、测试或生成器规则。
EXPAND —— 生成器执行机械性的后续结果。更便宜的模型补全范围明确且受限的小缺口。
VERIFY —— parser、compiler、schema、测试、static analysis 和 review 对结果进行检查。模型可以参与这个循环,但不应成为自身工作的唯一裁判。
这种方法并不会将 AI 排除在实现之外。
它改变的是我们使用其智能的位置。
公司最有价值的一种记忆形式,可能是那些不再需要解释的代码
组织拥有的技术知识远多于其文档中记载的内容。
ownership 如何运作。
哪些数据会被 snapshot。
哪些角色可以修改数据。
订单状态意味着什么。
正确的 CRUD 应该是什么样子。
哪些行为必须具备幂等性。
这些知识存在于代码、pull request、ticket、文档以及人们的头脑中。
LLM 可以尝试将其重建出来。
但在每次变更时重新还原知识,成本很高。
如果能将其中一部分决策转化为 constraint、schema 或 generator,它们就不再只是团队的知识。
它们会成为系统本身的属性。
这也正是为什么,应当将生成器视为不仅仅是更快编写 boilerplate 的方式。
它们还可以成为一种方式,用于以之后的模型能够自动继承的形式固化组织决策。
vibe coding 之后是什么?
Vibe coding 回答的是这样一个问题:
AI 能把它构建出来吗?
答案越来越常是:能。
但在大型系统中,更有意思的是另一个问题:
半年后,下一个 agent 是否还必须重新发现,为什么我们要以这种方式构建它?
如果答案是肯定的,那么我们每次都在为同一个决策付费。
模型会更好。
Reasoning 会更便宜。
Agent 会工作得更久。
Context windows 会不断扩大。
但即使一个 agent 读完一百万行代码,它仍然必须判断:其中哪些代表意图,哪些只是偶然的实现历史。
契约驱动的 AI 开发提出了另一条路径。
不要试图让 AI 越来越擅长猜测我们的系统。
而是让系统要求 AI 猜测的内容越来越少。
强大的模型应当在存在不确定性的地方帮助做出决策。
契约应当固化那些我们不希望再次协商的决策。
生成器应当执行我们已经知道的内容。
而更便宜的模型应当获得一个受限的问题,在其中我们仍然确实需要灵活性。
最昂贵的智能不值得花在产生最多代码的地方。
它值得花在那些日后会被重复数千次的决策产生之处。