可验证智能最近成了绕不开的话题。原因很简单——大模型越用越顺手,可它的“一本正经胡说八道”也让人越来越不踏实。代码审查、数学推导、流程规划、需求拆解,这些场景下我们需要的不是“看起来没问题”,而是“每一步都有依据、能复现、可审计”。这一波需求把“循证收敛递归”这类思路推到了台前,某高校团队开源的 VeriLoop E2 框架,做的就是这件事:把验证本身做成一个循环,让结论在证据的支撑下逐步收敛,复杂问题递归拆解到可验证的粒度。这篇文章就从我的视角把这个框架拆开聊清楚——它是怎么设计的、实际跑起来要注意什么、哪些坑我已经踩过了。
1. 可验证智能到底是什么,以及为什么单靠大模型不够
先别急着看框架,得先把“可验证智能”这个目标想明白。它不是一个营销词,而是对AI系统输出质量的一种硬性要求:一个结论不仅要正确,还要能说出来“为什么正确”,并且其他人可以依据同样的证据链复现这个结论。传统的大模型调用,本质上是“一次性黑盒生成”——你把提示词塞进去,模型吐出一段答案,你只能凭感觉判断它行不行。这种模式在低风险场景没问题,但在需要质量背书的场景里,完全不够用。
1.1 大模型输出的信任危机是结构性的
很多人以为大模型输出不可信,是因为“模型还不够聪明”。实际不是这样。哪怕模型能力再强,只要它的推理过程不可见、不可复核,它的正确性就是一种概率,而不是一种承诺。我见过太多案例:模型在数学题上答案是对的,解题步骤里却藏着一个根本说不通的中间等式;生成代码能通过语法检查,却在某个极端输入下踩到未定义行为。这种错误,你单看最终结果根本发现不了,因为问题是“嵌入”在过程里的。
更麻烦的是,如果让大模型自己检查自己的输出,很容易陷入自我确认的循环。LLM-as-Judge 之所以不可全信,是因为它评估时也会受到表述长度、语气、格式这些表面因素干扰,而且它和生成器共享同一套知识盲区。用同一个模型的同一个能力去验证同一个模型的输出,本质上是一个人既当运动员又当裁判,出错概率不会因为多问几遍就下降。
还有一个被低估的问题:错误是会在链条上传导的。前面一个小环节出错,后面所有环节都会跟着错,而且越往后越看不出问题根源在哪。举个例子,AI Agent 要做“规划-执行-汇总”三步任务,如果规划阶段理解错了用户意图,执行得再完美也是白搭。可你要是不看过程,只看最终汇总,会觉得一切正常。可验证智能要解决的就是这个结构性问题——它不追求模型不出错,而是追求一旦出错,你能迅速知道错在哪、为什么错、如何修正。
1.2 从“结果校验”到“过程可审计”的层次跃迁
如果给验证能力分层,大概是这样的:
| 层次 | 验证内容 | 典型手段 | 可靠程度 |
|---|---|---|---|
| 浅层 | 格式、语法、结构 | 正则、解析器 | 高但覆盖面窄 |
| 中层 | 约束满足、语义一致性 | 规则引擎、向量相似度 | 中等,依赖规则质量 |
| 深层 | 逻辑正确性、过程可复现 | 符号执行、多源交叉、递归验证 | 高,成本也高 |
我们平时用得最多的其实是浅层验证。比如前端表单校验、接口返回格式验证,这些当然有用,但它们只能证明“输出长得符合要求”,不能证明“输出内容是可靠的”。VeriLoop E2 这类框架把重心放在中深层:它要求每个结论都挂上证据链,证据链里的每个环节都要能继续递归追查下去,直到追到可以直接判定真伪的原子事实。这才是真正意义上的“可验证智能”。
顺带说一句,浅层、中层、深层不是互斥的,实际项目中往往是叠加使用的。框架里可以先跑语法检查,再跑规则校验,最后做递归逻辑审查。分层设计的价值在于:能用便宜手段过滤掉的,绝不动用昂贵手段,这也是性能优化的大方向。
2. 循证、收敛、递归:三个关键词拆解设计思路
VeriLoop E2 这个框架最核心的部分不在代码,而在它的方法论。标题里“循证收敛递归”六个字,每一个都有明确指向。把这三个词理解透了,你自己去实现一个类似系统也没问题。
2.1 循证:让每个断言都有据可查
“循证”是整套逻辑的地基。它的意思是:任何一条结论,都必须附带支持它成立的证据。这个“证据”不是聊天记录里的“我觉得”,而是结构化的、可检索的、能追溯到来源的信息。
在设计上,证据大体分两种。一种是直接证据,也就是某个验证器在检查时自己生成的中间结果,比如一个代码片段执行后的返回值、一个数学表达式的化简结果、一条规则命中的日志。另一种是派生证据,由多个基础证据组合推导出来的结论,比如“代码通过类型检查”这个结论,可能是“变量声明正确”“函数签名匹配”“调用参数类型一致”这三条基础证据共同推导出来的。
循证设计的关键,是不允许出现“无源断言”。框架里每一条状态变化、每一个最终结论,都要能说出它基于哪些证据、由哪个验证器在什么条件下生成。这样做一方面是为了审计,另一方面是为了错误定位——一旦最终结果不对,你可以沿着证据链往回走,看看是哪一步的证据出了问题。
这里有一个实操中的细节值得注意:证据不是越多越好。证据爆炸反而会让验证慢下来,也会把真正关键的信息淹没掉。我后来自己实现的时候,给证据设计了等级和有效期,高等级证据优先参与推导,低置信度或者过期的证据直接下沉,不参与最终结论的聚合。
2.2 收敛:验证过程必须有方向
“收敛”这个概念是从数学借过来的。它指的是:随着验证轮次的推进,系统对“结论是否成立”的确定性应该不断提高,而不是原地打转或者越变越模糊。
为什么需要收敛?因为一次验证往往得不出确定结论。验证器的判断可能是“不确定”“证据不足”“需要更多信息”。这时候你不能让流程卡死,也不能简单地重试一遍——重试大概率得到同样的不确定。正确的做法是:识别出当前的不确定来自哪个子断言,针对它去补充新的证据或做更细粒度的分解,然后在下一轮用新证据去逼近那个断言的真伪。
框架里实现收敛的一个典型手段,是维护一个“待验证断言集合”,每一轮结束后,把已经验证通过的断言移出集合,把新产生的不确定断言加入集合。这个集合的规模会越来越小,直到变成空集或者达到某个明确的上限。这个过程就像排查网络故障时用二分法定位问题——每次缩小一半范围,最终锁定故障点,而不像无头苍蝇一样乱试。
这里我得坦白一个踩过的坑:如果收敛的度量设计得不好,很容易出现“伪收敛”。也就是说,系统显示不确定性在下降,其实是因为验证器开始倾向于给出“差不多就行”的宽松判断,而不是真的把问题查清楚了。所以收敛指标不能只看轮次数,还要看每一轮实际处理了多少断言、有多少断言是从不确定变成确定的、有多少是直接被放弃的。
2.3 递归:把复杂验证拆到原子粒度
递归解决的是“一次验证不了那么深”的问题。一个复杂的结论,比如“这段代码实现了一个正确的排序算法”,是不可能被一个单一检查覆盖的。正确的思路是把它拆成子问题:“输入输出类型是否合规”“是否能处理空数组”“在边界条件下是否不崩溃”“时间复杂度是否在预期范围”。每个子问题还能继续拆,直到拆到可以直接用规则、执行、或者简单断言来判定的程度。
这个思路和程序员写单测的经验很像。你不会只写一个“测试整个系统”的巨型用例,而是会拆成颗粒度合适的单元测试、集成测试、端到端测试。递归验证也是同样的逻辑,只不过测试用例的执行顺序和依赖关系是动态生成的,而不是事先写死的。
递归还有一个容易被忽略的好处:子问题的结果可以复用。在验证过程中,很多子断言是重复出现的,比如“数组不为空”“对象不是null”“文件存在”。如果把每次递归的结果缓存起来,同一个断言下次再遇到时可以直接命中,不用重复计算。这个优化在实际运行时能省下大量的大模型调用开销,尤其在一个流程里多个地方引用同一个中间结论时,效果几乎是立竿见影的。
3. 核心组件拆解与关键参数选择
有了方法论的框架,接下来就要落地看组件了。一个可验证智能系统能不能用,通常取决于三个东西的设计:验证器、证据库、递归控制器。每一样都有不少门道,我一个个说。
3.1 验证器:不要指望单一模型能扛下所有
验证器是这个系统里真正“干活”的组件。很多人在设计时犯的第一个错误,就是试图用一个通用大模型去判断一切。这会让整个系统的可靠性天花板变得很低——大模型能处理语义判断,但对于“1+1是否等于2”这类精确逻辑,它的置信度评估并不可靠。
我建议的做法是搭建一个“验证器组合”:
- 规则验证器:把业务规则硬编码成可执行检查,比如枚举校验、格式校验、权限校验。这类验证器速度快、结果确定,适合做第一道闸门。
- 执行验证器:把代码或者命令真正跑一遍,用实际输出来证明结论。比如验证一段SQL是否合法,直接执行;验证一个数学过程,用符号计算库去化简。
- 语义验证器:让大模型对“两个表述是否等价”“这个结论和证据是否相关”做判断。这一层处理的是没法机器判定的模糊问题。
- 递归验证器:把断言拆成子断言,然后下发到上面的验证器去处理。也就是说,递归机制本身也可以看作一种验证器。
一个值得注意的点是组合的顺序。经验法则是:先跑便宜的、确定性的验证器,再跑贵的、概率性的验证器。因为确定性验证器一旦失败,就能直接给出明确结果,不需要继续往下折腾。只有当确定性验证器全部通过或者返回不确定时,才值得动用大模型做语义判断。
3.2 证据库:结构化存储是基础,防循环论证是关键
证据库是整套系统的记忆中枢。简化版实现里你可以用一个表格或者文档来存证据,但要想支撑复杂场景,最好还是结构化地管理。
每条证据至少应该包含这几个字段:证据ID、内容哈希、来源验证器、生成时间、置信度、依赖证据列表。这里“依赖证据列表”很重要,它构成了证据链的图谱结构,方便后续追查。
实操中比较麻烦的是循环论证问题。也就是断言A依赖证据B,断言B又依赖证据A,两边互为前提,最后谁都没法独立成立。这类问题的出现往往不是有意为之,而是递归拆解时没有做好依赖检查。解决方法是:证据库在记录依赖关系时检测环路,一旦发现某个证据直接或间接地依赖自身,就把它标记为“不可靠”并从有效证据集中剔除。
另一个经验是给证据内容做哈希。同一份证据如果被反复生成,内容哈希就能快速发现重复,避免同一个结论被重复计算并无限占用资源。刚开始我以为这只是一个优化点,后来发现它已经不只是优化了——在复杂任务验证中,重复证据如果不去重,会导致递归队列指数级膨胀,整个系统直接跑死。
3.3 递归控制器:深度、宽度、预算三件套
递归控制器主管两个事:什么时候往下拆,什么时候停下来。设计不好会出各种幺蛾子,最典型的就是递归不终止和递归爆炸。
实际操作中,我给控制器设了三个硬性参数:
- 最大递归深度:超过这个深度还没验证完的断言直接判为“未验证通过”。默认值我一般设在5到8之间,太深了就算能跑完,中间产出的证据也质量堪忧。
- 最大子断言数量:一个断言拆出来的子断言不能超过一定数量,比如10个。限制宽度是为了防止一个断言被拆成一堆细枝末节,把主目标淹没。
- 证据预算:整个验证流程允许消费的证据条数上限。预算用完了就不再接受新的递归请求,只能基于已有证据出结论,通常是“证据不足,拒绝通过”的保守结论。
这三个参数必须根据场景调。比如做代码验证时深度可以放开到10,因为代码的调用链一般比较长;做简单的规则判定时,深度2就足够了,拆深了纯属浪费。
4. 实操一场:搭一个最小可用的验证闭环
理论说了这么多,来点实际的。我以“数学应用题生成与验证”为场景,带大家把这个系统的最小闭环跑一遍。之所以选这个场景,是因为数学题的验证逻辑相对清晰,有明确的中间步骤,方便我们观察收敛和递归的过程。
4.1 搭建骨架:定义清晰的数据结构
先写一个最精简的骨架,用类似 Python 的伪代码展示核心逻辑:
@dataclass class Evidence: eid: str content: str source: str # 由哪个验证器生成 confidence: float # 置信度,建议0~1 depends_on: list # 依赖的证据ID列表 class VeriLoop: def __init__(self): self.evidence_pool = {} # eid -> Evidence self.max_depth = 6 self.max_branch = 10 self.budget = 100 def verify_claim(self, claim, depth=0): if depth > self.max_depth: return FAIL, "超递归深度上限" if len(self.evidence_pool) >= self.budget: return FAIL, "证据预算用完" # 先用便宜验证器 rule_result = self.run_rule_checker(claim) if rule_result is not None: return rule_result, self.record_evidence(...) exec_result = self.run_exec_checker(claim) if exec_result is not None: return exec_result, self.record_evidence(...) # 确定性验证器都没给出结论,进入递归拆解 subclaims = self.decompose(claim) if len(subclaims) > self.max_branch: return FAIL, "子断言过多" for sub in subclaims: sub_verdict, sub_evidence = self.verify_claim(sub, depth+1) if sub_verdict != PASS: return FAIL, sub_evidence return PASS, combined_evidence这个骨架把整条逻辑线串起来了。真到生产环境,你需要给每个验证器单独实现,但流程就是标准的“规则检查 -> 执行检查 -> 递归拆解”。
4.2 实战记录:一道应用题的完整验证过程
场景设定是让模型生成一道带完整解答过程的应用题,要求最后的答案是正确的,且解答的每一步都能验证。
第一次运行,模型给出的解答是:一个书店进了45本书,上午卖出12本,下午卖出的数量是上午的1.5倍,问书店还剩多少本?模型自己算的结果是19本。
用框架验证时,第一步的数值计算没问题:45-12-18=15,不是19。验证器在步骤二“下午卖出数量=12*1.5=18”时发现,模型在最后一步把减法算错了。这里就体现出“过程可验证”的价值了——如果你只看答案,19看起来挺合理的,其实中间算错了。
框架不会直接给出“错误”了事。它会进一步递归,把“45-12-18=15”拆成若干子断言:45-12=33、33-18=15,分别去执行验证。子断言计算通过后,系统会更新证据库,标记“最终正确答案为15”,并把模型原先的19对应的证据标记为“被推翻”。这个过程中,待验证断言集合从最初的“整道题解答正确”,缩小到“最后一步减法计算”,最后收敛到空集,同时给出了新结论的证据链:45减去12等于33,33再减去18等于15,每个节点都有执行验证器的计算结果作为证明。
如果你把验证轮次和状态变化记录下来,大概会看到这样一个过程:
| 轮次 | 待验证断言数量 | 关键操作 | 状态 |
|---|---|---|---|
| 轮1 | 1 | 整体解答验证 | 失败,定位到最后一步 |
| 轮2 | 3 | 拆解题干、拆分条件、验证子步骤 | 部分通过 |
| 轮3 | 1 | 验证最终计算 | 通过 |
| 轮4 | 0 | 证据链合并 | 收敛完成 |
实际跑起来可能不止4轮,因为模型可能还会犯条件理解错误、单位错误等,每类错误都会触发一次递归。但核心流程就是这样。
4.3 调参与观察要点
在这个场景中,最值得调的参数是递归深度和分支上限。因为数学题一般不会嵌套太多层,深度3基本足够,分支也不会太大。如果你发现验证总是超时,先看子断言的拆解粒度,很多情况下是拆得太多、太碎了。
另外,有一类问题很隐蔽:“证据链自洽但答案仍然错误”。什么意思呢?就是模型给出的解答过程中,每个中间步骤都被验证器确认没问题,但整体逻辑和题干不符。比如模型把“下午卖出上午的1.5倍”理解成了“下午卖出总书的1.5倍”。每个计算步骤都算对了,但出发点就错了。这种问题靠单纯的验证器是抓不住的,必须引入“语义一致性”检查——用语义验证器判断模型对题干的解读和题干原文是否等价。这也是为什么前面说验证器一定要组合,不能只有一类。
5. 常见问题与排查技巧实录
这个框架跑了一段时间,我用它验证过代码、做过多轮问答,也踩过不少坑。我把最有代表性的问题整理成一份速查表,希望能帮大家少走弯路。
5.1 问题一:递归不终止
这是最吓人的问题——验证流程跑着跑着就停不下来了。原因多半出在断言拆解上,一个断言被拆成子断言,子断言又被拆回原来的断言,形成了环。另一种常见情况是:同一个断言只是换了个说法又重新出现,比如“数组不为空”和“数组长度大于0”语义相同,但哈希不同,被当成两个不同断言反复处理。
至于解决方案,我建议从三方面入手。第一,在拆解时需要做语义去重;第二,严格控制递归深度和预算,超过上限直接返回未验证通过并且打上“超限”标记;第三,给每个断言生成规范化表示或者语义哈希,从源头降低重复派生的概率。
5.2 问题二:证据链自洽,但最终结论是错的
这类问题最伤脑筋,因为你会发现“验证全部通过,可结果就是不对”。原因多半是验证器的覆盖范围有盲区。比如在代码验证场景中,执行验证器只检查了正常路径,没覆盖异常路径;规则验证器只覆盖了部分业务规则,漏了一条隐蔽的后置条件。
我的解决思路是引入异构验证器做交叉验证。同一个断言,不仅用规则引擎判断,也让大模型从另一个角度复议,或者用符号执行去穷举边界条件。如果不同验证器结果冲突,那就自动进入多轮循环,把冲突本身作为一种“新证据”加入证据库,驱动下一轮收敛。我在实操中发现,这种“故意让它吵架”的做法虽然会提高验证成本,却能把很多隐蔽问题揪出来。
5.3 问题三:性能开销太大
可验证智能比普通生成慢,这是必然的。但慢也有慢的度。如果一次验证要好几分钟,那是不可接受的。我做的优化排序是:缓存命中优先、轻量验证器优先、大模型最后兜底。也就是说,能用场景缓存直接给结论的,绝对不重新跑;能用规则判断的,不用大模型。只有规则和执行都不能判定的时候,才调用语义验证器。
还有一个特别好用的优化是“预筛器”。大模型生成完结论后,先用一个很便宜的分类器判断结论是否很可能出错,只有“很可能出错”时才进入完整验证流程。大部分正常结论可以直接放行,大大降低了系统的平均成本。当然这是有取舍的,预筛器本身的失准率会带来一定的漏检,配置时需要根据业务风险调整筛选阈值。
5.4 常见问题速查表
| 现象 | 可能原因 | 排查方向 | 解决方案 |
|---|---|---|---|
| 递归不终止 | 断言拆解形成环路 | 检查断言依赖关系 | 深度上限、语义去重、环路检测 |
| 结论错误但验证通过 | 验证器覆盖盲区 | 检查未被验证的断言 | 引入异构验证器交叉检查 |
| 性能开销太大 | 大模型被过度调用 | 统计各验证器调用次数 | 缓存命中、预筛器、轻量验证器优先 |
| 结果不一致 | 验证器对同一断言给出不同结论 | 查看冲突证据 | 将冲突作为新证据驱动下一轮收敛 |
| 证据库冗余 | 断言重复产生相似证据 | 检查内容哈希 | 做归一化表示,合并相似证据 |
写在最后的个人体会
我把这套框架思路实践了几个项目之后,最大的感受是:可验证智能的价值,在于把AI从一个“结论提供者”变成了“过程管理者”。它不会让模型变成全知全能,但能让模型每一次输出都变成可以被回溯的过程。哪怕一个结论最终被证明是错的,它所走的路也会变成后续改进的镜子。
最后分享一个我自己觉得特别有用的小技巧:刚开始搭这类系统时,别急着上通用逻辑引擎或者复杂符号库。先把手头业务里最常用的规则沉淀成一个小的规则库,做成一个“最小可行验证器”,把闭环跑通。然后在反复调试中慢慢加递归深度、加语义验证器、把规则库扩展成评估规则集。渐进式搭建,比一上来就造一个超级框架要可靠得多。可验证智能这条路没有终点,但每多一个能独立验证的断言,系统的可信度就扎实一分。