OpenAI 敢宣称证伪 ETH,靠的不是模型更聪明——是数学有「编译器」;你的 AI 报告没有,所以它还在写错日期,附可运行代码)

本文提出将形式化验证思想引入AI文本生成流程,借鉴Lean证明系统的设计理念,构建面向自然语言的‘编译器’:通过解析、求值、链接三阶段流水线,对AI输出中的算术声明、事实声明和观点声明分别处理;支持安全AST求值、日期/数值复算、符号表驱动的事实核验,并明确标出不可判定内容。核心目标是用确定性机制抑制AI幻觉,提升生成内容可信度。

昨天(10 月 27 日)量子位报道了一条挺炸裂的新闻:OpenAI 在发新模型 GPT-6.1 Terra 的同一天,顺手公布了三项 AI 系统产出的科学成果,横跨数学、计算机科学和物理:

  1. 证明了局部平滑猜想(数学家 Sogge 1991 年提出,30 多年没人完全拿下),还顺带解决了 Kakeya 猜想和 Stein 限制猜想
  2. 宣称证伪了 ETH——指数时间假说。这个假说大致认为,像 3-SAT 这类复杂问题很难找到比穷举快得多的算法,过去几十年一大堆计算机科学结论都建立在它成立的基础上。现在 OpenAI 说,它的 AI 系统证明了这个地基是错的
  3. 给出了包括量子色动力学(QCD)在内的某些四维量子场论的存在性证明——千禧年大奖难题"杨-米尔斯"的一部分(质量间隙还没解决,百万美元暂时还领不走)

两个限定必须先钉死,免得成谣言:第一,这是 OpenAI 自己宣称的结果,数学界还在审查确认中(物理部分正由诺奖得主 David Gross 等外部专家把关);第二,证伪 ETH 不等于解决了 P vs NP,也不意味着明天就有实用新算法。

但今天我想聊的不是这三个成果本身,是报道里一个容易被划过去的细节:这三项成果不是只甩出一段自然语言推导就完事——数学与计算机科学结果的 Lean 形式化证明已经一并公开,谁都可以拿去机器复验。

这个细节才是重点。

Lean 是什么?数学的编译器

Lean 是一个形式化证明系统。你把数学证明写成 Lean 代码,它像编译器一样逐行检查每一步推导是否严格合法——公理用错了、跳步了、类型对不上,编译直接报错,一个符号都糊弄不过去。

这就是为什么 OpenAI 敢把"证伪 ETH"这种动摇地基的结论放出来:AI 写的证明过了 Lean 的编译,等于拿到了机器背书。没人需要"相信"这个 AI,大家只需要相信编译器。

再说回咱们日常干活。你让 AI 写周报、写促销文案、写行业快讯——它写得一样流畅、一样自信、一样"看起来对"。但它写"3 天后是周五"(实际是周六)、“12 万比 8 万多了 5 万”(明明是多 4 万)的时候,没有任何东西会报错。

差别就在这儿:

数学有编译器,自然语言没有。 AI 在 Lean 里错一步就编译失败;在你的文档里错一百处,排版照样精美。所谓幻觉检测,说白了就是给自然语言补一个编译器。

下面我用 176 行标准库代码搭一个最小版本。思路直接抄编译器:把文本里的声明分三类,能算的当场算,要查资料的走"链接",判不了的标出来转人工。

核心设计:三种声明,三条流水线

编译器不是万能的,它只管它能判的。这个"文本编译器"也一样,先把声明分三类:

类型类比例子怎么处理
COMPILE编译期检查“12 万比 8 万多 5 万”、“3 天后是周五”机器可判,当场复算,有错就报 ERROR
LINK链接外部库“《科学》创刊于 1880 年”编译器管不了,交给"链接器"查外部符号表;查不到就是 undefined reference
COMMENT注释“这大概是今年最重要的新闻”观点/预测句,编译器不判真伪,跳过、不放行

