"你的 Agent 跑了10轮之后还靠谱吗"
不稳在哪?就两个核心问题:上下文漂移和工具调用幻觉。
Agent 跑几步就偏了,忘了原始任务;该调工具的时候调错了,不该调的时候瞎调。
这篇文章,我们好好拆解一下这两个问题,讲清楚为什么跑偏、为什么会有幻觉、怎么发现、怎么解决。
1. 上下文漂移:Agent 为什么跑着跑着就偏了?
现象:目标悄悄变了
你让 Agent "分析这份销售数据,找出下滑原因",理想流程是:读数据 → 分析趋势 → 定位原因 → 输出报告。
实际跑起来可能是:读数据 → 发现格式有问题,开始修格式 → 修完格式又发现某个字段缺失,去查文档 → 查文档时被另一段内容吸引,开始做竞品分析 → 跑了10步,原始任务"找出下滑原因"一个字没碰。
这就是上下文漂移:Agent 的执行方向,悄悄偏离了原始目标。
不是模型"变傻了",是它在每一步都在做"当前上下文下最合理的下一步",但"最合理"不等于"最符合原始目标"。
根因:从注意力机制理解"为什么偏"
"Agent 跑久了会偏",但讲不清为什么偏。要真正理解漂移,得回到 Transformer 本身。
Transformer 里讲过 Self-Attention 的核心机制:每个 Token 对所有 Token 计算注意力权重,加权求和得到表示。这个机制带来了两个直接后果:
第一,注意力有"近因效应"。 Self-Attention 的权重不是均匀分配的,模型倾向于给最近的 Token 更高的权重。
原始指令在最前面,中间隔着大量中间结果,到后面几步时,原始指令的注意力权重已经被"稀释"了。Agent 不是"忘了"目标,是目标在它的注意力里占比越来越低。
第二,中间结果会"抢焦点"。 Agent 每一步的输出都追加到上下文里,这些中间结果本身就是新的刺激信号。
比如修格式时产生的日志、查文档时看到的内容,都会吸引模型的注意力。上下文越长,干扰信号越多,原始目标越容易被淹没。
这和 Transformer 那篇讲的 "Lost in Middle" 是同一类问题:上下文中间的信息最容易被忽略,而原始指令恰好被推到了"中间"甚至"开头"的位置。
总结一句话:上下文漂移的本质,是原始目标在注意力分配中逐渐失焦。

漂移的三种模式
不是所有漂移都一样,识别模式才能对症下药:
目标漂移:Agent 从任务 A 滑到任务 B。本来在分析销售数据,跑着跑去做竞品分析了。原始目标被新刺激完全替代。
优先级漂移:任务没变,但主次倒置了。本来"找出下滑原因"是主线、"修格式"是支线,结果 Agent 在支线上花了大半步骤,主线反而没推进。
风格漂移:目标和优先级都没偏,但输出风格变了。开头按要求输出结构化 JSON,跑了几步开始写大段自然语言解释。这种漂移最隐蔽,不影响任务完成但影响下游消费。

检测信号:怎么知道漂移了?
漂移不是突然发生的,是有信号的。关键是你得监控这几个指标:
- 当前动作与原始目标的关联度:如果连续两步的输出和原始目标没有直接关系,大概率在漂
- 步骤重复率:Agent 反复执行同一类操作(比如反复修格式),说明卡在子任务里出不来了
- 目标完成进度:跑了N步,原始目标的完成度还是0%,明显偏了
工程上可以做一个简单的漂移检测:每执行K步,把当前状态和原始目标丢给模型,让它判断"当前是否还在朝目标前进"。成本不高,但能有效抓住漂移。
解法分层:从简到难,每个都有代价
- 第一层:任务分解 + 子目标检查点
把复杂任务拆成有序子任务,每个子任务有明确的完成标准。Agent 完成一个子任务后,先检查"原始目标推进了吗",再决定下一步。
这是最简单也最有效的方式,适用于大部分场景。代价是:需要提前设计任务分解策略,对简单任务来说增加了不必要的开销。
- 第二层:上下文压缩
当上下文过长时,对历史步骤做摘要压缩,只保留关键信息。核心思路是控制上下文中"干扰信号"的量,让原始目标始终保持足够的注意力占比。
代价是:压缩可能丢失细节,某些场景下摘要信息不够精确。
- 第三层:定期 Re-Planning
每隔N步,暂停执行,让 Agent 重新审视原始目标和当前进度,重新规划后续步骤。相当于一个"航向校正"机制。
代价是:每次 Re-Planning 都是一次额外的 LLM 调用,增加了延迟和成本。但对长任务来说,这个代价远低于跑偏后全部重来的成本。

