Lean 4 自动化内幕

23. 对接外部工具🔗

本章目标:理解 Lean 4 如何与外部求解器(SAT/SMT/CAS/LLM)交互, 掌握证书模式和 oracle 模式的安全性区别, 学会使用 IO.Process API 实现外部调用, 并理解 LLM 辅助证明搜索的架构和信任模型。

在前两章我们学习了反射和性能工程。 但有些问题超出 Lean 内部 tactic 的能力或效率边界—— SAT 求解器处理百万变量的命题逻辑、 CAS 做 Gröbner 基计算、SMT 求解器做线性算术—— 这些工具经过几十年优化,在 Lean 中重新实现既不现实也无必要。

本章讨论如何安全地借用外部计算能力, 同时保持 Lean 内核作为最终信任锚。

23.1. 22.1 为什么对接外部工具🔗

  • 问题域:命题 SAT —— 外部工具:CaDiCaL、Kissat —— 为什么 Lean 不自己做:CDCL 算法 + 启发式需百万行优化代码

  • 问题域:SMT —— 外部工具:Z3、cvc5 —— 为什么 Lean 不自己做:理论组合框架极其复杂

  • 问题域:符号计算 —— 外部工具:Sage、Mathematica —— 为什么 Lean 不自己做:Gröbner 基、积分、级数求和

  • 问题域:ITP 桥接 —— 外部工具:Coq、Isabelle —— 为什么 Lean 不自己做:复用其他证明助手的引理库

  • 问题域:证明搜索 —— 外部工具:LLM(GPT、Claude) —— 为什么 Lean 不自己做:启发式 tactic 建议

核心张力:外部工具能快速给出答案,但 Lean 的保证依赖内核检查。 设计问题归结为:如何利用外部计算能力而不破坏信任模型?

23.2. 22.2 证书(Certificate)模式🔗

最安全的方式:外部工具生成证书,Lean 验证证书。

23.2.1. 工作流程🔗

❶ Lean 把问题编码为外部格式(DIMACS、SMT-LIB、JSON)
❷ 启动外部进程,发送问题
❸ 外部工具求解,返回证书(resolution proof、多项式系数等)
❹ Lean 解析证书,在内核中验证正确性
❺ 验证通过 → 构造 Lean 证明项

安全性:即使外部工具有 bug 或返回垃圾数据, Lean 内核会拒绝不正确的证书。外部工具不在可信基础(TCB)中

23.2.2. 案例:polyrith🔗

-- [示意] polyrith 的使用
example (a b : ℚ) (h1 : a + b = 3) (h2 : a - b = 1) :
    a = 2 := by
  polyrith
  -- ▸ 内部流程:
  -- ❶ 编码:把 h1、h2 和目标转为多项式约束,序列化为 JSON
  -- ❷ 调用:发送给 Sage 服务器(远程 HTTP 请求)
  -- ❸ 求解:Sage 计算 Gröbner 基,找到系数 1/2 * h1 + 1/2 * h2
  -- ❹ 返回:系数作为证书
  -- ❺ 验证:Lean 用系数构造 linear_combination,由 ring 验证

关键:Sage 不在 TCB 中——验证完全由 ring 在 Lean 内核完成。

23.2.3. 案例:SAT 证书🔗

❶ Lean 把命题编码为 CNF(DIMACS 格式)
❷ 调用 CaDiCaL
❸ 求解器返回 LRAT 证书(resolution proof 的紧凑表示)
❹ Lean 中的 LRAT checker 验证每一步 resolution

LRAT 是 SAT 竞赛标准——Lean 只需一个高效 LRAT checker 即可对接任意求解器。

23.2.4. 证书模式的代价🔗

  • 编码/解析开销:MetaM 端代码量不小

  • 验证开销:内核验证证书消耗 heartbeats

  • 证书膨胀:SAT 的 resolution proof 可达 GB 级

  • 工具依赖:需要外部工具可用(网络或本地安装)

23.3. 22.3 Oracle 模式🔗