这里有个关键观念:查不到 ≠ 没问题。链接器找不到符号时会直接报错停下,不会假装链接成功。放到文本核验上就是一句话:检索不到证据的事实声明,不能放行。

代码 1:安全求值——编译器的第一课是别用 eval

AI 文本里抽出的算术式要算一遍。第一反应是 eval("300 + 45")——打住,eval 能执行任意代码,文本里混入 __import__("os") 你就完了。正确做法是用 AST(抽象语法树)做白名单,只放行数字和四则运算:

"""给自然语言装一个「编译器」。

数学有 Lean:证明里错一步,编译就过不去。
自然语言没有编译器:AI 写错一个数,句子照样通顺。
这个脚本演示「声明编译器」的三段式:
  解析(从文本抽出声明)→ 编译(机器可判的,确定性复算)→ 链接(事实类,检索核验)
链接不到的事实就是 undefined reference;观点句是注释,编译器不查。

纯标准库,Python 3.10+。保存为 nlcompile.py 后 `python nlcompile.py` 跑 demo。
"""
from __future__ import annotations

import ast
import re
from dataclasses import dataclass
from datetime import date, timedelta

# ---------- 前端:安全求值 ----------
# 编译器第一课:别用 eval()。eval 能执行任意代码,
# 这里用 AST 白名单——只允许数字和 +-*/,其它语法树节点直接拒。
_BIN = {ast.Add: lambda a, b: a + b, ast.Sub: lambda a, b: a - b,
        ast.Mult: lambda a, b: a * b, ast.Div: lambda a, b: a / b}
_UNARY = {ast.USub: lambda a: -a, ast.UAdd: lambda a: +a}


def safe_eval(expr: str) -> float:
    def walk(node: ast.AST) -> float:
        if isinstance(node, ast.Expression):
            return walk(node.body)
        if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)):
            return float(node.value)
        if isinstance(node, ast.BinOp) and type(node.op) in _BIN:
            return _BIN[type(node.op)](walk(node.left), walk(node.right))
        if isinstance(node, ast.UnaryOp) and type(node.op) in _UNARY:
            return _UNARY[type(node.op)](walk(node.operand))
        raise ValueError(f"非法语法:{ast.dump(node)}")
    return walk(ast.parse(expr.strip(), mode="eval"))

思路跟编译器一样:别信输入文本,只认白名单里的语法结构。一句话:文本是数据,不是代码。凡是处理 AI 输出的系统,都得守住这条。

代码 2:解析——从文本里抽出"能判的"声明

# ---------- 声明:三种「可判定性」 ----------
# COMPILE:机器可判(算术、日期)——有标准答案,当场复算
# LINK:   检索可判(事实、年份)——编译器管不了,交给链接器查外部符号表
# COMMENT:判不了(观点、预测)——编译器眼里的注释,跳过但不放行
@dataclass
class Claim:
    phase: str
    raw: str
    data: tuple


def _wan(s: str, unit: str | None) -> float:
    return float(s) * (10_000 if unit == "万" else 1)


