ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Agent幻觉检测与置信度评估:从自一致性到事实验证的工程实践

Agent幻觉检测与置信度评估:从自一致性到事实验证的工程实践

做 Agent 相关项目时,我最怕看到的不是 Agent 说“我不知道”,而是它语气笃定地给出一个完全经不起推敲的答案。比如让某个 Agent 去查内部文档里一个接口的限流参数,它煞有介事地回复“单账号每分钟 120 次”,还贴心地附上了“来源标注”——可那份文档三个月前刚改过,限制早就变成了 60 次。

这类现象业内统一叫幻觉,但放到 Agent 场景里,它比单轮对话中的幻觉严重得多。单轮对话说错,最多影响一次问答的质量;Agent 说错,后面可能跟着真实的下单、发邮件、改配置。而且 Agent 里的幻觉会滚雪球:第一步推理编了一个错误前提,后续所有步骤都建立在这个错误前提上,最后输出一个看起来逻辑完整、实则全盘皆错的结论。

这篇文章我按实际做系统排查的路径,把整条链拆开讲:Agent 幻觉到底分几层、根因在哪、主流的幻觉检测路线各自怎么用、置信度评估怎么从 logprob 一路做到可用的校准概率,以及真实系统里怎么定阈值、做兜底。适合正在搭 Agent 应用、又不想被花式幻觉坑一把的开发者参考。

1. Agent 幻觉和普通大模型幻觉不是一回事:先分清三个层次

1.1 第一层:知识性幻觉——模型“不知道”却硬要回答

大模型本质上是一个参数量巨大的条件概率模型,训练时它从海量文本里学到的不是一份“事实数据库”,而是词语之间的统计关联。你问它一个问题,它给出的答案,本质是“在给定前缀的条件下,哪个词序列看起来最自然”,而不是“哪条事实为真”。当问题落在它的参数覆盖之外——比如太新的知识、太冷门的领域、或训练语料本身有错误——它没有能力说“我没学过这个”,只能基于统计惯性编造一个最贴近问题形态的回答。

这种幻觉在单轮对话里已经很头疼,在 Agent 场景里更隐蔽。因为 Agent 通常不是直接把话说给用户听,而是把话当成下一步行动的“依据”。一个编造出来的数值,可能直接变成某个工具调用的入参。我排查线上问题时见过:Agent 把某个项目的配额幻觉成另一个项目的配额,然后拿这个数字去改配置,差点改坏生产环境。知识性幻觉的危险不在于“答错”,而在于它伪装成可用信息进入后续链路。

1.2 第二层:推导性幻觉——推理链条半路掉链子

多步推理是 Agent 的家常便饭。一个任务往往要拆成“先查 A,再根据 A 的结果算 B,最后综合 B 下结论”。问题在于,模型做每一步时,都倾向于让“局部输出看起来自洽”。如果中间某一步的结论其实是错的,模型不会停下来报错,反而会在下一步里想方设法把这个错结论“圆过去”,生成一段逻辑上连贯、前提上虚构的推导。

用人话说,模型是个特别擅长“事后找理由”的家伙。你先给它一个错误结论,它能生成一大段论证把这个结论解释得头头是道。Agent 的多步循环恰好给了它大量“制造理由”的机会。这也是为什么很多 Agent 项目的线上错误里,最终答案往往是“结构完美、事实全错”——错误不是最后一步犯的,是早期某一步就歪了,后面每一步都在为错误打工。

1.3 第三层:工具性幻觉——Agent 特有的危险层

这一层是普通大模型幻觉没有的,也是我把 Agent 幻觉单独拿出来讲的原因。Agent 要调用工具,而工具调用过程中至少有三类幻觉高发。

第一类是“幻觉工具”。模型编造一个不存在的工具名或参数,比如文档里根本没有某个刷新参数,它却传得有模有样。第二类是“幻觉解读”。工具返回了错误码、空列表或者超时信息,模型把“查询超时”理解成“数据不存在”,把“权限不足”理解成“该用户没有记录”。第三类是“幻觉内化”。工具返回的中间结果明明有瑕疵(比如截断了、字段缺失),模型却不做校验,直接把残缺信息当成事实继续推。