不安全但方便:直接信任外部工具的结果。

-- [示意] ⚠️ 不安全:引入新公理
axiom smt_oracle : ∀ p : Prop, Decidable p
-- ▸ 这不是合法公理——可以用它证明 False

23.3.1. Oracle vs native_decide🔗

  • 逻辑层面 —— native_decide不引入新公理 —— Oracle(sorry/自定义公理):引入不可验证的假设

  • 可信基础 —— native_decide:Lean 内核 + 编译器 —— Oracle(sorry/自定义公理):Lean + 外部工具

  • 失败时 —— native_decide:类型错误 —— Oracle(sorry/自定义公理):可能引入矛盾

  • Mathlib —— native_decide:谨慎使用 —— Oracle(sorry/自定义公理):不接受

native_decide 不是 oracle——它在已信任的编译环境中做更快的判定计算。

23.3.2. Oracle 的合理场景🔗

❶ 快速原型:用 sorry 标记子目标,先完成整体架构
❷ 开发迭代:信任外部结果以加速编辑-编译循环
❸ 最终替换:逐步把 sorry 替换为证书模式或手动证明

永远不要把含 sorry 的代码合并到主分支。 #check_no_sorry 和 Mathlib CI 强制执行这一纪律。

23.4. 22.4 实现外部调用🔗

23.4.1. IO.Process.spawn——流式交互🔗

-- [示意] 启动外部进程并流式通信
def callSolver (problem : String) : IO String := do
  let child ← IO.Process.spawn {
    cmd := "z3"                    -- ❶ 可执行文件
    args := #["-in", "-smt2"]     -- ❷ 命令行参数
    stdin := .piped                -- ❸ 管道式 stdin/stdout
    stdout := .piped
    stderr := .piped
  }
  child.stdin.putStr problem       -- ❹ 写入问题
  child.stdin.flush
  let stdout ← child.stdout.readToEnd  -- ❺ 读取输出
  let exitCode ← child.wait
  if exitCode ≠ 0 then
    throw <| IO.userError s!"z3 failed with exit code {exitCode}"
  return stdout

23.4.2. IO.Process.output——一次性调用🔗

-- [示意] 简便版本,适合短时脚本
def callScript (input : String) : IO String := do
  let result ← IO.Process.output {
    cmd := "python3"
    args := #["solver_script.py", "--input", input]
  }
  -- ▸ result : { exitCode : UInt32, stdout : String, stderr : String }
  if result.exitCode ≠ 0 then
    throw <| IO.userError s!"script failed: {result.stderr}"
  return result.stdout

23.4.3. 在 tactic 中使用🔗

TacticMMetaM → 可执行 IO

-- [示意] 在 tactic 中调用外部工具
elab "external_solve" : tactic => do
  let goal ← getMainTarget
  let smt2 := encodeGoal goal          -- ❶ 编码
  let result ← callSolver smt2         -- ❷ 调用(IO 操作)
  match parseSolverOutput result with   -- ❸ 解析
- .sat cert =>
    let proof ← verifyCertificate cert goal  -- ❹ 验证证书
    closeMainGoal proof
- .unsat   => throwError "solver says unsat":.unknown => throwError "solver returned unknown"

23.5. 22.5 编码与解码🔗

对接外部工具的大量工作在于编码(Lean Expr → 外部格式)和解码(外部输出 → 证书)。