def parse_claims(text: str) -> list[Claim]:
    """lexer + parser:只收结构完整的声明,收不进来的句子不参与编译。"""
    claims: list[Claim] = []
    for m in re.finditer(r"([\d.]+)\s*(万)?[^\d。;]{1,15}?比[^\d。;]{0,10}?"
                         r"([\d.]+)\s*(万)?[^\d。;]{0,6}?多(?:了)?\s*([\d.]+)\s*(万)?", text):
        a = _wan(m.group(1), m.group(2)); b = _wan(m.group(3), m.group(4))
        c = _wan(m.group(5), m.group(6))
        claims.append(Claim("COMPILE", m.group(0), ("sub", a, b, c)))
    for m in re.finditer(r"([\d.+\-*/x×()()][\d.+\-*/x×()()\s]*?)\s*=\s*([\d.]+)", text):
        claims.append(Claim("COMPILE", m.group(0),
                            ("expr", m.group(1).replace("×", "*").replace("x", "*"),
                             float(m.group(2)))))
    for m in re.finditer(r"(\d+)\s*天后是(周[一二三四五六日天])", text):
        claims.append(Claim("COMPILE", m.group(0), ("date", int(m.group(1)), m.group(2))))
    for m in re.finditer(r"《([^》]+)》[^\d。;]{0,4}?创刊于\s*(\d{4})\s*年", text):
        claims.append(Claim("LINK", m.group(0), ("founded", m.group(1), int(m.group(2)))))
    # 带观点词的句子一律按注释处理(放最后,机器可判的优先)
    for s in re.split(r"[。;]", text):
        if any(w in s for w in ("最", "大概", "可能", "将会", "无疑")) \
                and not any(c.raw in s for c in claims):
            claims.append(Claim("COMMENT", s.strip(), ()))
    return claims

两个设计决定要说清楚:

先抓谁,有讲究。 机器能判的模式先抓,观点判断放最后。比如"这大概是最大的 3 + 4 = 7 的增长"这种句子,只要里面有能复算的算式,就先按算式判,不能因为有"大概"俩字就整句放行。

观点句不判对错。 编译器不检查注释写得好不好,咱们这个编译器也不判观点的真伪——标出来,转人工。这不是功能缺失,是这事儿本来就不归机器管。

代码 3:编译 + 链接——三类声明各走各的门

# ---------- 编译 + 链接 ----------
_WEEKDAYS = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]


@dataclass
class Diag:                      # 诊断信息,对齐编译器的 error / warning
    level: str                   # OK / ERROR / LINKED / UNDEFINED / SKIPPED
    raw: str
    msg: str

    def render(self) -> str:
        icon = {"OK": "✅", "ERROR": "❌", "LINKED": "🔗",
                "UNDEFINED": "⚠️", "SKIPPED": "💤"}[self.level]
        return f"{icon} {self.level:<9} {self.raw}  --  {self.msg}"


def compile_text(text: str, *, anchor: date, corpus: dict[str, int]) -> list[Diag]:
    """anchor:「N 天后」的锚定日期。corpus:外部符号表(检索结果的替身)。"""
    diags: list[Diag] = []
    for c in parse_claims(text):
        if c.phase == "COMMENT":
            diags.append(Diag("SKIPPED", c.raw, "观点/预测句,编译器不判真伪,转人工"))
            continue
        if c.phase == "LINK":
            _, name, year = c.data
            if name not in corpus:
                diags.append(Diag("UNDEFINED", c.raw,
                                  f"undefined reference to 「{name}」:语料查不到,别放行"))
            elif corpus[name] == year:
                diags.append(Diag("LINKED", c.raw, f"符号表核对一致({name} = {year})"))
            else:
                diags.append(Diag("ERROR", c.raw,
                                  f"声称 {year},符号表是 {corpus[name]}——事实性幻觉"))
            continue
        # COMPILE
        kind = c.data[0]
        if kind == "sub":
            _, a, b, claimed = c.data
            actual = a - b
            if abs(actual - claimed) < 1e-9:
                diags.append(Diag("OK", c.raw, f"{a:g} - {b:g} = {actual:g},编译通过"))
            else:
                diags.append(Diag("ERROR", c.raw,
                                  f"左边 {a:g} - {b:g} = {actual:g},声称 {claimed:g},"
                                  f"差 {abs(actual - claimed):g}"))
        elif kind == "expr":
            _, expr, claimed = c.data
            try:
                actual = safe_eval(expr)
            except (ValueError, SyntaxError) as e:
                diags.append(Diag("ERROR", c.raw, f"表达式无法解析:{e}"))
                continue
            if abs(actual - claimed) < 1e-9:
                diags.append(Diag("OK", c.raw, f"{expr.strip()} = {actual:g},编译通过"))
            else:
                diags.append(Diag("ERROR", c.raw,
                                  f"{expr.strip()} = {actual:g},声称 {claimed:g}"))
        elif kind == "date":
            _, n, claimed_wd = c.data
            claimed_wd = claimed_wd.replace("周天", "周日")
            real = _WEEKDAYS[(anchor + timedelta(days=n)).weekday()]
            if real == claimed_wd:
                diags.append(Diag("OK", c.raw, f"{anchor} + {n}天 = {real},编译通过"))
            else:
                diags.append(Diag("ERROR", c.raw,
                                  f"{anchor} + {n}天 = {real},声称 {claimed_wd}"))
    return diags

