作为一个在AI Agent工程领域摸爬滚打多年的开发者,我几乎每天都要面对同一个让人头疼的问题:模型一本正经地胡说八道。你可以把完美的提示词、精心搭建的知识库、层层防护的工具调用协议都准备妥当,然后Agent依然会在某个不起眼的环节,给你编造一个根本不存在的用户ID,或者面不改色地告诉你某个接口调用成功了——而实际上那个接口根本没被调用过。
这个现象在行业里有一个专门的称呼:幻觉(Hallucination)。而在Agent的工程链路(Harness Engineering)中,幻觉问题被放大了无数倍。如果说单轮对话里的幻觉只是一次尴尬的“张冠李戴”,那么在Agent的多轮工具调用、状态管理、自主决策过程中,幻觉会导致整个任务链路崩塌——而且崩塌的隐蔽性极强,你经常要等到业务数据彻底乱了才回头排查到源头。
这篇文章我想从一个纯粹工程落地的角度,聊聊我见过的AI Agent Harness工程中的幻觉问题,以及怎么在工程侧把幻觉压到可控范围内。如果你是做Agent应用的开发者、做AI应用架构的工程师,或者正在被“模型输出不可控”折磨的从业者,这篇东西应该会给你一些可操作的思路。我聊的解决方案不是学术论文里的理论框架,而是踩过坑之后沉淀下来的实战经验。
1. 幻觉问题的本质与Harness工程边界
1.1 幻觉到底是什么:从认知根源说起
在动手解决问题之前,先把幻觉的根源捋清楚。大语言模型的本质是“下一个token的概率预测器”,它并不知道自己说的话在外部世界是否真实。模型内部没有一张“事实数据库”的对照表,它所有输出都来自于参数空间中的概率分布。这意味着模型输出的每一句话,本质上都是“根据上下文和历史模式,最有可能出现的文本序列”。
这里有一个关键认知误区:很多人觉得幻觉是模型“坏了”或者“不够聪明”,其实不是。幻觉是生成式模型的结构性副产品。模型的优化目标是“生成人类觉得自然的文本”,而不是“生成真实正确的文本”。你让一个人做数学题,他做不出来会写“不会做”;让模型做数学题,它做不出来会觉得——既然你要一个答案,那我就给你编一个看起来最像正确答案的数字。
在Agent场景下,问题会进一步放大。单轮对话中幻觉通常只是“信息错误”,但Agent需要根据模型输出去调用工具、修改状态、执行动作。一旦输出的中间步骤出现幻觉,整个链路就被带偏了。我见过最典型的一幕:Agent在工具调用时对返回结果视而不见,反而自己编了一段“工具返回说xxx”的结果继续执行——模型根本没看工具的JSON返回,它只是在“猜测工具应该返回了什么”。
1.2 Harness工程解决什么:我们能干预的边界在哪
Harness Engineering这个词,拆开看就是“给模型套上的工程约束装置”。它指的是模型之外的整个工程链路:提示词模板、上下文管理、知识检索、工具注册、输出解析、状态跟踪、策略判断、后验证和纠错机制。
有一个概念必须明确:Harness不能修改模型的权重,也就无法从根上“杀灭”幻觉。所有基于LLM的应用都是在一个概率系统的地基上盖楼,Harness能做的不是让模型永远不说错话,而是让错误话的成本足够低、暴露足够快、影响范围足够可控。
想清楚这个边界很重要。很多团队立项时目标就是“彻底消除幻觉”,这个方向本身就错了。你永远无法让一个概率模型做到100%事实正确——因为“事实正确”本身就不是它的优化目标。工程上可落地的目标是:在关键路径上让幻觉无处遁形,在不关键路径上让幻觉造成的破坏可恢复、可兜底。
具体来说,Harness工程能干预的层面有四层:
- 输入层:控制喂给模型的上下文质量和边界,减少模型“自由发挥”的空间
- 决策层:在模型做关键决策前,增加结构化约束和校验门槛
- 输出层:对模型输出做格式校验、内容校验、事实核对
- 动作层:在Agent执行动作后,增加结果确认和反馈回环
这四层环环相扣。大多数幻觉问题不是某一层出了问题,而是各层之间的“缝隙”没有堵住——上下文给了错误的信息、决策时没有约束格式、输出后没有校验、执行后没有确认,四层全部放行,幻觉就畅通无阻地穿透了整个链路。
2. 从输入层到决策层:降低幻觉的工程化手段
2.1 检索增强(RAG)的精度陷阱:检索到不等于该用
RAG是当下对抗知识性幻觉最主流的手段,但很多人忽略了一个残酷的现实:检索到相关文档不等于模型会正确使用它。我在实际测试中发现,即使检索结果的top1是绝对正确的答案,模型依然有可能在生成时“跑偏”到主观想象——尤其是当检索结果与模型内部预训练时的固有认知存在冲突时。
这里有个真实的例子:某业务系统的用户名单里有一个特殊编号“UA-2024-XQ7”,这个编号看起来非常像某种产品型号。模型在预训练阶段从未见过这个编号,它在生成回答时,会有一种强烈的倾向——把这个编号“修正”成它见过的、看起来更合理的格式。于是在RAG已经提供了正确上下文的情况下,Agent依然输出了一个被“脑补修正”过的错误编号。
要解决这个问题,单靠“检索得更准”是不够的,必须在输入层做几件事:
第一,把检索结果的结构标记强化到“事实证据”级别,而不是“参考文本”级别。在提示词中明确告诉模型:只有上下文中明确出现的实体、数字、名称才能被引用,凡是未出现在检索结果中的信息,无论你多“确信”,都必须在输出中标记为猜测。
第二,控制检索结果的上下文长度和截断策略。很多人图省事把整篇文档塞进上下文,结果模型在长上下文中“丢失”了关键证据,反而被次要信息带偏。我习惯的做法是按语义相关性截断,每篇文档只保留与query相关性最高的段落块,并且把相关段落放到距离生成位置更近的地方。
第三,对不同来源的检索结果做区分。在我经手的项目里,检索源通常包括:结构化数据库的记录、历史FAQ、实时接口返回、知识库文档。这四类来源的置信度级别完全不同。数据库记录是强约束事实,FAQ是中等置信度,模型生成的历史答案是弱置信度。一定要在提示词里给模型建立“证据等级观念”,否则模型会把所有上下文一视同仁,把一条FAQ里的泛化表述当成确定事实输出。
2.2 工具调用链路上的幻觉重灾区:参数虚构与结果无视
如果说知识性幻觉是“说错话”,工具调用场景下的幻觉则是“做错事”,危害级别完全不同。
工具调用幻觉最常见的三种形态,我分别起个名字方便对号入座:
- 参数虚构:模型在调用工具时,对必填参数自己脑补了一个值。比如查询订单接口需要order_id,模型没有在上下文中找到这个ID,但它不选择拒绝,而是编了一个“看起来合理”的ID传进去。
- 结果无视:工具已经返回了明确的JSON结果,模型在后续生成时完全或部分忽略了真实返回值,转而去编造一个它“觉得应该存在”的结果。
- 调用编造:模型声称调用了某个工具,但实际上整个链路中根本没有发生这次调用。在我的项目里,这类问题经常出现在多轮Agent中——模型把“计划要调用”和“已经调用了”混为一谈。
针对这三种形态,纯靠提示词约束解决不了问题,因为模型对“什么该做什么不该做”的判断本身就不可靠。工程侧的标准做法是:把工具调用的决策权从“模型自由文本”中收回来,改用结构化决策协议。
具体落地方式是这样的:不再让模型在自由对话流里自然生成“我要查一下这个订单”,而是要求模型在必须调用工具时,输出一个严格符合预先定义的JSON Schema的结构化动作。Schema中每个字段都有明确约束:order_id必须是字符串且必须以“ORD-”开头,否则校验器直接拒绝该动作,并让Agent进入“澄清模式”——也就是向用户或上游系统继续索要参数,而不是强行用编造的参数继续执行。
我在某Demo项目里落地这套机制时,把工具调用幻觉率从直接的数值不展开说虚的,但是从“偶尔出现”压到了“上线后基本绝迹”。关键的改变就是让“参数缺失”不成为模型的自由发挥项,而成为一个需要被显式处理的独立分支——模型只有两个合法选择:提供所有必填字段,或者明确请求补充信息。选项之间不存在“编一个值”的中间态。
2.3 自我一致性采样:用群体投票压制随机性幻觉
当Agent面临开放性问题,没有标准答案、没有事实库可以核验时,对抗幻觉的手段就要从“外部证据”转向“内部一致性”。
自我一致性(Self-Consistency)是一个实操性很强的方案:对同一个问题做多次独立采样(比如让模型用不同的温度和不同的解释路径生成同一个答案),然后对多次结果做一致性对比。幻觉的生成具有一定的随机性——同一个问题,模型第一次编造了A方案,第二次可能编造出A或B或C。而真实的答案因为根植于模型参数中的稳定知识,会以较高的概率在多次采样中收敛到相似的结论。
我落地时发现的关键细节:多次采样不是简单地让模型“想几次”,而是要让每次采样在保证独立性的前提下,尽可能覆盖不同的推理路径。实际操作中我会先让模型做一次“开放式思考”输出推理过程,然后再做一次“直接回答”输出结论,对比两次结论的一致性。如果两次答案在关键实体和结论上一致,答案的可靠性会显著提升;如果矛盾,就让Agent进入“信息不足”分支,而不是强行给出答案。
代价方面要诚实说:自我一致性采样会让推理成本翻2-5倍,延迟也会线性增加。所以它不是所有请求路径上的默认方案,而是只对“高影响问题”启用。我的常驻设计是做一个类似路由闸门的东西,低风险问题走单次推理快通道,高风险问题(涉及交易、数据修改、对外承诺)走多重采样验证通道。
3. 输出校验与后验证:让幻觉最后一公里无处遁形
3.1 强制结构化输出:先把格式篡改的空间堵死
在Harness工程中,有一类幻觉经常被忽视:格式幻觉。模型该输出JSON时输出了一段散文、该输出枚举值时输出了一段解释性文本、该返回数字时返回了一串带着单位的字符串。这类问题的危害不在于“内容错误”,而在于它阻断了下游程序对模型输出的处理——你的Agent框架解析不了输出,就无法继续执行,系统直接卡死或抛出异常。
格式幻觉的根源是模型的“自由文本生成”本性:除非你明确阻断,它倾向于输出自然语言。所以对抗格式幻觉的第一条铁律是:必须强制结构化输出。
不是“请以JSON格式返回”那种软提醒,而是在Harness层面做硬约束。当代的主流模型大多支持JSON Schema约束生成,也就是在解码阶段就从概率层面限制输出只会落在符合Schema的token序列上。如果模型不支持这种约束,就要在输出后加一层严格的Schema解析器,解析失败就把这次输出判定为“生成失败”,走重试或拒答流程,而不是尝试“模糊修正”——有机会展开聊一下模糊修正的坑,它比直接失败还要危险。
我踩过的一个典型坑是:某一次输出解析器发现模型返回的JSON里少了一个字段,我当时图省事写了一个“自动补全默认值”的兜底逻辑。结果这个逻辑在生产环境运行了一个月后,某次模型在关键字段上漏了值,兜底逻辑自动补了一个默认值进去,下游系统把这个默认值当成真实用户输入处理,产生了数据污染。之后我改掉了所有自动补默认值的逻辑,规则很简单:解析失败就是失败,协议不允许的返回就要求模型重新生成,而不是让工程代码替模型“圆谎”。
3.2 事实核验层:独立验证器不能省
在知识问答类型的Agent任务中,哪怕RAG检索命中率已经达到了90%以上,我也坚持额外跑一层事实核验(Fact Verification)。因为模型的生成逻辑是“以检索结果为素材进行语言组织”,组织过程中可能添加了检索结果中没有的细节,或者把多个来源的信息拼接出了不存在的结论。这就是所谓的“忠实度问题”——内容看起来流畅且信息量充足,但实际上与源文档并不对应。
独立验证器的设计思路是:把模型的最终输出和它引用的来源文档拆开来,用一个独立的判断模型(也可以是规则+模型的混合体)去判断输出中的每个原子断言是否能够在来源文档中找到对应支持。
这里的关键词是“原子断言”。你不能让验证器去判断“这段话整体对不对”,而是要把输出拆成单一口径的陈述:一个实体、一个关系、一个数字、一个日期。然后逐条与来源对照,凡是来源中不存在的断言,就标记为“无据声明”,视严重程度触发修正或拒答。
这个步骤确实会增加一次额外的模型调用,但它的价值在长期运行中会被验证:Agent输出的最终质量阈值大幅提升,因为“输出前自检”这件事——模型自己是做不到的,但独立验证器可以。
3.3 拒绝与不知道机制:敢于说“我无法确认”才是成熟的表现
关于幻觉有一个被严重低估的解法:允许模型不知道。在业务上,很多时候用户对“不知道”的容忍度远高于对“乱编”的容忍度。你说不知道,用户顶多觉得这个助手能力有限;你乱编一个答案,一旦被发现,用户对整个系统信任度直接清零。
工程上要让“拒绝”成为一个模型可以合法选择的路径,而不是模型在信息不足时的“未定义逃生通道”。做法是在提示词层面显式声明:当且仅当上下文中无法找到明确答案时,输出“无法确认”是正确行为,并且把“无法确认”做成一个标签化的输出,方便Harness层识别。
更进一步的方案是置信度自评估机制,让模型对自己的回答同时输出一个置信度分数,Harness层设定阈值,低于阈值的回答一律转人工或触发追问。实测下来,模型的置信度分数和真实正确率的相关系数虽然不是完美正相关,但作为粗粒度的兜底已经够用。注意一点:不同模型的自评估校准程度差异很大,上线前一定要在你的业务数据集上做一次校准测试,找到真正划算的阈值边界。
4. 一个可复用的低幻觉Agent框架设计
4.1 框架总览:分层把守,避免单点信任
沉淀了上面这些方法之后,我在自己的项目里落地了一个分层低幻觉框架,结构大概是这样:
- 意图与危险区识别层:请求进入后,先判断这是高风险还是低风险任务,决定后续是否启用多重采样、是否强制事实核验
- 上下文与证据组装层:把检索结果、工具返回、历史对话整理成带证据标签的上下文,明确告诉模型哪些是强证据、哪些是弱证据
- 决策与动作规划层:强制结构化决策,工具调用必须符合预先声明的Schema,参数缺失走澄清分支
- 执行与工具层:工具调用记录真实日志,返回结果原文保留,模型无法绕过真实返回值
- 输出与验证层:结构化输出硬校验、独立验证器抽检、置信度阈值兜底
每一层各管一段,没有哪一层“全权信任”模型。这个设计的核心思路是一个很简单的工程哲学:永远不要在模型身上做单点信任。
4.2 关键实现细节与参数选择
在具体实现过程中,有几个参数和策略选择直接决定了系统的可靠性,值得单独拎出来讲。
第一,温度与采样策略。在Agent的工具调用路径上,我会把温度压得非常低。不是说低温就完全没有幻觉,而是低温让模型更倾向于选择概率最高的token序列,而概率最高的路径往往就是“照搬上下文、按照固定模板输出”的那条路。低温+结构化约束组合,是工具调用幻觉的最有效压制组合。但在创意类任务上就别用低温了,那是用创造力换正确率,不划算。
第二,重试策略不要无限循环。我见过一个极端案例:模型反复输出格式违规的结果,Agent框架反复重试,最后死循环跑到超时。工程上要设置重试次数上限,我的经验值是2-3次,超过上限就不再请求模型了,而是走“降级响应”——直接告诉用户暂时无法处理。同时每次重试时要给模型附加一个“上次为什么失败”的反馈信息,让模型在下一轮生成时了解具体错误,这比盲改有效得多。
第三,多模型层面的联防。在我的架构里,主推理和事实核验用的是不同的模型(或至少是不同temperature下互不可见的两次独立调用)。主推理模型负责“生成什么内容”,验证模型负责“检查这个内容有没有依据”。两个角色分离可以避免模型自我纠错的盲区——同一个模型对自己生成的文本往往没有批判能力,换个视角检查会更严苛。
4.3 场景一:知识问答类Agent的实战配置
针对知识问答类Agent,我最常用的一套“菜谱”长这样,可以直接参考:
- 检索侧:chunk切分控制在256-512个token区间,embedding模型选择检索精度足够的高分模型,召回后加一级rerank,从top50压缩到top5
- 证据标记:对top5段落逐个打上来源标签,并在提示词中要求模型只引用带标签的段落内容
- 引用要求:模型输出必须包含与断言一一对应的证据引用序号,没有引用的断言在验证层直接视为可疑
- 验证策略:对高风险问题启用独立验证器,拆解原子断言后与引用段落比对,覆盖率低于80%或关键实体无法对证的,要求模型重新生成
这套配置在多个问答型项目中跑过,整体效果是“编造率”被压得很低,但对应的代价是每轮问答的token开销增加了大约30%,延迟增加了几百毫秒。这就是一个权衡:在业务允许的成本范围内,尽量把幻觉压到更低。
4.4 场景二:工具调用类Agent的实战配置
对于需要连续调用多个工具完成任务的Agent,核心思路变成“动作的可验证性”:
- 每个工具会有独立的调用日志,包括入参、出参、耗时、错误码
- 模型生成的下游内容,凡是涉及到工具返回数据的,都要求从日志中溯源
- 工具返回结果为空或异常时,Agent不会基于“空结果”继续编造业务结论,而是进入“数据缺失处理流程”,明确向用户说明哪个环节数据缺失
- 关键路径上的每一步状态变更,都要在上下文中有记录,防止模型在多轮对话中“遗忘”之前的动作
这个场景下最需要注意的一个坑是:不要让模型在上下文中自己“总结”之前工具调用的结果。模型总结出来的“工具调用了,结果正常”可能是它脑补的,不是事实。正确做法是把工具调用的原始返回JSON保留在上下文里,模型要引用就让模型直接引用原始JSON字段,而不是让它二次转述。转述的次数每增加一次,信息失真的风险就增加一分。
5. 评测方法、常见陷阱与排错实录
5.1 幻觉评测集怎么建:别用通用题库自欺欺人
很多团队说自己在做幻觉评测,但拿来评测的问题全是公开通用数据集里的常识题。这类评测有一个严重缺陷:模型在预训练阶段可能已经见过答案,评测出来的分数反映的是“模型记忆力”而不是“Agent链路防幻觉能力”。
真正有效的幻觉评测集,一定要包含下面三类样本:
- 业务定制样本:拿你们业务场景里真实出现过的、发生过幻觉的case,整理成回归测试集。每修复一个bug,加一条case,防止复发。
- 反事实样本:故意构造与模型固有认知冲突的业务事实,比如某个存量系统的编号规则与常见格式完全不同,测试模型是否会被预训练固有认知带走。
- 缺失信息样本:设计一些刻意不给足必要参数的请求,考察Agent在信息不足时是否选择“澄清”而不是“脑补”。
我经历过一个典型的案例:A同学做了一个问答系统,在通用评测集上幻觉率看起来很低,但一上生产就被真实用户的问题打回原形。原因就是通用评测集的答案模型早就背过了,而生产环境里用户问的是系统内的私有数据,模型没见过,幻觉率直线上升。从那之后我们的评测集里100%都是业务私有数据,通用题库只作为参考,不作为验收依据。
5.2 幻觉率量化口径:算错了等于白算
评测学幻觉问题的另一个常见坑是指标口径混乱。模型生成一段200字的回答,里面包含一个错误的日期,那这一步算“幻觉”还是“正确”?有的人按“整段正确才算对”来算,有的人按“关键实体正确即算对”来算,两种算法下同一个系统的财报数据能差出一倍。
我的建议是根据业务场景拆解指标,而不是用一个笼统的“幻觉率”糊弄过去:
- 实体级幻觉率:统计输出中所有实体(人名、编号、日期、金额)中,无法在上下文或知识库中找到依据的比例
- 断言级幻觉率:从输出中拆分原子断言,统计无法被证据支持的断言比例
- 任务级成功率:整个Agent任务链路是否无错误地跑通了目标流程
这三个指标一个比一个严格。实体级是最低标准,断言级是质量线,任务级是业务线。上线评审时至少要把断言级和任务级一起看,只看实体级容易被“关键实体对了但整体逻辑编造”的case糊弄过去。
5.3 典型幻觉场景排查实录
最后分享几个真实环境里排查幻觉问题的实录,都是我踩过的坑,希望能帮你节约时间。
典型案例一:RAG失效但模型不说。某次排查发现Agent经常答非所问,日志显示检索模块正常返回了结果,但生成的答案与检索结果大面积不相关。原因排查后定位到提示词里有一句话把检索结果描述成“背景阅读材料”,模型把它理解成了不需要直接引用的参考信息。修正方式是把提示词的证据描述改成“唯一事实来源”,并加了一句“如果背景材料中没有信息,请回复无法确认”,效果立竿见影。
典型案例二:多轮对话中的实体漂移。用户在第一轮输入了订单编号,Agent第一轮回答正确。但到了第三轮、第四轮,模型开始出现订单编号记忆漂移的情况——它记不清编号了,又开始“按规律脑补”。解决方式是搞了一个外部状态变量,对话开始时就把关键实体固化到外部存储里,每一轮注入到上下文的“持久记忆区”。模型不再依赖它自己的内部注意力机制去“回忆”编号,而是每次从上下文的固定位置重新读取。
典型案例三:工具调用的“回声效应”。某个Agent在调用天气接口后输出“当前晴,27度”,但真实接口返回的其实是“小雨,19度”。排查后发现模型生成时过度依赖极小概率的上下文关联,它从用户上一句“周末准备出去露营”中推断出“晴天”才是合理的,于是把接口返回改成了“晴天”。这是工具调用幻觉里特别难防的一种:模型不是没看到工具返回,而是它主观觉得工具返回“不太合理”,于是用自己的想象覆盖了真实数据。解法是提高工具返回结果在提示词中的权重,并把“工具返回结果优先级高于一切推测”写成一个不可违反的硬规则。
写在最后的经验之谈
说实话,跟幻觉斗争这几年,我最大的体感变化是:从追求“让模型完全不说错话”转变成了“让系统整体对幻觉有免疫力”。单个模型输出错误其实是概率事件,你无法控制概率归零,但你可以控制错误出现后被放大还是被拦截。Harness工程真正的价值不在于消灭幻觉,而在于给幻觉装上一套围栏——让它在局部犯错,但是不让错误影响全局。
最后分享一个我个人比较推崇的小技巧:所有关键Agent输出,都加一句“本回答的决策依据如下”,然后强制模型列出支撑决策的上下文证据条目。注意这不是说给用户听的,而是说给Harness层的校验逻辑听的——有了明确的证据条目,校验层就不需要去猜“这句话是从哪个上下文里来的”,直接按图索骥做逐条比对就行。这套显式归因机制,是我所有这些方案里投入产出比最高的一个。