工具性幻觉之所以危险,是因为它直接连到真实世界的副作用。普通幻觉只是“回答错了”,工具性幻觉是“做错了”。一个 Agent 拿着幻觉出来的参数去调用下单接口、发送邮件、修改配置,后果往往不可逆。所以后面聊检测和置信度评估,我优先保证的也是这个层面:宁可让 Agent 怂一点多问一句,也不能让它拿着幻觉自信满满地动手。

2. 幻觉从哪里来:解码采样、上下文拼装与工具结果误读

2.1 自回归生成的本质是“续写”,不是“检索”

先把这个最根本的机制讲透。大模型生成文本时,是一个 token 一个 token 往后“续写”的:每一步都在算“给定目前已生成的内容,下一个 token 最可能是什么”。它只看得到“局部流畅”,看不到“全局正确”。你给它一个问题的前缀,它顺着语言的惯性往下走,目标是让整段话读起来像人写的、听起来有道理。

这里有个特别容易误导人的地方:模型训练时见过海量“问题+答案”的文本,所以在它的内部统计上,“看起来像参考答案”的输出确实会得到高概率。但“像参考答案”不等于“是参考答案”。一个词表的概率分布完全是向量的加权混合,它对“这个问题应该长什么样”有很强的统计直觉,对“这个事实是不是真的”几乎没有概念。可以把它想象成一个人喝多了说胡话:每一句话语法都通顺,每个句子在局部也自洽,但整体内容是他虚构的。模型的高质量“胡话”能力,就藏在自回归的机制里。

2.2 上下文的“弱引用”:模型没有义务忠于你的 prompt

Agent 跑起来之后,上下文中通常塞满了系统提示、检索片段、历史对话、工具接口文档。很多人默认“模型会以这些信息为准”,但实证经验告诉我:模型对上下文的引用是概率性的,不是强制的。当上下文信息与模型自身的预训练先验冲突时,模型经常选择“更顺口”的那个,而“更顺口”的往往来自统计惯性,而不是你刚塞进去的最新文档。

还有一个常见坑是“长上下文遗忘”:模型对长上下文的开头和结尾记得比较牢,中间部分容易被忽略。Agent 的系统提示往往在开头,但真正有用的工具返回和检索片段经常被夹在中间,等模型读到需要决策时,中间信息已经被稀释。更隐蔽的是“部分引用”:模型可能只参考了上下文里某一句沾边的话,就把剩余细节用自己的先验脑补完了。这种“沾边就补全”的行为,是幻觉在 Agent 场景里最普遍的形态。

2.3 工具链的错误累积:观察结果被“内化”为事实

这是 Agent 特有的重灾区。工具返回的数据对模型来说只是一个字符串,模型需要在下一步推理里“理解”它。问题在于,模型在解读观察结果时有强烈的“合理化”倾向:它倾向于把任何返回内容都解读成“符合预期的事实”,哪怕返回内容本身是错误、截断或含糊的。

举个例子,一个查询工具按条件返回了空数组。对程序而言,空数组的可能原因有:真的没有数据、参数拼接错了、后端在降级、权限被拦。模型不会去做这些区分,它大概率直接得出结论“目标对象不存在”,并且用非常确定的语气说出来。这不是模型的恶意,而是它的训练目标里没有“对工具输出保持怀疑”这一项。一旦这个错误结论被内化为事实,后续所有步骤都会基于它展开。我在真实项目里排查高发错误时,这类“观察解读错误”占比相当高,比纯粹的“知识不知道”更常见。所以做检测时,我一定把工具调用后的解读环节单独拎出来检查。

2.4 对齐训练的隐性副作用:模型被奖励了“自信”

还有一个不太被注意的根因:对齐训练本身在制造幻觉的温床。监督微调阶段,模型被训练成“面对用户问题必须给出完整、结构化、有用的回答”,含糊其辞或直接承认不会,都会被当作差样本压低权重。到了偏好对齐阶段,标注者又普遍更偏爱语气肯定、信息丰富、听起来自信的回答。于是模型学到的隐性规则是:宁可自信地错,也不要犹豫地对。