注意日期复算里的 anchor:「3 天后是周几」必须锚定一个绝对日期才能判。AI 写日期爱用"明天"“三天后"这种相对词,而它的上下文里往往没有"今天几号”——日期幻觉的根源就在这。所以 anchor 是强制传入的关键字参数,藏不起来、漏不掉。

UNDEFINED 这一档得多说两句:「《柳叶刀》创刊于 1823 年」这句话本身没毛病(1823 年创刊是真的),但符号表里没收录《柳叶刀》——真的也不放行。为啥?系统验证不了它,放行它的理由就只剩"看着像真的",幻觉就是这么混过去的。编译器也是这个脾气:符号表里查不到,就是 undefined reference,链接直接失败,哪怕那个函数在别的库里真的存在。

串起来跑一遍:一段 AI 快讯的"编译报告"

demo 直接拿 ETH 新闻编一段 AI 生成的快讯:五句话、6 条声明,每种命运都埋了一条。_main 完整如下(含打印逻辑),复制就能跑:

# ---------- demo ----------
def _main() -> None:
    # 一段 AI 写的「科研快讯」,五句话五种命运
    ai_text = (
        "OpenAI 同日甩出三项科学成果,相关 Lean 证明 12 万行,比上一版的 8 万行多了 5 万行。"
        "其中复杂性理论部分用的引理合计 300 + 45 = 355 条。"
        "《科学》杂志创刊于 1880 年,《柳叶刀》创刊于 1823 年。"
        "这大概是今年最重要的 AI 新闻,没有之一。"
        "外部审查 3 天后是周五公布初步意见。"
    )
    corpus = {"科学": 1880}          # 符号表里只有《科学》,没有《柳叶刀》
    anchor = date(2026, 10, 28)      # 周三

    print("=" * 66)
    print("输入(AI 生成的快讯):")
    print(f"  {ai_text}")
    print(f"锚定日期:{anchor}(周三)  符号表:{corpus}")
    print("=" * 66)
    diags = compile_text(ai_text, anchor=anchor, corpus=corpus)
    for d in diags:
        print(d.render())
    print("=" * 66)
    n_err = sum(1 for d in diags if d.level == "ERROR")
    n_ok = sum(1 for d in diags if d.level in ("OK", "LINKED"))
    n_skip = sum(1 for d in diags if d.level in ("UNDEFINED", "SKIPPED"))
    print(f"nlcompile: {len(diags)} 个声明 → {n_ok} 通过,{n_err} 编译错误,{n_skip} 未放行")
    print("结论:有错就拦,查不到就不放,观点不替人下结论——像编译器一样。")
    print("=" * 66)


if __name__ == "__main__":
    _main()

python nlcompile.py 的实际输出(本地实测):

==================================================================
输入(AI 生成的快讯):
  OpenAI 同日甩出三项科学成果,相关 Lean 证明 12 万行,比上一版的 8 万行多了 5 万行。其中复杂性理论部分用的引理合计 300 + 45 = 355 条。《科学》杂志创刊于 1880 年,《柳叶刀》创刊于 1823 年。这大概是今年最重要的 AI 新闻,没有之一。外部审查 3 天后是周五公布初步意见。