2. 工具调用幻觉:Agent 为什么调了不该调的工具?
现象:不是不会调,是调错了
你给 Agent 配了3个工具:search_database、search_web、send_email。
理想情况:用户问"上个月销售额多少",Agent 调用 search_database,拿到数据,回复用户。
实际可能发生的事:
- Agent 调了一个 search_api------你的工具列表里根本没有这个
- Agent 调了 search_database,但参数传了 date: "明天"------接口要求 YYYY-MM-DD 格式
- 用户只是闲聊"今天天气不错",Agent 硬是调了 search_web 去搜天气------根本不需要调工具
这就是工具调用幻觉:Agent 在工具调用上产生了"虚构"行为。
根因:从概率生成理解"为什么幻觉"
理解工具调用幻觉,要搞清楚一个关键事实:模型选工具,不是在查表,是在猜。
大模型的每一步输出都是概率采样。工具调用也一样------模型不是从工具列表里"查找"最匹配的工具,而是根据上下文预测"下一个最可能出现的工具名"。
这意味着:
- 如果工具描述模糊,多个工具看起来"都可能对",模型就靠概率选,选错就是幻觉
- 如果参数类型没约束,模型按"感觉"填值,填出来的格式和类型可能完全不对
- 如果模型被训练得"太积极"(过度倾向于使用工具),它会在不需要工具的时候也硬调一个
总结一句话:工具调用幻觉的本质,是概率生成遇到了结构性约束不足。
幻觉的三种类型
不同类型的幻觉,根因不同,解法也不同:
Type 1:调用不存在的工具
Agent 生成了一个工具列表里没有的工具名。比如你只有 search_database 和 search_web,它调了 search_api。
根因:工具描述和任务描述之间存在"语义缝隙",模型根据任务"编"了一个看起来合理的工具名。你的工具叫 search_database,但任务描述里提到"搜索数据",模型可能觉得 search_api 更匹配。
Type 2:参数类型或格式错误
工具调对了,但参数传错了。比如接口要求 limit: integer,模型传了 limit: "十个";要求 date: "YYYY-MM-DD",模型传了 date: "上周五"。
根因:参数的类型约束和格式约束没有在工具描述中明确声明,模型按自然语言习惯生成参数值,而不是按接口要求。
Type 3:无意义的工具调用
本来不需要调工具,Agent 硬调了一个。用户问"你好",Agent 调 search_web 搜"问候语"。
根因:模型有"工具使用倾向"------训练数据中,使用工具的对话往往得到更高的奖励信号,导致模型过度倾向于调用工具,哪怕当前不需要。

解法:每种幻觉对应不同策略
对抗 Type 1(调错工具):工具描述结构化
核心思路:让模型对工具的"边界"有清晰认知。
- 工具描述不要只写"搜索数据库",要写清楚"搜索数据库,仅支持SQL查询,不支持API调用"
- 每个工具加 Few-shot 示例,展示什么场景该调这个工具、什么场景不该调
- 调用前校验:模型输出的工具名必须在注册列表中,否则拒绝执行
对抗 Type 2(参数错误):参数 Schema 约束
核心思路:用结构化约束替代自然语言"希望"。
- 工具的每个参数都要有 JSON Schema 定义:类型、枚举值、格式、是否必填
- 利用模型的结构化输出能力(response_format 或 tool_choice),让模型按 Schema 生成参数
- 调用前对参数做类型检查和格式验证,不通过则重试
对抗 Type 3(无意义调用):调用必要性判断
核心思路:给模型一个"不调工具"的选项。
- 在工具列表中显式加入"无工具需要调用"的选项,让模型知道不调工具也是合法选择
- 调用前加一层判断:当前用户意图是否真的需要工具?如果只是闲聊、确认、总结,直接回复
- 设置调用频率阈值:同一轮对话中,如果工具调用次数超过N次,触发人工确认
通用防线:调用全流程校验
不管哪种幻觉,都可以用一套"三段式"防线兜底:
- 调用前:校验工具名是否在注册列表中,参数是否符合 Schema,是否满足调用必要性
- 调用中:设置超时和异常捕获,工具执行失败不要直接暴露给模型原始错误(容易触发下一轮幻觉),而是转化为结构化的错误信息
- 调用后:校验返回结果是否符合预期格式,异常结果触发重试(最多2-3次),超过重试次数则降级处理
这套防线不解决根因,但能有效拦截大部分幻觉的后果。根因靠工具描述和参数约束解决,兜底靠全流程校验保障。

