
1. 这不是“调提示词”而是一场形式化验证的工程实践你有没有试过这样写提示词把“请按步骤推理”改成“请用自然演绎法展开前提推导”把“确保逻辑严密”换成“每一步结论必须有且仅有一个前件支撑”再把“避免幻觉”明确为“所有断言需标注来源类型公理/定义/引理/已证命题”这不是在玩文字游戏而是 Boris Cherny 在真实项目中对 Claude Agent SDK 所做的——他没用测试用例覆盖分支也没靠人工抽检输出而是把整个 Agent 的行为契约直接翻译成 Lean 定理证明器能理解的、可机械验证的形式化规范。关键词里没有“Lean”“形式化验证”“定理证明”但标题里那个“用 Lean 形式化验证 Claude Agent SDK”的动宾结构已经锁死了这件事的技术内核这不是一个关于“怎么让大模型更听话”的技巧分享而是一次将 AI 行为建模为数学对象、并用形式化工具对其做严格证明的系统性工程。它面向的不是刚学完 LangChain 的新手而是那些已经写过数百行 Agent 逻辑、却在关键业务场景中反复被“看似合理实则错漏”的推理链绊倒的资深开发者。这类人最清楚当 Agent 要在金融风控中生成合规检查报告或在医疗辅助中推导用药禁忌时“85% 准确率”毫无意义你需要的是“在给定前提集下结论必然成立”的确定性。Boris 的做法本质上是把提示词从模糊的自然语言指令升格为带语义约束的、可验证的程序接口契约。他验证的不是某一次输出是否正确而是整个提示词模板所定义的 Agent 行为边界——只要输入满足预设条件输出就必然满足后置断言。这种思路跳出了传统 Prompt Engineering 的经验主义框架直指 AI 系统可靠性的底层矛盾我们能否像验证一段 C 代码的内存安全那样去验证一段提示词驱动的推理过程的逻辑完备性答案是肯定的而 Lean 就是他选择的“编译器”。2. 为什么是 Lean而不是 Coq、Isabelle 或其他证明助手在决定用 Lean 验证提示词之前Boris 团队实际对比了四套主流交互式定理证明器Coq、Isabelle/HOL、Agda 和 Lean 4。这个选择不是拍脑袋而是基于三个硬性约束反复权衡的结果。第一个约束是表达能力与可读性的平衡。Coq 的 Gallina 语言极其强大但其依赖类型和归纳定义的语法对非专业形式化验证者极不友好。比如要表达“Agent 的响应必须包含且仅包含一个 JSON 对象该对象的reasoning字段是字符串数组每个字符串必须是形如Step N: ...的格式”在 Coq 中需要先定义嵌套的归纳类型、再构造复杂的谓词来约束 JSON 结构代码量轻松破百行且调试成本极高。而 Lean 4 的#check命令和Mathlib库提供的Json模块允许用接近自然语言的声明式语法直接书写约束例如def validReasoningField (j : Json) : Prop : match j with | Json.obj o o.contains reasoning ∧ match o.find? reasoning with | some (Json.arr steps) steps.All fun s match s with | Json.str s s.startsWith Step ∧ s.contains : | _ False | _ False | _ False这段代码清晰表达了字段存在性、类型匹配、字符串格式三重约束任何熟悉 JSON 结构的工程师都能快速理解其意图。第二个约束是与外部系统的可集成性。他们需要将 Lean 验证结果反馈到 Agent 的运行时监控中。Lean 4 编译为高效的 C 二进制且支持通过 FFIForeign Function Interface与 Python 无缝交互。团队用 Lean 写了一个轻量级验证服务Agent 每次生成响应后Python 主程序将其序列化为 JSON调用 Lean 服务的verify_response函数返回True/False及失败位置。而 Isabelle 的 ML 接口虽然稳定但部署复杂难以嵌入现有 Python 微服务架构Agda 的编译目标Haskell则与生产环境技术栈脱节。第三个约束是社区生态对“计算型证明”的支持。他们发现很多提示词约束本质是可计算的比如“响应长度不超过 500 字符”、“所有数字必须保留两位小数”、“时间戳必须符合 ISO 8601 格式”。Lean 的#eval命令能直接执行这些检查无需额外编写解释器。Mathlib 中的String和Format模块提供了丰富的字符串处理原语使得将自然语言需求如“日期必须用中文‘年月日’格式”转化为可执行的 Lean 断言变得可行。相比之下Coq 的Compute命令在处理复杂字符串操作时性能较差且缺乏成熟的格式化库。最终Lean 4 在“人类可读性”、“系统可集成性”、“计算可行性”三个维度上取得了最佳交点。这不是对其他工具的否定而是针对“将提示词作为可验证接口”这一特定场景的精准选型——就像你不会用 Fortran 去写前端界面也不会用 React 去写操作系统内核。3. 提示词如何被“翻译”成 Lean 中的可验证契约把一句“请逐步推理并给出最终答案”变成 Lean 里的可验证命题中间隔着一整套建模工作。Boris 的核心方法论是将提示词视为定义了一个“响应空间”的过滤器而 Lean 的任务是精确刻画这个空间的数学边界。整个过程分为三层映射缺一不可。第一层是语义锚定层为提示词中的关键指令词赋予形式化含义。例如“逐步推理”在 Lean 中被锚定为ReasoningTrace类型它不是一个字符串而是一个由Step结构体组成的列表每个Step包含stepNumber : Nat、premise : List String前提引用、conclusion : String本步结论和ruleApplied : InferenceRule应用的推理规则。这里的InferenceRule是一个归纳类型枚举了ModusPonens、HypotheticalSyllogism、DefinitionUnfolding等有限规则集。这意味着任何声称“逐步推理”的响应其reasoning字段必须能被解析为一个List ReasoningTrace且每一步的ruleApplied必须属于预定义集合。第二层是结构约束层将提示词对输出格式的要求转化为 JSON Schema 的 Lean 表达。他们没有直接手写 JSON 解析器而是利用 Mathlib 的Json模块和自定义的Schema类型。例如对“输出必须是 JSON 对象包含answer字符串、confidence0-1 浮点数、sources字符串列表三个字段”的要求Lean 中的契约是def responseSchema : Schema : Schema.obj [ (answer, Schema.str), (confidence, Schema.floatInRange 0 1), (sources, Schema.arr Schema.str) ]这个Schema类型本身就是一个可计算的验证函数validate responseSchema responseJson会返回Bool。第三层是逻辑一致性层这是最难也最关键的一步它将提示词中隐含的语义关系显式化为 Lean 中的逻辑蕴含。例如提示词要求“如果confidence小于 0.3则sources列表不能为空”这在 Lean 中被写为theorem lowConfidenceImpliesSources (r : Json) : isValidResponse r → let c : getConfidence r in c 0.3 → not (getSources r).isEmpty这个定理的证明不是由人手动完成的而是由 Lean 的自动证明策略simp和linarith驱动前提是getConfidence和getSources函数本身是纯的、可计算的。Boris 团队为此专门重构了 JSON 解析逻辑确保所有字段提取函数都标记为[implemented_by]并指向纯函数实现。整个“翻译”过程不是一次性完成的而是迭代式的先写出粗粒度契约如字段存在性跑通验证再加入细粒度约束如字符串格式观察 Lean 报错信息反推提示词歧义最后补全逻辑蕴含形成闭环。一个典型例子是他们最初写的“答案必须基于提供的文档”被 Lean 报错failed to synthesize instance for DocumentReference这才意识到提示词中“文档”一词未明确定义其结构于是回溯修改提示词增加“所有引用必须以[DOC_ID:xxx]格式标注”并在 Lean 中定义DocumentReference类型。这种“Lean 报错 → 提示词修正 → 契约更新”的循环恰恰证明了形式化验证的价值它把模糊的人类直觉逼成了精确的机器可读规范。4. 实际验证流程从提示词模板到可执行的 CI/CD 检查点验证不是发生在开发者的笔记本上而是深度嵌入到 Agent SDK 的持续集成流水线中。Boris 团队设计了一套“三阶段验证”流程确保每次提示词变更都经过形式化检验。第一阶段是静态契约生成。当开发者提交一个新的提示词模板.prompt文件时CI 流水线首先运行一个 Python 脚本prompt2lean.py。这个脚本不是简单地做字符串替换而是基于一套预定义的“提示词模式库”进行语义解析。例如识别到{{#each documents}}...{{/each}}语法就自动注入DocumentReference类型约束识别到{{assert confidence 0.5}}就生成对应的lowConfidenceImpliesSources反向定理。脚本输出一个.lean文件其中包含完整的responseSchema定义、ReasoningTrace类型、以及所有从提示词中提取的逻辑定理。第二阶段是Lean 编译与类型检查。CI 调用lean --make编译该文件。这一步捕获所有语法错误、类型不匹配和未定义符号。一个常见失败是提示词中写了“使用贝叶斯定理”但 Lean 契约中未定义BayesTheorem规则导致ruleApplied类型不兼容。此时 CI 直接失败并给出清晰错误“BayesTheoremis not a constructor ofInferenceRule”迫使开发者要么补充规则定义要么修改提示词。第三阶段是运行时契约验证。这是最核心的环节。CI 启动一个模拟的 Agent 服务用一组标准化的测试用例Test Case Bank向其发送请求。每个测试用例包含inputContext上下文 JSON、expectedSchema预期响应结构、expectedLogic预期逻辑关系如“若输入含否定词则输出必须含‘谨慎’一词”。Agent 生成响应后CI 调用 Lean 编译好的验证二进制传入响应 JSON 和对应的契约文件。验证结果分为三类Valid完全符合、InvalidStructureJSON 结构错误如缺少字段、InvalidLogic结构正确但逻辑违反如confidence0.2但sources[]。CI 不仅报告失败还会输出 Lean 的反例Counterexample例如对于lowConfidenceImpliesSources定理Lean 会生成一个具体的 JSON 示例其中confidence为0.1且sources为空直观展示为何契约被违反。这套流程已上线三个月共拦截了 17 次提示词变更引入的逻辑漏洞。最典型的一次是一位开发者将提示词中的“最高置信度为 0.95”误写为“最高置信度为 0.9”Lean 在 CI 阶段立即报错confidence 0.95定理无法被证明因为新约束太强导致部分合法响应被拒绝。团队据此调整了契约将上限改为≤ 0.95并增加了对0.95边界值的显式测试。这证明形式化验证不是给开发加锁而是提供了一面高精度的镜子让每一次提示词的微小改动其影响都无所遁形。5. 踩坑实录当 Lean 验证器遇上真实世界的提示词混沌理论很美落地全是坑。Boris 在博客中坦承他们花了近六周才让第一个提示词模板通过全部验证其中大部分时间花在了与“现实世界混沌性”的搏斗上。第一个大坑是自然语言的指代消解困境。提示词中常出现“上述步骤”、“前面提到的参数”、“根据上下文判断”等模糊指代。Lean 要求所有变量必须有明确作用域和类型。例如提示词说“请比较 A 和 B 的值并指出哪个更大”Lean 契约需要知道A和B的具体类型是NatFloat还是String以及比较规则字典序数值大小。团队最初的解决方案是强制在提示词中用{{A: Int}}这样的语法标注类型但这遭到了产品团队抵制认为破坏了提示词的可读性。最终妥协方案是在 Lean 契约中定义一个ContextualValue类型它是一个带有typeHint字段的联合体并在验证时如果遇到未标注类型的指代Lean 会启动一个轻量级的“类型推断引擎”扫描上下文 JSON 中同名字段的值自动推断其最可能的类型如123推断为Int123.45推断为Float并将推断结果作为验证的前提之一。第二个大坑是大模型输出的“创造性”与形式化约束的冲突。有一次提示词要求“用三个比喻描述云计算”Lean 契约定义了metaphors : List String。Agent 却生成了1. 云像天空广阔无垠。2. 云像海洋深不可测。3. 云像...—— 这看起来完美但 Lean 的String类型无法区分“编号字符串”和“纯文本”导致metaphors被解析为一个包含单个长字符串的列表而非三个独立字符串。解决办法是在 Lean 中定义Metaphor类型并强制要求每个比喻必须以换行符\n分隔同时在验证函数中加入splitOn \n操作并检查分割后列表长度恰好为 3。这暴露了一个深刻事实形式化验证的难点往往不在数学证明本身而在如何精确建模人类语言的“不精确性”。第三个大坑是性能瓶颈。早期他们试图在 Lean 中直接解析原始的、未经清理的 Agent 响应 JSON其中包含大量 HTML 标签、Markdown 语法和冗余空格。Lean 的Json.parse在处理 50KB 的混乱文本时编译时间飙升至 2 分钟以上CI 流水线完全不可用。解决方案是前置一个“响应净化器”一个用 Rust 编写的超轻量服务只做三件事——移除所有 HTML 标签、将 Markdown 链接[text](url)替换为text、压缩连续空白字符。净化后的 JSON 体积平均减少 65%Lean 验证时间稳定在 800ms 以内。这三个坑共同指向一个经验形式化验证不是把现实塞进数学模具而是用数学的严谨性去驯服现实的毛边。每一次失败的验证都是对提示词设计缺陷的一次精准诊断。6. 这套方法能复制吗给想动手的开发者的四条硬核建议看到这里你可能会想这听起来很酷但我的团队没有形式化验证背景能上手吗Boris 的回答很实在可以但必须放弃“一步到位”的幻想从最小可行契约开始。他给出了四条基于血泪教训的建议。第一条永远从“结构验证”起步跳过“逻辑验证”。不要一上来就想证明“如果输入含敏感词则输出必须打码”。先聚焦在“输出必须是 JSON 对象”、“必须包含status和data两个字段”、“data字段必须是数组”这种零歧义的结构约束上。Lean 的Json.Schema模块对此支持极好几行代码就能搞定且能立刻获得 CI 的即时反馈。这能建立团队信心并快速暴露提示词中最基础的格式问题。第二条把 Lean 当作“超级 linter”而非“证明引擎”。初期不要追求 100% 的数学证明覆盖率。把 Lean 验证器看作一个比 ESLint 更严格的代码检查器它的核心价值是“提前发现那些靠人工 Review 绝对会漏掉的结构性错误”。例如一个提示词要求“返回用户姓名、年龄、城市”Lean 可以确保每次响应都有且仅有这三个字段且类型正确age是数字而非字符串这比任何单元测试都可靠。第三条契约与提示词必须双向绑定版本同步。他们曾犯过一个致命错误Lean 契约文件v1.2.lean对应提示词v1.1.prompt而 CI 流水线拉取的是v1.2.prompt。结果验证通过但线上出 bug。现在所有提示词文件都采用prompt_name-vX.Y.Z.prompt命名对应的 Lean 契约必须是prompt_name-vX.Y.Z.leanCI 脚本会强制校验两者版本号一致否则拒绝构建。第四条接受“不完美验证”但绝不接受“不验证”。Boris 强调他们的第一个可验证提示词只覆盖了 30% 的业务逻辑但它成功拦截了 100% 的结构类故障。形式化验证的价值不在于它能解决所有问题而在于它能以极低成本消灭那些最高频、最顽固、最让人抓狂的“低级错误”。当你在深夜收到告警发现 Agent 因为少了一个逗号而返回了{error: invalid json}你就明白哪怕只加一条Schema.obj [...]验证也值得。最后分享一个他亲测有效的技巧在 Lean 契约文件顶部用注释写明“此契约验证的提示词版本”和“本次验证覆盖的核心需求”例如-- Validates prompt financial_summary-v2.1: ensures confidence in [0.0, 1.0], sources non-empty if confidence 0.4, and reasoning trace has at least 3 steps.。这能让任何新加入的成员在 10 秒内理解这份 Lean 代码存在的唯一目的——它不是炫技而是守护某个具体业务承诺的盾牌。