-- [示意] 编码:Lean Expr → SMT-LIB
partial def encodeToSMT (e : Expr) : MetaM String := do
  if let some (a, b) ← matchAnd? e then       -- ❶ 匹配 And
    return s!"(and {← encodeToSMT a} {← encodeToSMT b})"
  else if let some (a, b) ← matchOr? e then   -- ❷ 匹配 Or
    return s!"(or {← encodeToSMT a} {← encodeToSMT b})"
  else if let some a ← matchNot? e then        -- ❸ 匹配 Not
    return s!"(not {← encodeToSMT a})"
  else                                          -- ❹ 不可识别 → 不透明变量
    let name ← mkFreshUserName `x
    return s!"{name}"

和反射中的 quote 一样,编码覆盖范围直接决定工具能力边界—— 不可识别的构造被当作不透明变量,求解器无法分析其语义。

23.6.1. 架构🔗

  Lean Server ──→ LLM API ──→ tactic 候选 ["simp", "ring", ...]
  (目标 + 上下文)                       │
       └── 逐个尝试执行 ←──────────────┘
           验证通过 → 接受;全部失败 → 报错

23.6.2. 实现骨架🔗

-- [示意]
elab "llm_suggest" : tactic => do
  let goal ← getMainTarget
  let ctx ← getLocalHypotheses
  let response ← callLLM (formatPrompt goal ctx)  -- ❶ 调用 LLM
  let candidates ← parseTactics response           -- ❷ 解析候选
  for tac in candidates do                          -- ❸ 逐个尝试
    try
      evalTacticStr tac    -- 在 MetaM 中执行
      return               -- 成功 → 结束
    catch _ => continue
  throwError "all LLM suggestions failed"

23.6.3. 信任模型与防护🔗

LLM 不在 TCB 中——和 SAT 求解器地位相同:提供候选,Lean 内核裁决。

LLM 可能生成:             应对:
  正确 tactic             → 内核验证通过 ✓
  错误 tactic             → 内核拒绝 ✓
  包含 sorry 的 tactic     → 框架层面过滤 ⚠️

防护措施:过滤 sorry、每个候选设 heartbeat 上限、执行后检查目标是否全部关闭。

23.7. 22.7 三种模式对比🔗

  • 安全性 —— 证书模式:高 —— Oracle 模式:低 —— LLM 辅助:高

  • TCB —— 证书模式:Lean 内核 —— Oracle 模式:Lean + 外部工具 —— LLM 辅助:Lean 内核

  • 确定性 —— 证书模式:确定 —— Oracle 模式:确定 —— LLM 辅助:不确定

  • 适用 —— 证书模式:SAT/SMT、Gröbner —— Oracle 模式:开发迭代 —— LLM 辅助:探索性证明

  • Mathlib —— 证书模式:接受 —— Oracle 模式:不接受 —— LLM 辅助:结果可接受

  • 代表 —— 证书模式:polyrithbv_decide —— Oracle 模式:sorry 工作流 —— LLM 辅助:LeanDojo、ReProver

23.8. 22.8 实践考量🔗

23.8.1. 可用性与 fallback🔗

polyrith 需要网络 → 失败时打印系数,用户手动写 linear_combination
SAT tactic 需本地安装 → 提供安装指引或 Docker 镜像
LLM tactic 需 API key → 优雅降级,给出清晰错误

23.8.2. 可复现性🔗

外部工具引入不确定性。解决方案:

  • 缓存证书polyrith 支持 --only 模式使用缓存系数

  • 固化 LLM 建议:成功的 tactic 替换为确定性版本

  • CI 独立:不依赖外部服务,只用缓存结果

23.9. 22.9 失败模式🔗

23.9.1. 失败 1:外部工具不可用🔗

-- [示意]
-- ✗ 用户没装 z3
-- 错误:z3: command not found
-- ✓ tactic 应捕获 IO 错误并给出安装提示
-- ✓ 提供不依赖外部工具的 fallback

23.9.2. 失败 2:编码不完整🔗

-- [示意]
-- ✗ 目标涉及自定义类型,编码函数不识别 → 求解器返回 unknown
-- ✓ 诊断:打印编码后的 SMT-LIB 查看遗漏
-- ✓ 修复:扩展编码函数,或先 unfold/simp 消除未识别构造

23.9.3. 失败 3:证书验证失败🔗

-- [示意]
-- ✗ 外部工具 bug 导致错误证书 → Lean 验证失败
-- ✓ 这正是证书模式的价值——错误被捕获,不引入矛盾
-- ✓ 更新工具版本或报告 bug

23.9.4. 失败 4:Oracle 引入矛盾🔗

-- [示意]
-- ✗ 自定义公理 axiom smt_says (p : Prop) : p → 可证明 False
-- ✓ 用 sorry 而非自定义公理——sorry 至少有编译器警告
-- ✓ oracle 公理永远不要进入生产代码

23.9.5. 失败 5:LLM 建议含 sorry🔗

-- [示意]
-- ✗ LLM 返回 "intro h; sorry" → 框架不过滤则证明不完整
-- ✓ 执行前过滤 sorry;执行后检查无剩余目标

23.9.6. 失败 6:证书膨胀导致验证超时🔗

-- [示意]
-- ✗ 10 万变量 SAT 问题,resolution proof 100 万步 → heartbeats 爆炸
-- ✓ 用 native_decide 验证证书(而非内核归约)
-- ✓ 使用更紧凑的证书格式

23.10. 22.10 设计检查清单🔗

  • 信任模型:证书模式还是 oracle?生产代码必须用证书模式

  • 编码范围:需要编码哪些构造?不识别的如何处理?

  • 证书格式:返回什么格式?如何在 Lean 中验证?

  • 可用性:工具不可用时的 fallback?

  • 可复现性:证书缓存?CI 能否不依赖外部服务?

  • 性能:编码 + 调用 + 验证的总时间可接受?

  • 错误信息:各种失败场景是否有清晰提示?

23.11. 22.11 练习🔗

23.11.1. 练习 1(设计):选择集成模式🔗

对以下场景,选择证书模式、oracle 模式或 LLM 辅助,说明理由:

(a) Mathlib PR 中需要证明一个 200 变量的 SAT 问题。
(b) 个人项目探索阶段,需快速验证一批代数恒等式。
(c) 教学工具:学生输入定理,系统尝试自动找到证明。

提示:(a) 必须证书模式——Mathlib 不接受 sorry; (b) oracle(sorry)加速迭代,后续替换; (c) LLM 辅助——不确定性可接受,内核保证正确性。

23.11.2. 练习 2(分析):可信基础对比🔗

列出以下证明方式的 TCB,从小到大排序:

(a) by ring
(b) by native_decide
(c) by polyrith(调用 Sage 后用 ring 验证)
(d) by sorry(信任外部 SMT 的口头声明)

提示:(a) Lean 内核;(b) 内核 + 编译器; (c) 和 (a) 相同(Sage 不在 TCB 中——证书由 ring 验证); (d) 内核 + 外部 SMT + 人工判断。

23.11.3. 练习 3(进阶):设计编码函数签名🔗

为以下场景设计类型签名(不需实现):

-- (a) 把 Prop 目标编码为 SMT-LIB,需要返回变量映射以便解码
-- def encodeGoalToSMT : ??? := ...

-- (b) 解析 LRAT 证书
-- def parseLRAT : ??? := ...

提示: (a) Expr → MetaM (String × HashMap Expr String) (b) String → MetaM (List LRATStep)

23.12. 22.12 小结🔗

  • 外部工具的价值:利用专门求解器补充 Lean 内部 tactic

  • 证书模式:外部工具给证书,Lean 验证——外部工具不在 TCB 中

  • Oracle 模式:直接信任外部结果——不安全,仅限开发迭代

  • native_decide ≠ oracle:不引入新公理,只是更快的判定计算

  • IO.Process API:spawn(流式)和 output(一次性)两种方式

  • 编码/解码:覆盖范围决定工具能力边界(同反射的 quote)

  • LLM 辅助:LLM 提供候选,Lean 验证——安全但不确定

  • 可复现性:缓存证书,CI 不依赖外部服务

  • 主要陷阱:工具不可用、编码不全、oracle 矛盾、证书膨胀

一句话总结:对接外部工具的核心原则是计算外包,验证内留—— 让专门的求解器做它擅长的计算,但证明正确性由 Lean 内核担保。

下一章讨论自动化方法论——如何从手动证明中系统地发现可自动化的模式。