3. 先搞清楚:Agent里的幻觉不止一种
先把幻觉类型拆清楚。
因为不同类型的幻觉,根因不同,解法也不同
知识幻觉
这是最常见的幻觉:模型编造不存在的事实。
比如用户问:"某个产品支持私有化部署吗?"
文档里没写,模型却回答:"支持,并且支持 Kubernetes 部署。"
这就是知识幻觉。
它的问题在于:模型把训练数据里的通用经验,硬套到了当前业务上。
工具幻觉
Agent 调用了一个不存在的工具,或者把工具能力想象得比实际更强。
比如系统里只有 search_docs,它却生成了一个 query_database。
这类幻觉比知识幻觉更危险,因为它已经开始影响执行链路了。
参数幻觉
工具是对的,但参数是瞎填的。
比如接口要求:
json
{
"date": "YYYY-MM-DD",
"limit": "number"
}
模型传了:
json
{
"date": "上周五",
"limit": "十个"
}
工具名没错,但参数不符合 schema,照样会出问题。
证据幻觉
RAG 场景最容易出现这个问题。
模型明明没有从文档里检索到相关内容,却说:"根据文档可知......"
或者文档 A 只说了"支持退款",模型回答成"支持 7 天无理由退款"。
这类幻觉最隐蔽,因为它披着"有来源"的外衣。
行动幻觉
这是 Agent 系统里最危险的一类。
该确认的不确认,该审批的不审批,该只读的不小心变成写操作。
比如用户只是问:"帮我看看这个订单能不能退款。"
Agent 却直接调用退款接口,把钱退了。
这就不是回答错了,而是系统事故。

所以,Agent 幻觉不能只说"模型会编"。
更准确的说法是:Agent 幻觉是模型在事实、工具、参数、证据和行动上产生了不受控的生成。
4. 总体思路:不要指望一层防线解决所有问题
约束 Agent 幻觉,不能只靠一层。
Prompt 要不要写?
当然要。
但 Prompt 只是第一层边界提醒,不是保险箱。
真正的工程防线至少有四层:
- Prompt约束:告诉模型什么能做、什么不能做
- 工具约束:限制它能调什么工具、参数能怎么填
- 证据约束:要求回答必须有来源,没有证据就拒答
- 输出校验:生成后再检查,不能让结果直接裸奔给用户

这四层解决的问题不一样。
Prompt 约束解决的是"模型知道边界"。
工具约束解决的是"模型不能乱行动"。
证据约束解决的是"模型不能空口编事实"。
输出校验解决的是"模型说完以后还要验收"。
面试里能说出这四层,基本就比只会说 Prompt 的同学高一档。
第一层:Prompt约束,只能管边界,管不住全部
Prompt 是必须要做的。
但你要知道它的能力边界。
Prompt 能做什么
它能把系统规则说清楚:
- 不知道就说不知道
- 只能基于工具结果和检索文档回答
- 文档没有提到的信息不要补充
- 高风险动作必须先确认
- 输出必须包含来源或置信度
比如系统提示词可以写:
你只能基于提供的工具结果和检索文档回答。
如果证据中没有出现相关信息,必须回答"当前资料中未提及"。
涉及支付、退款、删除、发邮件等写操作时,必须先向用户确认,不能直接执行。
这类 Prompt 很有用。
它至少让模型知道边界。
Prompt 管不住什么 但是,只靠 Prompt 管不住这些东西:
- 工具名是否真实存在
- 参数类型是否符合接口要求
- 检索文档是否真的支持回答
- 高风险操作是否真的被拦截
- 模型输出是否和证据一致
为什么?
因为 Prompt 本质上还是自然语言约束。
它是"希望模型遵守",不是"系统强制执行"。
只说"我在Prompt里要求模型不要幻觉"

