小白从零开始学agent(四)——上下文漂移与如何约束大模型幻觉

  "你的 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 幻觉不能只靠用户投诉发现。

  至少要监控这些指标:

  • 无证据回答率
  • 检索为空仍回答的比例
  • 工具调用失败率
  • 参数校验失败率
  • 输出校验失败率
  • 人工接管率
  • 用户纠错率

  这些指标不是为了好看,是为了告诉你系统哪里在变差。

分场景设置不同安全等级

  不是所有场景都要同一套防线。

  闲聊场景,可以轻一点。

  业务咨询,要证据约束。

  写操作,要审批。

  医疗、金融、法律,要更严格,很多时候只能给建议,不能替用户做决定。

  安全等级要跟风险匹配。

  不要简单任务上重防线,也不要高风险任务裸奔。

相关推荐
明月_清风1 小时前
显存即正义:不同显存容量能训多大的模型?一文说清硬件边界与训练策略
前端·后端·ai编程
constCpp1 小时前
AI 编程:追不完的工具,理得清的问题
人工智能·ai编程·ai-native
VIP_CQCRE2 小时前
用 Ace Data Cloud 接入 Codex:让 AI 编程工具配置更简单
openai·ai编程·开发工具·codex·acedatacloud
liliangcsdn2 小时前
OpenClaw如何通过js编程从浏览器提取数据
ai编程
小虎AI生活11 小时前
AI 培训现场翻车后的应急方案设计:从工具选型到教学流程的系统化思考
ai编程
犀利豆15 小时前
我和 Claude Code 一起写了一本介绍 Claude Code 原理的书
人工智能·ai编程·claude
Leonardo-15 小时前
30分钟打造本地AI问答系统
ai编程
Fluxproxy16 小时前
AI数据集采集、批量模型推理报错频发?AI项目网络稳定性优化实战
python·ai·ai编程
OpenTiny社区16 小时前
GenUI SDK v1.3.0 发布|多框架兼容,一键换物料,渲染器 & 演练场全面增强!
前端·ai编程