锚定日期:2026-10-28(周三)  符号表:{'科学': 1880}
==================================================================
❌ ERROR     12 万行,比上一版的 8 万行多了 5 万  --  左边 120000 - 80000 = 40000,声称 50000,差 10000
❌ ERROR     300 + 45 = 355  --  300 + 45 = 345,声称 355
❌ ERROR     3 天后是周五  --  2026-10-28 + 3天 = 周六,声称 周五
🔗 LINKED    《科学》杂志创刊于 1880 年  --  符号表核对一致(科学 = 1880)
⚠️ UNDEFINED 《柳叶刀》创刊于 1823 年  --  undefined reference to 「柳叶刀」:语料查不到,别放行
💤 SKIPPED   这大概是今年最重要的 AI 新闻,没有之一  --  观点/预测句,编译器不判真伪,转人工
==================================================================
nlcompile: 6 个声明 → 1 通过,3 编译错误,2 未放行
结论:有错就拦,查不到就不放,观点不替人下结论——像编译器一样。
==================================================================

6 条声明,三条流水线,各归各的:算术错的当场编译错误(“12 万比 8 万多 5 万”,差了整整 1 万,人眼扫过去毫无感觉);事实对上的链接成功;符号表没有的(哪怕它是真的)undefined reference;观点句跳过不背锅。

这就是"给自然语言装编译器"的意思:AI 该怎么写还怎么写,但写完能不能发出去,先过机器这道关。

边界也得说清楚

  1. 覆盖范围取决于模式库。 没收录的句式抓不到,抓不到就不参与编译——宁可漏,不可错抓。生产系统里这层是 LLM 抽取 + 规则兜底,比这里的正则宽得多。
  2. 符号表必须可信。 corpus 是"编译器信任的外部库",它错了,LINKED 就是给错误盖章。生产里这一层是搜索核验,要多来源交叉。
  3. 判不了不等于没用。 把观点句、查不到的声明明确标出来,读者至少知道哪几句没人担保——这比"全文看起来都很可信"诚实得多。

现成的"完整版编译器":AIHCC

上面是 176 行的教学版。把三条流水线做到生产级,是 AIHCC 的 /detect 正在做的事:提交一段文本,它逐句抽声明、分类——数字和日期走确定性复算,事实声明走搜索核验(多源取证,相当于一个真符号表),最后逐句标注并给出存疑句数(summary.red)。注册即得 API Key,免费 3 次/天,也有 MCP 可以直接挂进你的 Agent 工具链。

它的 Duet 则把这道"编译"放在了定时任务的出口:每天自动生成的晨报先过核验,有存疑句、或者没核验成的,一律转人工审批,你点头才标记可发布。说白了:AI 负责写,"编译器"负责拦,你负责点最后那个头。

写在最后

这次 OpenAI 的三项成果,我觉得最值得琢磨的不是"AI 又证出了什么",而是它默认了一条规矩:AI 给出的重大结论,必须附一份机器能校验的证明,不然不作数。 数学界有 Lean,AI 证伪 ETH 才有人敢听;自然语言没这东西,AI 写错你周报里的日期,你只能自己扛。

在"自然语言的编译器"普及开来之前,每次要把 AI 写的东西发出去时,都值得多问一句:

这段话,编译过吗?

代码说明:全文代码合并为 nlcompile.py,基于 Python 3.10+ 标准库(ast / re / dataclasses / datetime),无第三方依赖,demo 输出为本地实测结果(Python 3.12.2);demo 中的快讯文本为演示用合成内容(其中《柳叶刀》1823 年创刊属实,此处用于演示"语料查不到不放行")。ETH 证伪等三项成果为 OpenAI 宣称、Lean 证明已公开、尚待学界最终确认,且与 P vs NP 无关;报道细节以量子位原文及 OpenAI 官方公告为准。AIHCC /detect 与 Duet 的功能以官方页面为准。

#OpenAI #大模型 #AI幻觉 #Python #Agent安全

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

forest_09

您的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值