AI 编程循环的形式化验证门控

AI 编程代理(AI coding agents)的兴起为软件开发生命周期引入了一个强大但不可预测的元素。虽然 LLM 能够生成大量的代码,但它们在一致性、遵循严格的业务逻辑以及避免走捷径方面往往表现不佳。挑战不再仅仅是如何让 AI “更聪明”或改进提示词(prompt),而是如何创建一个系统,使 AI 在物理上无法违反架构规则。

结构化背压的概念

这种方法的核心思想是“结构化背压”(structural backpressure)。开发者不再依赖 AI 遵循提示词中提供的一组指令——AI 可能会忽略或产生幻觉——而是可以将这些规则移入类型系统和编译器中。

当规则被编码进类型时,编译器就成了最终的守门人。如果 AI 代理尝试生成违反业务规则或安全约束的代码,编译器将拒绝构建项目。这创造了一个反馈循环,AI 会因为编译器的拒绝而被“弹回”,从而被迫纠正其方法以满足系统的形式化要求。

正如该项目的作者 pyrex41 所指出的:

将规则从提示词移入编译器拒绝违反的类型中,然后利用这些拒绝来“弹回”AI 编程循环。

实现守卫类型

为了实现这一点,开发者使用“守卫类型”(guard types)或能力(capabilities)。这些类型作为满足特定条件的证据。例如,与其传递一个原始字符串作为用户 ID,系统可能会要求一个 TenantAccess 类型。为了获得该类型的实例,AI 必须调用一个特定的验证函数,该函数执行实际的检查。

通过将这些守卫类型的构造函数设为私有或受限,你可以确保 AI 无法简单地“幻觉”出一个成功的验证。AI 必须与系统的真实世界状态进行交互,以产生编译器继续执行所需的证据。

批判性视角:未经验证构造函数的危险性

虽然结构化背压的理论框架非常强大,但其实现需要严谨的细节关注。社区讨论中提出的一个关键点是:允许 AI 代理直接调用构造函数存在风险。

如果 AI 可以简单地调用像 (tenant-access user-id tenant-id true) 这样的构造函数,它实际上就绕过了验证过程。AI 并不是在证明访问权限;它是在断言它。正如 @singron 指出的:

如果 AI 在调用构造函数,那么它就能做出自己的断言并推导出它想要的任何结果。这似乎是本末倒置的。AI 应该使用 tenant-access 的结果来推断用户是租户的成员,而不是如果它们可以直接调用 (tenant-access user-id tenant-id true),那么它们可以为任何东西“证明” tenant-access

为了减轻这种风险,守卫类型的构造函数应该被命名并受到极度严格的审查。一种常见的模式是将这些构造函数命名为 newUnverified,并将其使用限制在系统中最受信任的部分,例如 JWT 解析器或直接数据库查询。这确保了 AI 代理无法创建“伪造”的证据来满足编译器。

结论:确定性优于智能

将 AI 集成到编程循环中的目标不是为了构建一个更智能的代理,而是在构建一个更具确定性的环境。通过将证明责任从提示词转移到运行时和编译器,开发者可以创建“足够陡峭的护栏,让 AI 无法翻越围栏”的保护机制。

最终,最稳健的 AI 辅助开发工作流是那些将 AI 视为不可信角色,并使用健全的编程原则——例如强类型和基于能力的安全性——来强制执行软件的架构完整性。

Sources