正确姿势是:Prompt 负责声明规则,工程层负责强制执行。
第二层:工具调用约束,防止Agent乱行动
Agent 和普通 LLM 最大的区别是:Agent 会调用工具。
所以 Agent 的幻觉,很多时候不是"说错",而是"做错"。
工具层要做的不是相信模型,而是限制模型。
工具白名单
不要把所有工具都暴露给 Agent。
当前任务只给当前需要的工具。
用户只是问知识库问题,就不要给它退款、删除、发邮件这种写工具。
这样即使模型想乱调,也没有工具可调。
工具描述要写清边界
工具描述不能只写:
txt
search_order:查询订单
太粗了。
应该写成:
txt
search_order:只读工具,用于按订单ID查询订单状态、金额、时间。
不能修改订单,不能退款,不能查询用户隐私字段。
工具描述越含糊,模型越容易误用。
参数 schema 校验 工具参数必须有 schema。 类型、枚举、必填字段、格式,都要约束清楚。
比如:
json
{
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "订单ID"
},
"action": {
"type": "string",
"enum": ["query", "refund_preview"]
}
},
"required": ["order_id", "action"]
}
模型传错参数,不能直接执行。
要么打回重试,要么降级处理。
Observation 必须来自真实工具结果 这是一个很关键的点。 工具返回结果不能让模型自己编。 Agent Loop 里,Observation 必须!在这里插入图片描述(https://i-blog.csdnimg.cn/direct/528bb6e7ed144b5f934906188dbdb424.png) 来自真实工具执行结果,而不是模型生成一句"查询结果如下"。 否则工具调用就失去意义了。 高风险工具必须审批
只读工具和写工具要分开。
查询订单是一回事,退款是另一回事。
高风险动作至少要加:
- 用户二次确认
- 权限校验
- 金额/范围阈值
- 人工审批
- 幂等和回滚机制
Agent 系统里,最怕的不是模型不知道,而是模型自信地执行错动作。
第三层:证据约束,让回答有据可依
很多人以为接了 RAG,就不会幻觉了。
这也是误区。
RAG 只能给模型提供外部证据,但不能保证模型一定忠实于证据。
之前在 RAG落地最难的地方在哪 里讲过,RAG 最难的不是某一个环节,而是文档预处理、召回质量、生成忠实度三个环节级联放大。
在 Agent 系统里,证据层至少要做五件事。
先检索,再回答
涉及业务事实、产品规则、公司政策的问题,不要让模型凭记忆答。
必须先检索。
模型训练数据里再懂,也不一定懂你们公司昨天刚更新的退款规则。
强制引用来源
回答里要能追溯到文档。
比如:
txt
根据《退款规则》第3节,未发货订单支持原路退款。
不是为了让回答看起来正式,而是为了逼模型把结论和证据绑定。
检索不到就拒答
如果检索结果为空,或者相关性低,就不要硬答。
应该回答:
当前资料中没有检索到相关规则,无法确认。
这比编一个看起来合理的答案强太多。
Rerank 降低噪声
召回文档多,不等于证据强。
Top-K 拉得太大,噪声文档混进来,模型反而更容易被带偏。
工程上常见做法是:先粗召回,再 Rerank,最后只把最相关的证据给模型。
答案和证据做一致性校验
生成之后,再检查一遍:
- 回答里的关键结论,证据里有没有出现?
- 数字、时间、限制条件是否一致?
- 有没有文档没提到但模型自己补充的内容?
- 这一步可以用规则,也可以用一个轻量模型做 LLM-as-Judge。
但注意,Judge 也会错,所以高风险场景不要只靠它,要结合规则和人工审核。

第四层:输出校验,生成完不是结束
很多系统把"模型生成回答"当成最后一步。
这不对。
在 Agent 系统里,生成之后还要验收。
结构化输出校验
能用 JSON,就不要让模型自由发挥。
比如客服工单分类,输出应该是:
json
{
"category": "refund",
"confidence": 0.86,
"need_human": false,
"evidence_ids": ["doc_102", "doc_305"]
}
这样才能校验字段是否缺失、枚举值是否合法、置信度是否低于阈值。
自由文本看起来顺滑,但很难管。
业务规则校验
模型说"可以退款",系统还要检查:
- 订单是否存在
- 是否超过退款期限
- 金额是否异常
- 用户是否有权限
- 当前状态是否允许退款
这些不能交给模型自由判断。
应该由业务代码兜底
确定性规则,别让概率模型拍脑袋。
敏感内容校验
涉及医疗、法律、金融、隐私、公司内部数据的内容,要做额外校验。
比如:
- 是否泄露个人信息
- 是否给出越权建议
- 是否包含未经证实的结论
- 是否把建议说成了确定事实
这类场景里,模型最好只输出建议,不直接给最终决策。
多模型或双阶段审查
复杂场景可以加一个 Reviewer:
第一阶段:Generator 生成回答。
第二阶段:Reviewer 检查回答是否被证据支持、是否违反规则、是否需要人工介入。
这就是 Reflection / Review 的思路。
但别滥用。
每加一层模型调用,成本和延迟都会上升。
简单 FAQ 没必要这么重,高风险动作才值得上。
5. 约束之后还幻觉,怎么办?
因为现实里没有 100% 不幻觉的 Agent。
你不能说:"我约束完就不会幻觉了。"
这句话太假了。
更好的回答是:我承认幻觉无法完全消灭,所以系统要有拦截、降级、记录和复盘机制。
先拦截
出现这些情况,不能直接返回给用户:
- 没有证据来源
- 置信度低
- 输出校验失败
- 工具参数不合法
- 高风险动作没有确认
- 生成结果和证据冲突
拦截不是失败,而是保护系统。
再降级
拦截之后怎么办?
看场景:
- 换更强模型重新生成
- 只返回检索到的原始证据
- 让用户补充信息
- 转人工处理
- 对写操作直接停止执行
比如用户问一个政策问题,模型没有证据支持,那就只展示检索到的相关文档,让用户自己看,不要让模型总结。
高风险动作要回滚或审批
如果幻觉影响的是写操作,就不能只靠"重新回答"解决。
比如错误发邮件、错误退款、错误删除数据,这些都是动作层事故。
所以高风险工具必须提前设计:
- 审批流
- 幂等机制
- 回滚机制
- 操作日志
- 权限隔离
否则出了问题,你连怎么恢复都不知道。
必须记录 trace
只要出现幻觉,就要能复盘。
至少记录:
- 用户原始输入
- 系统 Prompt
- 检索到的文档
- 工具调用参数
- 工具真实返回
- 模型最终输出
- 校验失败原因
- trace_id
没有 trace,就没有复盘。
没有复盘,幻觉就会反复发生。

6. 工程上怎么长期治理幻觉线上兜底只能止血。
真正要把幻觉率降下来,还得做长期治理。
先归因分类
每次幻觉事故,都要归因。
到底是:
- Prompt 边界没写清?
- 工具描述太模糊?
- 参数 schema 太松?
- RAG 没召回正确文档?
- Rerank 把噪声排前面了?
- 输出校验没拦住?
不归因,后面只能瞎改。
把事故样本加入 eval
这是很多团队容易忽略的地方。
出了幻觉,不是改完 Prompt 就算完。
要把这次失败样本加入评测集。
以后每次改 Prompt、改工具、改 RAG,都要跑一遍回归测试。
否则你修好了这个 case,可能又把另一个 case 搞坏了。
建立监控指标
Agent 幻觉不能只靠用户投诉发现。
至少要监控这些指标:
- 无证据回答率
- 检索为空仍回答的比例
- 工具调用失败率
- 参数校验失败率
- 输出校验失败率
- 人工接管率
- 用户纠错率
这些指标不是为了好看,是为了告诉你系统哪里在变差。
分场景设置不同安全等级
不是所有场景都要同一套防线。
闲聊场景,可以轻一点。
业务咨询,要证据约束。
写操作,要审批。
医疗、金融、法律,要更严格,很多时候只能给建议,不能替用户做决定。
安全等级要跟风险匹配。
不要简单任务上重防线,也不要高风险任务裸奔。