这给下游置信度评估带来一个非常现实的问题:你用“语气”来判断模型是否确定,完全不可靠。模型可以在零把握的情况下输出一段排比工整、断言密集的句子,读起来比很多真人专家还笃定。因此后面讨论置信度时,我反复强调一个原则:绝不以模型的语言自信度为依据,只看分布层面的稳定性与外部验证结果。

3. 幻觉检测的三条主流技术路线

3.1 自一致性路线:同一个问题多问几遍

自一致性是目前幻觉检测里性价比最高的通用路线,核心思想特别朴素:如果模型真的“知道”一个事实,那么多次独立采样应该得到相同或高度相似的答案;如果答案是编的,那么每次采样的“发挥”往往不稳定,答案会飘。现实中模型可能对同一个问题给出不同措辞、不同细节、甚至不同结论,稳定性就成了“是否真实”的天然探针。

具体做法是:把同一个问题用略高的 temperature(一般 0.5 到 0.8)独立采样 N 次,然后把每次答案结构化抽取出来做语义聚类,最后计算最大聚类所占比例作为一致性分数。一致性低于阈值,就判定为可疑幻觉,进入人工确认或重试流程。伪代码大致如下:

def consistency_check(question, sampler, n_samples=5, temperature=0.7): answers = [sampler.generate(question, temperature=temperature) for _ in range(n_samples)] claims = [normalize_and_extract(a) for a in answers] # 抽取关键结论 clusters = cluster_by_semantic_equivalence(claims) # 语义等价聚类 majority_ratio = max(len(c) for c in clusters) / n_samples return majority_ratio, clusters[0] if majority_ratio >= 0.6 else None

这个方案的好处是通用、不需要外部知识、实现不复杂;缺点也很明显:成本是 N 倍生成开销,而且对“系统性幻觉”无能为力——如果模型从预训练里记住了一个错误关联,它可能每次自信地编出同一个错误答案,自一致性照样是 100%。所以自一致性适合当第一道防线,不适合当唯一防线。

3.2 语义熵:把 token 层面的概率提升到语义层面

传统上很多人会拿生成序列的平均 logprob 或 token 级熵来评估确定度,但实践中这个信号很不可靠。原因是模型表达同一个意思时可以用完全不同的措辞,比如确定地知道答案是 42,它可以写成“答案是 42”“结果就是 42”“应该拿 42”——token 层面的分布分散了,但语义高度一致。只看 token 级熵,会把“确定但措辞多样”误判成不确定。

语义熵就是针对这个问题提出的:先采样多个答案,再用自然语言推断(NLI)或语义向量相似度判断它们是否表达同一个意思,把同义的答案归为一个语义簇,最后在簇的层面计算熵。公式上就是标准的香农熵,只是概率定义在语义簇上而不是单词上:

H_semantic = -Σ_i p_i · log(p_i)

其中 p_i 是第 i 个语义簇的占比。熵越低,说明模型在语义层面高度收敛,倾向于确定;熵越高,说明模型在真正的“意思”层面摇摆,很可能是幻觉高发区。这个方案比纯 logprob 靠谱得多,是中等成本里性价比最高的。实践中要注意语义等价判断本身的质量:用 embedding 相似度最便宜但容易误判,用 NLI 模型更准但更慢,我一般对短答案用 embedding,对长答案用 NLI 两两判别。

3.3 事实验证:引入外部裁判

上面两种路线本质上都是“从模型身上找不稳定的证据”,事实验证路线则换个思路:不依赖模型自证,直接引入外部知识来核对。步骤是先把生成答案拆解成一组原子事实,每个事实是一个可单独验证的断言,比如“该接口默认超时时间为 30 秒”;然后逐条用外部来源验证——可以是检索增强生成召回的相关文档、结构化数据库、专用的判别模型;最后汇总各条事实的验证结果,计算“通过比例”作为置信度。

这个路线最大的优势是可解释性强:你能明确告诉用户“哪一句不可信”。缺点是成本和复杂度高得多,而且要小心拆解器和验证器各自的偏差——后面我会专门讲验证器本身也会幻觉的问题。在工程上,我的经验是把事实验证留给高影响场景:涉及写操作、金额、时间、配置的关键断言才逐条验证,日常闲聊式的问答不值得付出这个成本。

三条路线的对比如下:

路线依赖资源单次额外延迟可解释性对系统性幻觉的鲁棒性
自一致性无外部依赖,纯采样高(N 倍生成)中弱
语义熵语义等价判断器中中弱
事实验证检索源/知识库/验证器高强强

4. 置信度评估:从 logit 到校准,四层递进

4.1 第一层:直接用 softmax/logprob——最容易拿到,也最容易误导

很多刚接手的开发者会直接拿模型返回的 logprob 当置信度。这个想法很自然,但必须泼一盆冷水:softmax 概率衡量的是“模型认为这个词序列在语言上有多自然”,不是“这个答案在事实上有多正确”。两者关系非常弱。

我在一个内部问答集上做过简单实验:把模型输出里最高的 softmax 概率按 0.9 以上、0.8 到 0.9、0.7 到 0.8 分桶,分别统计桶内实际正确率。结果高概率桶里只有大约七成正确,中概率桶里甚至不呈现单调关系。换句话说,一个 0.95 的“置信度”背后,真实正确率可能只有 0.6 到 0.7。这类现象术语叫概率过度自信,是大模型的通病。这就好比一个天气预报员,嘴上说“90% 下雨”,但真按这个预报统计,一年下来下雨率只有 60%。他给的不是概率,只是“听起来很确定的语气”。所以 logprob 可以用,但必须经过校准,不能直接拿来做门槛判断。

4.2 第二层:自一致性置信度——把生成路径稳定性纳入考量

比 logprob 可靠得多的是基于自一致性的置信度。思路和前面检测一样:采样 N 次,看答案聚合后的支持比例。最简单的定义是:

C_consistency = N_majority / N

加权版本可以再把每条路径的平均 logprob 乘进去,让“既稳定又自然”的答案得到加分:

C_weighted = Σ_i w_i · I(answer_i ∈ majority_cluster) / Σ_i w_i w_i = exp(mean_logprob_i)

这类置信度的含义很直观:模型不是只给出一种“顺口”的答案,而是多条独立采样都指向同一个结论,稳定性本身就是知识的证据。实践中它的校准效果通常比 logprob 好一截,因为它把“生成路径的脆弱性”显式抓了出来。局限在于对系统性幻觉依然无能为力:如果模型记忆了一个错误事实,它只会稳定一致地重复错误。

4.3 第三层:概率校准——让置信度变成可信概率

有了一个分数还不够,还要让分数“指哪儿打哪儿”。比如你说置信度 0.8,那么在大量样本上就应该有大约 80% 是对的。度量这个达标程度的标准工具是期望校准误差(ECE):把样本按预测置信度分桶,然后比较每个桶内“平均置信度”与“实际准确率”之差,加权平均。

校准最常用的手段是温度缩放:在验证集上优化一个标量温度参数 T,把 logits 统一除以 T 再走 softmax。直观理解就是“统一调一调模型自信的嗓门”:如果模型整体过度自信,T 取大于 1,把概率摊平一些;如果过度保守,T 取小于 1,把概率拉开一些。温度缩放只有一个参数,几乎不会过拟合,但在管线里效果立竿见影。线上出现误判,我第一件事就是重新看校准曲线,而不是盲目调阈值。

这里有个重要的工程提醒:校准集必须来自你自己的目标分布。用公开的通用问答集校准出来的温度,放到你的 Agent 实际问题上往往失准,因为不同领域、不同难度下模型的过度自信程度完全不同。正确做法是攒一批真实线上问题,人工标注正确性,拿这批数据重新拟合温度。

4.4 第四层:Agent 级复合置信度——单点可靠还不够,全链路可靠才有用

单条回答的置信度评估得再准,到 Agent 场景仍然不够。Agent 的形态是一次任务要走完多步工具调用,最终交付结果。就算最后一步答案的置信度高达 0.95,如果前面某一环工具调用本身失败过,这个 0.95 也是建立在不牢的地基上。

所以我在工程上会构造一个 Agent 级的复合置信度,把三个信号揉在一起:步骤置信度(每步推理的自一致性或语义熵分数)、工具成功信号(工具调用是否成功、返回是否符合 schema、有无重试)、目标达成检查(最终结果是否回答到了用户原始诉求)。简化版公式可以写成:

C_trajectory = mean(步骤置信度) × min(关键步骤置信度) × 工具成功比例

