大模型程序性知识--错一步与步步错

目录
摘要
程序性错误指模型事实都对、流程却出错的失败形态。
本文把这类错误拆成五种类型,给出与幻觉的两段式区分方法。
再量化错误传播,讲清归因手段、稳定性口径与分治修复。
1. 程序性错误的五类型
大模型评测里,答错常被笼统归为幻觉。但多步任务中还有另一类失败:每一步引用的事实都对,结果仍然错了。
这类失败的根源不在知识,而在流程。流程错误有稳定的形态,可以分类,可以判定,也可以分治修复。本章先给出五类型框架。
1.1 五类型总览:流程错在哪一层
认知心理学把知识分成两类。陈述性知识回答“是什么”,程序性知识回答“怎么做”。
大模型的程序性知识体现在多步任务里:拆解目标、编排步骤、调用工具、核对返回、汇总结论。任何一环走样,都属于程序性错误。
按出错位置划分,五类型覆盖了计划层、时序层、分支层、接口层与兜底层。
图 1:程序性错误五类型结构图
五类型分两族:结构族管步骤之间的组织方式,接口族管与工具和环境的交互质量。判定时先定族,再定类型,最后找证据。
| 类型 | 出错位置 | 一句话定义 | 典型现场 |
|---|---|---|---|
| 步骤遗漏 | 计划层 | 漏做必要环节 | 数据迁移忘了先建索引 |
| 顺序颠倒 | 时序层 | 先后错位触发故障 | 先删旧表再做备份 |
| 条件误判 | 分支层 | 分支走错 | 检索为空该重试却直接放弃 |
| 参数错传 | 接口层 | 实参与形参不匹配 | 把城市名传给国家参数 |
| 异常路径缺失 | 兜底层 | 出错后无兜底动作 | 上游超时仍继续生成结论 |
表 1:五类型速查表
速查表的用途是沟通口径。排查故障时,先和团队对齐错误属于哪一层,再往下钻细节,能省掉大量无效争论。
1.2 结构族:漏步骤、乱顺序、错分支
步骤遗漏最隐蔽。必要环节没出现在轨迹里,没有任何报错,直到下游产出异常才暴露。
比如代码发布任务,模型直接执行部署,跳过了测试环节。部署本身成功,故障在几小时后才出现。判定特征是清单差集:要求环节减去实际环节,缺项即证据。
顺序颠倒的破坏力更强。每个步骤单独看都正确,先后一换就出故障。
典型例子是先执行删除、后执行备份。两个动作都按规范完成,数据却已经丢了。判定特征是顺序约束被违反:写明 A 必须先于 B,轨迹里 B 的位置却靠前。
条件误判发生在分支点。模型选了错误的分支,或者把条件判反了。
比如工具返回空列表,正确路径是改写查询重试,模型却走进了直接总结的分支。判定特征是期望分支与实际分支不一致,且分歧点附近有明确条件表达式。
三类结构错误有一个共同点:轨迹本身语法合法,每步都在做事,错在组织方式。这也是它们容易被漏检的原因,日志里没有任何一步真正失败。
边界情况要留意:结构错误常与事实错误交织。模型先幻觉了一个前提,再按错误前提走了错误分支。此时修复分支谓词没用,得回到第 2 章的两段式判定,先定位主导错误。
1.3 接口族:参数错传与异常缺失
参数错传是工具调用场景的高频错误。实参的名字、类型或语义与工具预期不匹配,张冠李戴。
常见形态有三种:一是字段错位,把 user_id 传给了 user_name;二是单位错误,把毫秒传给了预期秒的参数;三是类型错误,把字符串 ID 传成了数字。工具往往不报错,只是安静地查错对象。
异常路径缺失指流程只写了顺利路径。上游返回空、超时、权限拒绝时,流程没有对应动作,错误被无声传递。
后果是连锁放大:检索为空,模型拿空结果继续摘要,产出一篇看似通顺实则无据的结论。这类输出表面上像幻觉,根因却是流程缺兜底,修复手段完全不同。
下面用规则式分类器把五类型固化成代码,输入轨迹特征,输出类型与证据。
# 来源:自实现 / proc_error_classifier.py
# 规则式程序性错误分类器:按优先级依次检查五类特征
REQUIRED = ["建表", "鉴权", "写入", "校验"] # 任务要求的必要环节
ORDER_RULES = [("鉴权", "写入")] # 声明顺序约束:前项须先执行
TOOL_SCHEMA = {"query": ["city", "date"], "send": ["user_id", "msg"]}
def classify(trace):
"""trace 为轨迹特征字典,返回(错误类型, 证据)二元组"""
done = trace.get("steps", [])
# 规则一:必要环节缺失,优先级最高
missing = [s for s in REQUIRED if s not in done]
if missing:
return "步骤遗漏", "缺失环节: " + ",".join(missing)
# 规则二:顺序约束被违反
for first, second in ORDER_RULES:
if done.index(first) > done.index(second):
return "顺序颠倒", second + "先于" + first + "执行"
# 规则三:期望分支与实际分支不一致
exp, act = trace.get("expect_branch"), trace.get("branch")
if exp and act and not exp == act:
return "条件误判", "期望" + exp + "实际" + act
# 规则四:实参名不在工具形参表中
for tool, args in trace.get("calls", {}).items():
bad = [a for a in args if a not in TOOL_SCHEMA.get(tool, [])]
if bad:
return "参数错传", tool + "收到非法实参" + ",".join(bad)
# 规则五:发生过失败但无兜底动作
if trace.get("had_failure") and not trace.get("fallback"):
return "异常路径缺失", "步骤失败后无兜底动作"
return "未检出", "五类规则均未命中"
if __name__ == "__main__":
bad = {"steps": ["建表", "写入", "鉴权", "校验"], # 写入跑到了鉴权之前
"branch": "放弃", "expect_branch": "重试",
"calls": {"query": ["city", "date"]}, "had_failure": False}
print(classify(bad))
# 输出:('顺序颠倒', '写入先于鉴权执行')
分类器的规则顺序就是判定优先级:先看缺不缺环节,再看顺序、分支、参数,最后看兜底。顺序颠倒的证据会先于条件误判返回,因为漏掉鉴权的写入更危险。
工程映射:这套规则可以直接嵌进轨迹审查流水线。每次执行后跑一遍,命中即记录类型与证据,供第 6 章的修复路由器消费。
2. 与幻觉的区分判定
程序性错误和幻觉的修复方向完全不同。前者改流程约束,后者改知识获取方式。混着修,两边都见效甚微。
本章给出一套可操作的判定流程,先用对照表划清边界,再用两段式检查落到轨迹上。
2.1 陈述性与程序性:分界与对照
幻觉是陈述性错误:输出内容与可验证事实不符,比如编造论文编号、虚构不存在的接口。
程序性错误是另一个层面:每个中间产出都经得起核对,错在环节组织、先后次序或分支选择。
| 维度 | 陈述性错误(幻觉) | 程序性错误 |
|---|---|---|
| 错误层面 | 内容与事实不符 | 流程与规范不符 |
| 检查对象 | 每步输出的真实性 | 步骤集合、顺序、分支、参数 |
| 核对手段 | 外部检索、规则校验 | 清单比对、顺序约束、schema 校验 |
| 典型表现 | 引用不存在的来源 | 事实都对但结果错误 |
| 修复方向 | 接地检索、引用约束 | 流程守卫、清单、兜底分支 |
| 复发特征 | 换个问法就消失或变形 | 同类任务稳定复现 |
表 2:两类错误对照表
最后一行是经验判据:程序性错误有结构性,同类任务会稳定复现。幻觉受采样影响更大,波动明显。复现稳定性本身就是一个弱信号。
灰色地带要承认:两类错误常常串联。模型先幻觉一个不存在的工具参数默认值,再据此走进错误分支。此时两类证据都在,需要判断主导方。
判断标准看因果链起点:最早出现的错误是什么类型,主导方就是什么类型。先错事实再错流程,按陈述性修;先错流程后引出事实错误,按程序性修。
2.2 两段式判定:先查事实,再查流程
判定顺序不能乱。先查流程容易把幻觉误判成流程错:模型引用了假事实,后续分支再正确也没用。
正确顺序是先事实后流程。核心推论只有一句:事实全部核对无误而结果错误,必为流程错。
图 2:两段式判定决策图
阅读顺序自上而下:第一段是内容审计,逐个核对中间产出的真实性;第二段是结构审计,比对清单、顺序、谓词与参数。两条出口对应两类修复路线。
| 事实核对 | 流程核对 | 判定结论 | 修复路线 |
|---|---|---|---|
| 全对 | 有缺陷 | 纯程序性错误 | 流程守卫与清单 |
| 有错 | 无缺陷 | 纯陈述性错误 | 检索接地与引用约束 |
| 有错 | 有缺陷 | 混合错误 | 按因果链起点定主导 |
| 全对 | 无缺陷 | 判定存疑 | 检查核对器覆盖面 |
表 3:两段式判定四象限表
第一段的核对不要依赖模型自述。有研究发现思维链解释可能不忠实:给提示加偏置后,模型答案随之偏移,解释却只字不提。该研究在 13 个 BBH 任务上让准确率最多下降约 36%。
所以事实核对必须用外部证据:真实检索结果、数据库查询、确定性规则。模型说自己查过了,不算查过。
第二段的流程核对用第 1 章的五类型规则。清单差集查遗漏,顺序约束查颠倒,分支记录查条件,schema 查参数,兜底标记查异常。四象限表给出结论后,进入第 3 章的传播分析。
3. 错误传播的乘法衰减
定位到错误类型只是开始。更关键的问题是:单步质量如何决定整条轨迹的成败。
本章用一个极简模型回答:假设每步错误率相同且相互独立,联合成功率随步数按幂次衰减。这个模型不精确,但足够解释长流程为什么难。
3.1 从单步错误率到联合成功率
设单步错误率为 p,独立假设下 n 步全部成功的概率约为 1 减 p 的 n 次方,记作 (1-p) 的 n 次幂。
直觉推论很直接:p 不变时,步数翻倍,成功率平方化衰减。单步 95 分的流程,二十步之后只剩约 36 分。
图 3:单步错误率 0.05 时的累计成功率衰减链
图中数字是 p 取 0.05 的计算结果。十步之内成功率尚有六成,超过二十步迅速塌向三成,五十步只剩零头。长流程的失败不需要任何一步很差,只需要每步都有一点小概率出错。
另一个有用的量是减半步数:成功率降到一半需要多少步。近似公式是 0.693 除以 p。p 为 0.05 时约 14 步减半,p 为 0.02 时约 34 步减半。
独立假设显然过强。真实轨迹里错误会相关:一步理解偏了,后续同方向的步骤更容易跟着偏。相关性能让实际衰减快于公式。
相关性也可能反向:模型偶尔自我纠正,把前面的错冲掉。此时实际表现好于公式。所以幂次衰减应理解为基准线,用于估算量级和比较方案,而非精确预测。
3.2 联合成功率计算器
把公式落成代码,方便在方案评审时快速查表。输入单步错误率列表与步数列表,输出衰减矩阵。
# 来源:自实现 / joint_success.py
# 联合成功率计算器:输出幂次衰减数据表与减半步数
import math
def joint_rate(p, n):
"""独立假设下,n 步全部成功的概率为 1 减 p 的 n 次方"""
return (1.0 - p) ** n
def half_life_steps(p):
"""成功率减半所需步数,精确值由对数计算"""
return math.log(0.5) / math.log(1.0 - p)
if __name__ == "__main__":
cols = [5, 10, 20, 30, 50, 100]
print("p值 " + " ".join(str(c) + "步" for c in cols) + " 减半步数")
for p in (0.02, 0.05, 0.10):
row = " ".join(f"{joint_rate(p, n):.3f}" for n in cols)
print(f"{p:.2f} {row} 约{half_life_steps(p):.0f}步")
程序输出如下表,保留三位小数。
| 单步错误率 p | 5步 | 10步 | 20步 | 30步 | 50步 | 100步 | 减半步数 |
|---|---|---|---|---|---|---|---|
| 0.02 | 0.904 | 0.817 | 0.668 | 0.545 | 0.364 | 0.133 | 约34步 |
| 0.05 | 0.774 | 0.599 | 0.358 | 0.215 | 0.077 | 0.006 | 约14步 |
| 0.10 | 0.590 | 0.349 | 0.122 | 0.042 | 0.005 | 0.000 | 约7步 |
表 4:联合成功率衰减表
表中两个数字值得记住:p 为 0.05 时百步成功率不足百分之一;想把整体成功率维持在九成,p 为 0.05 只容得下约两步,p 为 0.01 也只容得下约十步。
推论是设计准则:与其把单步做到极致,不如压缩步数。每合并两个环节,联合成功率就少乘一次衰减因子,效果等价于把单步错误率显著降低。
3.3 长流程的实证证据
乘法衰减不只是纸面模型。Faith and Fate 一文系统测量了Transformer在组合任务上的表现。
| 现象 | 论文数据 |
|---|---|
| 3位数乘3位数零样本 | GPT-4 约59%,ChatGPT 约55% |
| 复杂度继续增加 | 正确率从接近满分滑向零 |
| 错误构成 | 传播型错误多于局部型错误 |
| 隐藏错误 | 约82.3%的正确答案内部计算图至少含一个错误节点 |
表 5:Faith and Fate 关键实验结果
第三行是本文主题的直接证据:单步操作大多正确,失败来自错误沿步骤传播。组合层数加深后,完全正确的节点占比骤降,传播型错误占比反超局部型。
第四行更值得警惕:不少最终答案是对的,中间过程却已带错。这提示最终通过率会高估流程质量,只看结果会漏掉大量潜伏错误。
ReAct 论文的动机也来自同一观察:纯思维链是静态黑盒,内部推理不接地,会导致事实幻觉与错误沿推理链传播。交错推理与行动,让每一步都能拿到外部观察校准,正是切断传播链的手段。
工程映射:评估长流程任务时,必须同时报告步数与成功率,单看成功率没有意义。二十步 0.60 与百步 0.13 是完全不同的工程问题,前者修分支,后者先砍步数。
4. 步骤级归因方法
整条轨迹失败后,第一个工程问题不是怎么修,而是错在哪一步。归因错位,修复就会打偏。
本章定义两个关键概念,再给出两种互补的归因算法:二分定位与依赖图反查。
4.1 引入点与暴露点
最终失败的位置叫暴露点,最早偏离正确轨迹的位置叫引入点。两者经常相距很远。
常见形态是:第 3 步参数用错,第 4、5 步执行无误,第 6 步结论错误才显性化。盯着第 6 步修,永远修不好。
图 4:引入点与暴露点分离示意图
图中只有步骤 3 出错,步骤 4 和 5 的执行逻辑完全正确,只是输入已被污染。归因的目标是找到步骤 3,而不是暴露错误的步骤 6。
引入点的定义依赖参照系:需要一个判定步骤产出是否正确的校验器,术语叫预言机。校验器可以是规则,可以是检索比对,也可以是人工抽检。
没有校验器就没有归因,这是前提条件。校验器的覆盖面决定归因的分辨率:只能查最终答案时,归因粒度就只有整条轨迹。
另一个前提是可重放。归因算法需要在中间任意位置截断轨迹并检查状态。环境有副作用时,比如已经发出的邮件、已经支付的订单,直接重放会放大事故,需要沙箱或影子环境。
4.2 二分定位:对半切轨迹重放
二分定位借用代码调试里的二分查找。思路:检查轨迹前半段状态是否正确,正确则错误在后半,反之在前半,逐次对半缩小。
# 来源:自实现 / bisect_attribution.py
# 二分定位归因器:对半切轨迹,用状态校验器找错误引入点
def make_trace(bad_at, n):
"""构造 mock 轨迹:第 bad_at 步起状态被污染"""
return [{"step": i, "state_ok": i < bad_at} for i in range(1, n + 1)]
def bisect_first_bad(trace, check):
"""check(mid) 判定前 mid 步状态是否正确,返回引入点与重放次数"""
lo, hi, replays = 1, len(trace), 0
while lo < hi:
mid = (lo + hi) // 2 # 对半切轨迹
replays += 1 # 每次判定对应一次重放
if check(trace, mid):
lo = mid + 1 # 前半仍正确,错误在后半
else:
hi = mid # 前半已出错,错误在前半
return lo, replays # lo 即首个状态错误步骤
if __name__ == "__main__":
trace = make_trace(bad_at=3, n=8)
check = lambda t, i: t[i - 1]["state_ok"] # mock 状态校验器
step, replays = bisect_first_bad(trace, check)
print(f"引入点:第{step}步,重放{replays}次")
# 输出:引入点:第3步,重放3次
代码核心是循环里的两条分支:校验通过就把下界推到中点之后,校验失败就把上界压到中点。八步轨迹只需三次重放即可锁定引入点。
复杂度是对数级:n 步轨迹约需 log2(n) 次重放。一千步的轨迹约十次,一万步约十四次。相比线性逐段排查,成本下降两到三个数量级。
适用前提有两条。一是状态可判定:每个截断点都能回答好或坏。二是错误单调传播:一旦引入便持续存在,不会被后续步骤修正。
第二条不成立时二分会失准。错误在第 5 步被第 7 步洗掉,中点校验会误判后半段为好,定位漂移。这正是下一节依赖图方法要解决的问题。
4.3 依赖图归因:失败输出反查依赖步骤
依赖图归因不看时间顺序,看数据流向。每个步骤的输出引用了哪些前置输出,记录成边,就得到一张有向图。
归因方向是反向遍历:从失败输出出发,沿依赖边回溯,所有可达的步骤构成嫌疑集。嫌疑集与时间信息结合,最早的可疑步骤即引入点候选。
图 5:依赖图反查示意图
实线是正向数据流,虚线是归因时的反查方向。失败输出只依赖步骤 3 和步骤 4,步骤 1 和 2 通过传递闭包进入嫌疑集。反查排除不了步骤 2,但能排除与失败输出毫无数据关联的步骤。
| 维度 | 二分定位 | 依赖图归因 |
|---|---|---|
| 假设 | 错误随时间单调传播 | 错误沿数据流传播 |
| 成本 | 约 log2(n) 次重放 | 一次构图加图遍历 |
| 前提 | 状态可判定、可重放 | 每步输入引用可记录 |
| 失效场景 | 错误被中途修正 | 依赖记录缺失 |
| 强项 | 定位首个引入点 | 处理错误被洗掉的情况 |
表 6:两种归因方法对比
依赖图的价值在于处理恢复现象:错误发生后被后续步骤纠正。Faith and Fate 测得,约 82.3% 的正确答案内部仍含错误节点。二分面对这类轨迹会漏检,依赖图能保留完整痕迹。
依赖图的成本在记录侧:每个步骤都要登记输入来源。工程上通常在工具调用层统一埋点,把返回值编号并在后续引用时记录,侵入性可控。
同样要强调:归因依据是状态与工具返回,不是模型自述。前文提到的研究已经证明,思维链解释可能系统性地美化真实过程。让模型自己解释错在哪,得到的多半是合理化的故事,而不是因果链。
5. 执行不稳定与分支漂移
同一任务跑两次,一次成功一次失败,这不是玄学。程序性错误的另一个特征是不稳定,它改变了对与错的定义方式。
本章解释漂移的成因,再给出度量口径:对多步任务,成功率才是有意义的指标。
5.1 同一任务两次结果为何不同
根源在采样。温度大于零时,每一步的 token 生成都是概率采样,同一个上下文有多种延续。
多数位置上概率分布集中,采样结果稳定。到了分支点,两条路径的概率接近,微小扰动就会改变走向。这就是分支漂移。
图 7:分支漂移示意图
同一个分支点,两次执行走出两条路。两条路的难度不同:重试路径信息充足,放弃路径先天缺氧。单次成败反映的是采样落点,不是模型能力。
分支点的谓词越模糊,漂移越明显。条件写成"结果不够好就重试",什么叫不够好没有量化,模型每次都要临场解释,解释随采样波动。
把条件改成显式谓词,比如命中数少于三即重试,漂移幅度立刻收窄。这也是第 6 章把谓词化条件列为修复手段的原因。
路径分歧的规模有实证数据。Self-Consistency 论文在 GSM8K 上测得:PaLM-540B 贪心思维链约 56.5,采样多条路径后多数投票约 74.4,绝对提升约 17.9 个百分点。
| 解码方式 | GSM8K 正确率 |
|---|---|
| 贪心思维链 | 约56.5 |
| 多路径采样加多数投票 | 约74.4 |
| 绝对提升 | 约17.9个百分点 |
表 7:Self-Consistency 在 GSM8K 上的结果
提升的前提是路径分歧足够大且多数路径可对。换个角度看,这组数字也说明单条路径的成败波动巨大,一次执行的结论几乎没有统计意义。
5.2 稳定性检测器与成功率口径
度量不稳定需要主动检测:同一任务重复执行 k 次,统计成功率、答案分布与分支分歧。先看检测器实现。
# 来源:自实现 / stability_detector.py
# 稳定性检测器:重复执行并统计一致性与成功率
# mock 用固定分支序列代替真实采样,保证示例输出可复现
from collections import Counter
BRANCH_SEQ = [0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1]
def run_once(branch):
if branch == 1: # 1 表示重试分支,产出正确答案
return "answer_A", "retry"
return "answer_B", "giveup" # 0 表示放弃分支,产出错误答案
def stability(seq):
"""统计成功率、top答案占比与分支分歧情况"""
results = [run_once(b) for b in seq]
answers = Counter(a for a, _ in results)
succ = sum(1 for a, _ in results if a == "answer_A")
top, cnt = answers.most_common(1)[0]
return {"执行次数": len(seq), "成功次数": succ,
"成功率": round(succ / len(seq), 2),
"top答案": top,
"top答案占比": round(cnt / len(seq), 2),
"分支种类数": len(set(b for _, b in results))}
if __name__ == "__main__":
r = stability(BRANCH_SEQ)
print("执行", r["执行次数"], "次,成功", r["成功次数"], "次,成功率", r["成功率"])
print("top答案", r["top答案"], "占比", r["top答案占比"],
"分支种类数", r["分支种类数"])
# 输出:执行 20 次,成功 12 次成功率 0.6
# 输出:top答案 answer_A 占比 0.6 分支种类数 2
一次示例运行的结果整理成指标表。
| 指标 | 示例值 | 含义 |
|---|---|---|
| 成功率 | 0.60 | 20 次执行中 12 次成功 |
| top答案占比 | 0.60 | 最常见答案覆盖六成执行 |
| 分支种类数 | 2 | 两条分支都被走到过 |
表 8:稳定性检测示例指标
三个指标分工明确:成功率衡量能力水位,top 占比衡量输出一致性,分支种类数定位漂移是否发生在同一分叉点。分支种类数大于等于二而 top 占比低,说明漂移点就在主路径上。
由此得到口径修正:多步任务的对错必须用成功率表述,并附执行次数。20 次里 12 次成功,报告为 0.60,样本太少时置信区间过宽,结论不稳。
工程上建议最少 20 次,重要变更用 50 到 100 次。低于这个量级,把单次失败当回归、把单次成功当修复,都是在噪声里做决策。
稳定性数据还能反哺归因:把失败执行与成功执行在首个分歧步骤上对齐,分歧点即分支漂移点,通常就是需要谓词化的条件。
6. 按类型分治的修复
归因给出类型,修复按类型分治。不存在万能修法,五类错误对应五种手段,混用收效甚微。
本章先给映射总览,再落成路由器代码,最后讨论修复的能力边界。
6.1 修复手段与错误类型的映射
映射原则是针对性:每种错误错在哪一层,修复就做在哪一层。计划层的错补清单,时序层加守卫,分支层做谓词化,接口层上校验器,兜底层补异常分支。
图 9:五类型到五手段的修复映射图
映射是一对一的:左列错误类型来自第 1 章分类器输出,右列是各自的修复手段。左列顺序即判定优先级,右列手段可以叠加使用。
| 错误类型 | 修复手段 | 落地形式 | 生效条件 |
|---|---|---|---|
| 步骤遗漏 | 补步骤清单 | 计划模板加运行前勾选 | 环节清单可枚举 |
| 顺序颠倒 | 加顺序守卫 | 每步声明前置条件 | 依赖关系明确 |
| 条件误判 | 谓词化条件 | 自然语言条件改写为代码谓词 | 条件可量化 |
| 参数错传 | 参数校验器 | schema 校验加语义检查 | 工具接口有规范 |
| 异常路径缺失 | 异常分支补全 | 空结果、超时、拒绝各配兜底 | 异常类型可穷举 |
表 10:修复映射与生效条件表
第四列最容易被忽略:每种手段都有生效条件。条件不可量化的分支,谓词化做不了;接口没有规范文档的工具,校验器无从写起。
落地位置也有讲究:知识性内容放系统提示或示例里,判定性约束放运行时校验器里。提示词负责让模型通常做对,校验器负责在它做错时拦住,两层缺一不可。
顺序守卫的实现要点是前置条件断言:每个工具调用发出前,检查其声明的输入是否已就绪。不满足即拒绝执行并回退到最近检查点,代价是一次重放,收益是截断传播链。
6.2 修复策略路由器
映射表落成代码就是路由器:输入归因器给出的类型与证据,输出结构化修复方案。
# 来源:自实现 / repair_router.py
# 修复策略路由器:错误类型到修复方案的映射与证据回填
REPAIRS = {
"步骤遗漏": ("补步骤清单", "环节写入计划模板,运行前比对勾选"),
"顺序颠倒": ("加顺序守卫", "每步声明前置条件,缺项即拒发调用"),
"条件误判": ("谓词化条件", "条件改写为显式谓词,由代码求值"),
"参数错传": ("参数校验器", "按 schema 与语义双重校验实参"),
"异常路径缺失": ("异常分支补全", "空结果超时拒绝各配兜底动作"),
}
def route(error_type, evidence):
"""返回修复方案,证据字段供回填到提示词或校验器"""
plan = REPAIRS.get(error_type)
if plan is None:
return {"手段": "人工分析", "动作": "补充判定规则", "证据": evidence}
return {"手段": plan[0], "动作": plan[1], "证据": evidence}
if __name__ == "__main__":
for etype, ev in (("顺序颠倒", "写入先于鉴权执行"),
("异常路径缺失", "检索超时后未触发兜底")):
r = route(etype, ev)
print(etype, "->", r["手段"])
print(" 动作:", r["动作"])
print(" 证据:", r["证据"])
路由器的价值在证据字段。归因器产出的证据,比如"写入先于鉴权执行",要原样回填:写进守卫规则作为断言条件,或写进提示词作为反例。
回填后形成闭环:错误发生,归因定位,路由给出手段,证据变成新规则,同类错误下次被拦截。五类错误各自沉淀规则,拦截线越修越密。
未命中的类型走人工分析出口。这不是失败分支,而是规则体系的扩张入口:人工判定后把新类型补进映射表,路由器随迭代生长。
6.3 修复的边界:运行时守卫救不了能力缺失
分治修复有明确的能力边界:它提升稳定性,不创造流程知识。模型根本没学过某个流程时,守卫只会让它反复失败在被拦截的位置。
实证数据来自 AgentTuning。Llama-2-70B-chat 在多个智能体任务上近乎零分,错误分析显示多数是无效指令与重复这类初级错误。
| 模型 | ALFWorld | 知识图谱任务 | 备注 |
|---|---|---|---|
| Llama-2-70B-chat | 约6.0 | 约0.0 | 初级错误为主 |
| AgentLM-70B | 约86.0 | 约47.0 | 轨迹微调后 |
| GPT-3.5 | 约14.0 | 约27.2 | 当时商用基线 |
表 11:AgentTuning 关键对比数据
ALFWorld 属于 AgentLM 的训练内任务,数字不能直接外推。更有说服力的是 held-out 任务:总分相对提升约 176%,70B 版本接近 GPT-3.5 的水平。
这组数据划出了修复的分界线:初级程序性错误靠轨迹数据微调根治,提示词与守卫只能缓解。第 1 章分类器输出的错误分布,可以直接当作微调数据的选题依据:哪类错误高频,就重点采集哪类正确轨迹。
外部交互结构也在改进。ReAct 在 HotpotQA 上的表现:标准提示约 28.7,纯行动约 25.7,ReAct 约 27.4,与自洽结合约 35.1。在 ALFWorld 上,少样本 ReAct 比在约一千到十万条任务上训练的模仿与强化学习方法高约 34 个百分点。
两篇论文合起来的结论:结构化的推理与行动交织,配合外部观察校准,能显著减少事实幻觉与传播型错误。这属于流程结构的改进,与运行时守卫、轨迹微调互补而非互替。
成本维度上,三种修复的代价排序大致是:运行时守卫最轻,单次调用开销远低于整条轨迹重放;提示词改造次之;轨迹微调最重,但只有它能处理能力缺失。选型顺序也就明确了:先上守卫止血,再改提示词与结构,高频且守卫无效的错误最后走微调。
7. 总结
程序性错误是知道是什么、做错怎么做的失败,分五型:漏步骤、乱顺序、错分支、错参数、无兜底。判定口诀是两段式:先逐步核对事实,事实全对而结果错,必为流程错。
量化的核心是乘法衰减:单步错误率 p 下,n 步联合成功率约为 (1-p) 的 n 次方,减半步数约 0.693 除以 p。长流程先砍步数,再谈单步质量。
归因区分引入点与暴露点:二分定位约 log2(n) 次重放找首个引入点,依赖图反查处理错误被洗掉的轨迹,两者互补。依据永远是状态与工具返回,不是模型自述。
不稳定的任务是采样随机性在分支点的漂移,正确口径是成功率加执行次数。修复按类型分治:清单、守卫、谓词、校验器、兜底分支各管一层,能力缺失则交给轨迹微调,结构改进交给推理与行动的交织设计。
外部引用
- ReAct:推理与行动协同原始论文:https://arxiv.org/abs/2210.03629
- Self-Consistency 多路径采样与多数投票:https://arxiv.org/abs/2203.11171
- Faith and Fate:Transformer组合推理能力边界:https://arxiv.org/abs/2305.18654
- AgentTuning:智能体轨迹指令微调:https://arxiv.org/abs/2310.12823
- 思维链解释的不忠实性研究:https://arxiv.org/abs/2305.04388
- 程序性知识条目(认知心理学背景):https://en.wikipedia.org/wiki/Procedural_knowledge
更多推荐



所有评论(0)