做了快两年Agent落地,我被问得最多的问题不是某个功能效果多惊艳,而是“怎么让它别一本正经地胡说八道”。模型输出一篇流畅回答很容易,难的是让回答的每个关键结论都对应得上真实数据。尤其当Agent开始调用接口、操作业务系统、生成结构化结果之后,幻觉就不是一句"大模型通病"能糊弄过去的了,它会直接影响用户决策、后台数据和项目口碑。这篇文章就把我实际排查、修复过的幻觉问题理一遍:先分清Agent幻觉到底是什么,再拆成因,最后给出一套分层的落地解法。
1. 先说清楚:AI Agent的幻觉到底是什么
1.1 别把"大模型说错话"和"Agent幻觉"混为一谈
普通问答场景里,模型答错一个常识性问题,用户可以自己判断或重新提问;但Agent场景完全不同。Agent是一个会执行动作的系统,它要读上下文、调工具、看返回值,然后基于这些结果生成下一步输出。幻觉在Agent里的定义更该严格一些:不是"回答得不完美",而是Agent在缺少真实依据的情况下,把编造的内容包装成事实输出,甚至继续触发下一轮动作。
比如一个物流查询Agent,用户问"我的包裹到哪了",理想流程是:调用物流接口、读取轨迹、返回实际位置。幻觉版流程可能是:接口超时或返回异常,Agent却根据历史对话里的一个模糊地址,脑补出"包裹已到达某中转站"。用户一旦相信了这个结果,就会产生一连串后续问题。这种错误不是单纯的语言生成瑕疵,而是系统级的事故隐患。
1.2 我实际见过的三个典型幻觉画面
第一个画面来自一个内部库存查询Demo。用户问某款物料还剩多少,Agent调用库存服务的接口,但接口恰好因网络抖动返回了一个空对象。正常做法是告诉用户"查询失败,请稍后重试",结果Agent基于会话开头的一条旧数据,回复"当前库存剩余32件"。那条旧数据其实是三天前的,而且对应的不是这款物料。
第二个画面是在一个数据分析Agent里。它需要读取销售表格和成本表格,再计算利润率。因为销售表里的某个字段名非常接近成本表里的字段名,Agent把两个数字引错了,最后输出"本月利润率提升12%"。表面上结论很完整,但把中间步骤展开后发现,分子分母根本不来自同一张表。
第三个画面来自客服场景。用户咨询延迟发货的赔偿规则,Agent非常流畅地引用了一条看起来很像样的平台规则,甚至给出了"三倍赔偿"的说法。可实际上知识库文档里根本没有这一条。用户按这个结论去申请,客服后台才发现是Agent自己圆了一段话。
这三个案例的共同点不是模型不懂词句,而是链条里缺少一个"证据一致性核查"环节。幻觉之所以在Agent里更危险,是因为它会顺着工具调用和流程决策一路传播,直到影响真实业务。
2. 幻觉从哪里来:四个层面逐个拆
2.1 输入侧的信息缺失和工具返回冲突
模型本身没有实时访问数据库的能力,它所有回答都建立在上下文窗口内的文字上。如果上下文里根本没有对应的真实信息,模型就只能靠语言惯性自圆其说。这就好比让一个只带地图但不允许看路牌的人开车去陌生地点,他开得越久,编的路越多。
更隐蔽的是上下文冲突。知识库里同时存在两个文档:一份是旧版规则,一份是新版规则,但Agent没有做版本标注,只把两个段落都塞进提示词。模型不会自动判断"哪一份更权威",它倾向于选择措辞更完整、更连贯的那段来续写。把多份内容无差别地拼进上下文,本身就是制造幻觉的温床。
2.2 工具反馈链上的污染
工具调用是Agent特有的一环,也是幻觉放大最严重的地方。一个完整的任务往往包含多次工具调用:先查用户信息,再查订单状态,最后查配送轨迹。前面任何一步返回异常、字段为空或超时,后面的环节都会受到影响。
我在排查中发现一个共性规律:工具返回异常时,Agent大概率不会直接承认失败,而是选择用上下文碎片补全答案。尤其当返回值里还残存部分字段时,模型更倾向于"把剩下的内容填成一句完整的话"。这种乐观推断在对话里显得合理,在业务系统里却是无法容忍的。
另外还有一种常见污染:多轮对话中的旧工具结果串场。用户第一轮问A客户的订单,第二轮问B客户的订单,Agent如果没有严格区分任务边界,很容易沿用上一轮的返回结果,张冠李戴。
2.3 提示词给了太多想象空间
很多幻觉其实是提示词设计逼出来的。如果我们给Agent写的是"你是专业助手,请根据你的知识回答",那基本等于鼓励模型自由发挥。模型没有渠道区分"内部知识库里有依据"和"内部知识库里没依据",它会一视同仁地生成。
我见过更夸张的提示词是"请尽量详细、完整地回答,帮助用户解决问题"。这句话放在客服Agent里等于灾难,模型为了"完整"会自动补全缺失规则。提示词里的每一个功能词都在暗示模型的生成策略:给定"尽量补充",它就补;给定"严格依据资料块",它才收手。
这里有个容易被忽略的细节:不是所有"看似合理"的回应都来自模型能力不足,很多是提示话语义给模型留出了无依据扩展的通道。
2.4 模型自身的生成惯性与训练偏好
就算输入的上下文信息完整、提示词也足够严格,模型仍然可能犯错。因为大模型在预训练阶段的目标是"生成流畅、符合统计规律的语言",流畅性和事实性并不总是同一件事。有些表达听起来非常顺口,但在逻辑上与给定数据对不上。
强化反馈阶段还会进一步强化这个现象。模型在训练中倾向于学习"用户喜欢听完结、完整的答案",所以它会觉得"给一个明确数字"比"承认不确定"更安全。从人类的观感上说,坚定、具体的回复确实更有说服力,但模型分不清"应该坚定的事实"和"为了让语气坚定而补的细节"。
所以当项目里出现顽固幻觉时,不要只在提示词里找原因,要意识到当前模型本身就有"生成诱人回答"的倾向。调低温度可以减少部分随机错误,但不会消灭这类结构性问题。
3. 整体解法框架:别只盯着模型调Prompt
3.1 一张方案矩阵快速选型
幻觉治理没法靠单点解决。我在不同项目里用过一套分层方案,每层的目标和适用场景完全不同。先看这张矩阵,再结合自己的项目选型:
| 层级 | 治理目标 | 典型手段 | 适用场景 |
|---|---|---|---|
| 检索层 | 确保模型有真实资料可用 | 知识库召回、来源元数据、相关性门槛 | 客服问答、政策咨询 |
| 工具层 | 确保工具调用结果可信 | 函数定义、重试超时、返回值校验 | 订单查询、数据计算 |
| 决策层 | 控制Agents的生成边界 | 提示词约束、拒答分支、置信度阈值 | 所有Agent场景 |
| 解码层 | 降低生成的随机性 | 温度、top_p、最大输出限制 | 需要稳定结果的场景 |
| 训练层 | 对特定领域做偏好对齐 | 领域微调、指令数据、偏好优化 | 长期运营的垂直Agent |
选型逻辑很简单:如果项目只能做一天优化,先做检索层和决策层,这两层的投入产出比最高;如果要交付生产系统,工具层是必经之路;解码层几分钟就能改完,但效果有限;训练层最持久也最贵,一般留到业务稳定后做。
3.2 检索增强不做成"拼凑资料库"
检索增强是目前对抗幻觉最主要的武器,但很多人把它做成了"把知识库内容强行塞进上下文"。这么做不是不对,而是不完整。检索增强想要真正压制幻觉,至少要在三个细节上做对。
第一,每个检索片段都要带可追溯元数据,比如来源文档编号、更新时间、页码。模型拿到这些信息后,可以被要求"引用时必须注明来源编号"。这样即使生成内容有瑕疵,我们也能快速定位是哪份资料造成的。
第二,要给召回结果设置相关性门槛。不是找到5条就塞5条,低分片段塞进去只会增加上下文的冲突概率。我在实际项目中通常把相关性分数低于0.6的片段直接丢弃,宁可不答,也不让模型拿无关内容凑数。
第三,提示词里必须明示"资料与问题的关系"。不能只说"请基于以下资料回答",还要加上"如果资料中没有对应信息,请明确回答没有找到"。这一句话能把拒答率提升一个量级,很多年轻人只看"召回率",忽略了"拒答提示词"的重要性。
3.3 工具调用:从入口到出口全锁住
工具调用是Agent区别聊天机器人的关键,也是幻觉最容易漏进来的地方。我倾向于把工具调用设计成高保全动作,而不是一个自由发挥的岔路口。函数定义要从一开始就收紧:参数强制、枚举值限制、必填字段标清楚。
很多框架允许模型自由推断参数,这其实是风险来源。比如查询订单工具有一个可选参数"订单状态",模型如果没有被约束,就可能自动补一个还没经过用户确认的状态值。正确做法是把参数模式设为严格模式,可选参数缺省也不让模型乱填。
工具返回之后还需要一层校验。我的习惯是三步:第一步确认接口是否真的返回成功;第二步确认返回内容是否符合字段约束;第三步确认数值是否在合法范围内。任何一步不过,就进入"无法确认"分支,而不是继续生成回答。工具返回None或空值时,需要有一句专门提示:"本次工具调用未返回有效结果,你应该向用户说明暂时无法查询,而不是补全答案。"这句话能让很多模型悬崖勒马。
4. 实操:搭一个能压住幻觉的客服资料Agent
4.1 场景设定与设计取舍
我最近在一个内部Demo里搭过一套客户咨询Agent,参考了前面说的所有要点。场景是这样的:某公司内部客服团队每天要处理大量关于产品规格、售后规则、库存状态的问题,知识库里文档更新频繁,准确率要求很高。
最初原型非常简单:把用户问题直接交给模型,让它自由作答。结果测了一百条问题,有十几条出现了无依据的延伸回答。后来我把流程改成"先检索、再作答、有据才答":用户问题进来先做知识库检索,检索结果带着来源信息进入提示词;如果检索结果为空或分数不够,直接返回"暂无对应信息,建议转人工"。
这个设计的核心取舍是:允许Agent拒绝回答,但不允许Agent无依据回答。从产品体验上看,拒答听起来很消极,实际操作下来效果反而更好。用户得到"未找到对应信息"后大概率会重新表述问题或转人工,而得到一段看似精确的编造答案后,才会真正造成投诉。
4.2 关键代码片段与决策说明
关键代码不复杂,但每一段都对应一个防幻觉决策。下面是我用的简化版流程:
def retrieve_knowledge(query): # 返回带来源元数据和相关性分数的资料片段 chunks = vector_db.search( query, top_k=5, min_score=0.6 # 低于0.6的片段直接丢弃 ) results = [] for c in chunks: results.append({ "content": c.content, "source_id": c.meta["doc_id"], "updated_at": c.meta["updated_at"], "score": c.score, }) return results def call_tool_with_check(tool_name, args, timeout=3): # 工具统一封装:超时、重试、状态校验 resp = invoke_tool(tool_name, args, timeout=timeout) if not resp.ok or resp.data is None: return None # 空结果作为独立分支,不直接拼进回答 if not validate_schema(resp.data): return None return resp.data def agent_reply(user_input): history = build_history() chunks = retrieve_knowledge(user_input) if not chunks: return "暂时没有找到与您问题匹配的可靠资料,建议转人工处理。" return generate_answer(user_input, history, chunks)这段代码里最有讲究的是min_score=0.6这个值。它不是拍脑袋定的,而是用一批真实用户问题跑过对照组后得到的平衡点:阈值设到0.8时漏掉太多有效资料,回答过于保守;设到0.4时低质量片段开始混入,幻觉率明显上升。不同知识库的这个值会有差异,建议上线前用标注问题集调一遍,不要直接抄别人项目的参数。
retrieve_knowledge和call_tool_with_check分开放还有一个好处:检索失败和工具失败是两种不同的失败,处理策略也不同。检索失败说明用户问的问题可能不在知识库范围内,应该转人工;工具失败说明用户的问题本来可以答,只是系统层面暂时没拿到数据,可以提示重试或稍后再试。把这两个分支混在一起,会让后面提示词的约束力大打折扣。
4.3 提示词模板与置信度护栏
代码只能搭框架,真正影响生成内容的还是提示词。我在这套Demo里用的模板大致是这样的:
你是内部客服助手。你的回答必须基于下面提供的资料块,禁止使用资料块之外的内部知识。 资料块: {chunks} 生成规则: 1. 如果资料块中没有直接回答用户问题的内容,请回复:"没有找到对应信息,建议转人工。" 2. 回答中涉及数字、日期、规则时,必须引用资料块对应的来源编号。 3. 禁止推断资料中没有给出的数字和日期。 4. 如果多份资料内容冲突,先向用户说明存在不同说法,再分别给出出处。这个模板看起来平平无奇,但它做了三件重要的事:明确信息边界、强制来源引用、授权拒答。实际测试里,只加第一句话就能明显减少无依据内容的产生。原因是模型对"边界"非常敏感,一旦知道"资料块之外的内容不可用",它生成时的选择空间就大幅收窄。
另外我还会在生成结果后面接一层简单的一致性校验。用一次额外的模型调用,把"Agent回答"和"资料块"同时交给校验模型,让它判断回答中是否存在资料块之外的事实性断言。如果校验模型标记了可疑点,就触发兜底回复或转人工。这种二次校验会占用一些额外推理成本,但对企业级场景来说,多花这点成本换取可追溯性非常划算。
5. 常见问题与排查技巧实录
5.1 现象:工具明明成功了,回答还是编的
我遇到过一种非常迷惑的情况:接口正常返回,数据也是真实的,但Agent最终回答里还是多了几个字段没有的数据点。后来把完整的上下文日志打出来才发现,模型在等待工具返回的时候,已经把一段"补充说明"生成出来了。后续文本生成阶段没有完全按照工具结果重新起草,而是接着之前的半成品继续补全。
这个问题的根因在实现层:Agent框架把工具调用过程拆成多轮模型调用,模型可能在第一轮就写了部分内容,第二轮需要基于工具结果继续时,这部分历史被保留了下来。排查技巧很简单,把上下文的输入内容完整打印出来,看模型最终生成前到底看到了哪些内容。很多所谓"幻觉",其实只是输入侧没有把错误占位文本清理干净。
5.2 现象:加了检索增强反而更不靠谱
有种反直觉的情况:不接知识库时模型至少答得不痛不痒,接了检索增强以后反而开始答错。这类问题的核心几乎都是知识库本身混乱。比如知识库里有大量重复文档,一份规则修订了五次,但五版都躺在库里;或者检索到的几份资料看起来相关,实际讲的是不同维度的问题。模型面对这些互相打架的信息时,自主判断哪条更接近用户问题,就很容易选错。
这类问题没有捷径,只能治理数据。给知识库文档加版本号、有效时间、所属业务维度;检索时排除过期版本;同主题文档做合并或加摘要。别指望模型在一个乱知识库里自动完成版本筛选,检索增强不背这个锅。
5.3 现象:模型明明用了资料块,还是给出可疑结论
有些Agent会把资料块里的内容引用得很准确,但最终结论的方向和被用户问题带偏了。比如规则原文明明是"工作日发货",用户说"周末能发吗",模型根据资料块给出的答案却变成"可以周末发货"。这是对话中的隐含假设污染:模型为了迎合用户问题,把资料块内容往用户期待的方向凑。
这类问题靠加提示词作用有限,更好的办法是让模型先输出"判断依据摘要",再输出最终回答。把中间推理步骤暴露出来之后,内部校验和用户审视都能更快发现问题。
5.4 常见问题速查
| 症状 | 最常见的根因 | 最快处理办法 | 长效手段 |
|---|---|---|---|
| 回答内容没有依据 | 上下文里没放原文或检索失败 | 拼接检索原文并加来源编号 | 让"无据拒答"成为默认规则 |
| 工具失败后仍补全 | 缺返回值校验或None走了正常分支 | 对None分支写专门提示词 | 统一封装工具并做三步校验 |
| 接了知识库反而更乱 | 库内版本冲突、重复文档 | 下线过期版本 | 建立文档版本管理机制 |
| 重复循环追问自己 | 置信度校验逻辑过强 | 减少反射最大次数 | 设计"最多一次转人工"策略 |
5.5 几条实际调参记录
温度调低到0.1确实能减少随机性,但对顽固幻觉帮助有限;真正提升明显的是把最大输出长度控制住,太长等于给模型自由发挥的纸面空间。top_p我一般保持在0.9附近,过低会让文本变得生硬,反而容易出现重复退化解。
我给反思类Agent设置过最大迭代次数,一般2到3次就够了。很多团队把这个次数设到5次以上,系统会陷入"质疑—修正—再质疑"的循环,算力消耗翻倍,幻觉率反而没有明显下降。反思不是越多越好,加一条"超过迭代上限就转人工"的兜底分支更实际。
6. 长期经验:幻觉治理不是单点工程
6.1 把"不知道"设计成正常结果
做了这么多案例后,我最大的体会是:治理幻觉,先治理"必须答"的心态。很多产品经理看到Agent回复"未找到对应信息"就觉得体验不好,于是要求模型"尽可能给出答案"。这条思路是在给幻觉铺路。一个高质量的Agent应该明确支持"合法拒答",把"未找到对应信息"设计成体系结构中的正常分支。
评估指标也要跟着改。不要只看"正确率"这一个数,把正确回答率、主动拒答率、引用覆盖率分开看。一个系统正确率达到85%、拒答率10%、引用覆盖100%,比另一个系统正确率90%但误报飞行的系统可靠得多。评估标准改对了,工程方向才能稳住。
6.2 给资源有限的团队三件事
如果没有太多人力把五个层级全部落地,我建议先做最容易见效的三件事:第一,所有检索片段带来源ID和更新时间;第二,工具返回做空值和类型校验;第三,提示词里写清"资料块之外不得使用内部知识"。这三件事加起来可能不用一天,但能挡住大部分看起来确实吓人的幻觉事故。
另一个值得记住的个人经验是:克制Agent的自主程度。它不需要对每个问题都给出完美回答,大多数时候"没查到"比"编一个"安全得多。幻觉不可能被完全消灭,但通过流程设计,可以把它隔离在关键决策之外。把每一次失控都当成排查入口,不断收敛上下文的入口和工具的出口,幻觉率会随项目迭代一点点下降——这也是做Agent工程最值得投入的方向。