这里用乘法而不是加权求和,逻辑是“任何一块短板都会压垮整体”:一次关键工具调用失败,哪怕其他步骤都自信满满,整条轨迹也不放心。这个复合置信度才是拿来和业务阈值比较的东西,低于阈值就进入下一节的兜底流程。

5. 在真实 Agent 系统里落地:检测、兜底与重试的工程决策

5.1 在哪一步拦:入口、出口还是每一步?

做检测第一件事不是选算法,而是定位置。我见过三种做法:只在最终答案出口做一次检测,成本最低,但拦不住“中间步骤已经做错事”的风险;每一步都做检测,覆盖率最高,但延迟和成本成倍上升;在入口做任务级预判,能提前拦掉本身不可达的任务,但对单步错误无能为力。

我的实际推荐是混合式:入口做一次快速“可行性与风险分诊”,用极小的成本判断这个任务是否可能完成、是否涉及高危写操作;对写操作、外部不可逆调用这类关键动作,在执行前单独做一次置信度检查;最终出口再做一次事实验证兜底。三个点分工明确,入口管“这活接不接”,关键动作管“这一步动不动”,出口管“最后的话能不能信”。

5.2 阈值怎么定:先画校准曲线再拍板

阈值不是拍脑袋定的。可靠的做法是先攒数据:收集几百到上千条真实任务轨迹,人工把每条最终答案标成“正确/部分正确/错误”,然后对每条轨迹计算复合置信度,按置信度分组画可靠性图和精度-召回曲线。有了这个曲线,你就能回答几个关键问题:置信度 0.9 以上的样本里实际正确率是多少?如果阈值定在 0.9,能拦住多少错误?又会误伤多少本来正确的回答?

举例说明。某个内部项目里,我把阈值从 0.85 调高到 0.92,高危错误拦截率从 70% 掉到了 55%,但误伤率从 18% 降到了 8%。业务侧无法接受 18% 的误伤(每五次就有一次把对的答案打回重试,体验很差),最后折中选了 0.90。这类取舍没有标准答案,完全取决于你的业务对“漏过错误”和“误伤正确”的容忍度。但如果没有校准曲线,你连取舍的依据都没有。

5.3 检测失败后的三条路:重试、改写、求助

检测出低置信度之后,不该简单粗暴地丢给用户一句“我不确定”。工程上应该按情况分级处理。我的标准决策逻辑大致是这样:

if confidence < threshold: if tool_available and likely_transient_error: retry_with_better_params() # 可能是工具抖动,换个参数重试 elif task_decomposable: split_and_verify_each_claim() # 拆开任务,逐项事实验证 else: ask_user_for_confirmation() # 兜底:把候选结果交给人确认

举个例子:Agent 查用户订单时复合置信度偏低,先判断是不是查询参数拼错了,修正参数重试一次;重试后仍然不确定,就把任务拆成“先查订单号是否存在,再查状态字段”,每一步都做个简单校验;如果拆解后还有断言过不了验证,就回到用户侧,把两三个候选结果列出来让人确认。总原则是:在错误不可逆的操作之前多花一次调用成本,永远比事后擦屁股划算。

5.4 成本控制:采样次数、延迟和质量的三角取舍

检测是有代价的,尤其是自一致性这类需要多次采样的方案。我在项目里做过一组对比:不做检测、采样 3 次、采样 5 次、采样 8 次,分别看延迟增幅和拦截效果。大致规律是采样 3 次能拿到大部分增益,再往上边际递减明显;而延迟是线性上升的。8 次采样换来的一点拦截率提升,很多时候不如把省下的算力投到事实验证上。

几个省钱技巧:第一,采样可以并行发起,聚合端用一个轻量判别模型,不增加串行延迟;第二,不是所有步骤都需要高成本检测——写操作和涉及金额、时间的步骤才用 5 次采样,普通信息查询用 3 次甚至直接单次加一个轻量熵检查;第三,把检测结果连同采样分布一起落日志,后面做校准和阈值调整都靠这批历史数据。

6. 边界条件与常见误区:为什么检测结果也不能盲信

6.1 误区一:低置信度等于幻觉,高置信度等于正确

这是最要命的误解。自一致性和语义熵判断的是“模型的稳定性”,而稳定性不等于真实性。一个错误如果深深刻进了模型的预训练权重,它会被稳定地、自信地重复。我在前面反复举例:模型多次采样给出同一个错误参数名,一致性 100%,语义熵接近 0——所有基于“模型自身不确定性”的检测全部失效。

应对办法只有一个:当答案涉及可验证的外部事实时,不能只看模型内部信号,必须上外部验证。不是说内部信号没用,而是说它只对“模型没记住、所以现场编造”的那类幻觉有效;对“模型记住了错误信息”的那类系统性幻觉,内部信号天然绝缘。理解了这条边界,你才不会把检测输出当成真理。

6.2 误区二:验证器自己也会犯错

事实验证路线引入外部裁判,但裁判也是模型,同样有幻觉和偏差。最典型的问题是“顺从偏差”:验证器在判断生成文本时,很容易被生成文本的语气和篇幅带着走,一个措辞自信又冗长的答案,更容易被验证器放行。更糟的是,如果验证器和生成器用的是同源技术,偏差还会互相放大。

我在工程上采取两条措施:一是尽量让验证器与生成器异构,生成用 A 模型,验证用 B 模型,两个模型的偏差模式不同,互相兜底的效果更好;二是定期用人工构造的“对抗样本集”评测验证器本身的准确率,把这个准确率纳入置信度的修正系数。如果验证器自身准确率只有 0.8,那它说“通过”这件事本身也要打折扣。

6.3 误区三:采样参数乱调会让检测失真

自一致性检测对采样温度非常敏感,但很多人不关心这个。温度设成 0 或极低,模型输出几乎固定,采样 N 次得到 N 个一模一样的答案,一致性虚高到接近满分;温度太高,答案被冲散,语义聚类不稳定,又会让一致性虚低。我在一个内部测试集上简单对比过几组参数:

采样温度一致性均值对已知幻觉的召回率备注
0.00.970.10一致性虚高,幻觉全漏
0.30.880.42稍有区分度
0.70.740.71区分度较好,推荐区间
1.00.610.66区分度变差,波动大

这个表只说明数量级关系,不同任务和模型的具体数字会变,但规律稳定:0.5 到 0.8 是检测采样的合理区间。还要注意,检测用的采样参数可以和正式回答的参数分开配置。正式回答想保守稳定就用低温度,检测采样用中高温度搅动一下,两害相权取平衡。

6.4 什么时候别再指望检测:直接改架构

最后必须说一句清醒话:检测和置信度评估是安全网,不是根治手段。它们只能把幻觉“拦下来”,不能让模型“不产生”幻觉。如果你的 Agent 在某个场景里天天触发检测,最该做的不是调阈值,而是回头改架构。可落地的治理手段有这些:对涉及事实的问题强制走检索增强生成,把答案锚定在检索片段上;工具调用一律做严格 schema 校验,参数名、枚举值不过就重试;能结构化输出的就用约束解码,不放开自由生成;对关键断言加规则校验,比如日期格式、金额范围、ID 存在性这类可以用程序先验的知识,根本不该让模型自由发挥。

治理和检测的正确分工是:治理从源头压低幻觉发生率,检测负责兜住漏网之鱼。两者都做,线上错误率才能压到一个可接受的水平;只做一个,迟早会在某个深夜被一条离谱的 Agent 报错惊醒。

最后分享一点个人体会。最早我做检测时只加了一个 logprob 阈值就上了线,结果误判率惨不忍睹——把一堆措辞自信但内容正确的答案全拦下来,用户投诉不断。后来换成语义熵加自一致性双通道,才算有了可用性;再后来在写操作上做了事实验证,才把高危错误真正压下去。这个演进让我明白一个道理:检测方案没有银弹,它是在“能拦多少错”和“误伤多少对”之间反复权衡的过程,而每一轮权衡都需要数据支撑。另外强烈建议把每个回答的置信度、采样分布、检测结果全部落日志。出了事故能回溯是底线,模型升级后校准曲线和旧阈值也一定要重测——我吃过一次亏,升级模型后忘了重测,旧的 0.85 阈值在新的置信度分布下形同虚设,线上漏了一大波错误。把这个流程固化下来,比调十次阈值的收益都大。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进