上下文污染破坏LangGraph智能体的七种隐性方式
过时状态、庞大的工具缓存、重复的RAG片段、有损摘要以及臃肿的工具集:七种不会引发错误的故障,及其修复方法与检测方式。
来源: 7 Ways Context Pollution Quietly Killed My LangGraph Agents
想象这样一个LangGraph智能体,它告诉用户项目“按计划可在7月交付”。但日历上显示的是8月。日志中既没有异常记录,也没有工具调用失败的情况,所有指标都显示正常。根据团队追踪的所有标准,这个智能体状态良好,却在三天时间里悄悄误导了人们,直到有人发现异常。
这类事件都有一个看不见的根源——上下文污染,下面的七个案例都源于此。每个案例都没有产生任何错误。这些场景是虚构的示例,公司名称和数字均为编造,但其背后的机制正是智能体开发者经常遇到的问题。
两分钟了解上下文污染
当智能体进行推理时,它看不到你的应用程序,只能看到一整段文本,即上下文窗口,除此之外别无他物。可以将其想象成一张办公桌:桌上的任何内容都可以被使用,而桌子之外的东西对智能体而言就不存在,即便它们就放在几英尺外的数据库中。
每轮对话中,会有几样内容被添加到这张“办公桌”上:
- 系统提示,也就是指令
- 工具定义,即智能体可以调用的功能
- 对话历史,也就是迄今为止说过的所有内容
- 工具输出,即你的 API 返回的结果
- 状态,也就是 LangGraph 节点存储并传递的信息
- 检索到的文档,如果你使用了 RAG 技术的话
初学者很少听说这一点:输入内容的长度是有限制的,且模型的注意力并不会均匀分配在所有内容上。某些内容虽然存在,但实际上会被忽略;它们还可能过时、重复或相互矛盾,而模型却不会提出异议——它会默默地处理这些无用信息而不作任何反馈。
上下文污染指的是那些错误、过时、冗余或矛盾的信息存在于模型处理范围内,导致其行为变差,却不会引发任何错误提示。这才是真正的危险——系统崩溃会在凌晨2点将你惊醒,而上下文污染则会让模型的输出长期出现20%的错误。
下面的每个案例都会说明其症状、根本原因、解决方案以及如何在自己的系统中检测它,因为那些无法被发现的故障终会再次出现。
案例1:模型卡在7月状态
症状。以某个虚构的应用程序的报表生成模块为例,假设名为Meridian Energy。该模块一直使用实际上属于上个月的“本月”数据。用户将问题归咎于数据管道,但这其实并无不妥。
根本原因。在会话启动时,开发者仅在LangGraph状态中存储了一次当前有效的报表周期(一个名为current_period的键,值为2026-07),并且检查点也保存了这一信息。后来用户恢复了旧会话——这是一个常用功能——而保存的检查点又把日期带回了7月。该报表模块并未出现混淆,它只是按照自身的上下文行事,而其上下文显示的正是7月。
修复方法。采用一种重构状态设计的规则:永远不要存储那些可以廉价重新计算的内容。当前日期、当前周期以及任何标有“today”的信息,都应在每次运行开始时重新计算,而非被持久化保存。将current_period从需要检查点的状态中移出,放到一个会在每次调用时重新计算它的独立节点中,只需添加一行代码,就能彻底避免这类错误。
检测方法。在开发阶段,每次运行开始时打印状态字典,并检查每个键值是否可能已经过时以及你是否能察觉到这一问题。凡是存储日期、状态或“current”字样的键值都值得怀疑。
案例2:8,000个标记的工具转储数据淹没了指令
症状。该智能体开始无视格式规则。系统提示要求每个答案都以汇总表开头,它起初确实如此做,但后来有时就不遵守了。这种随机出现的情况是最糟糕的故障类型。
根本原因。有一个工具通过API获取原始JSON响应并将其放入上下文中。对于小型查询,数据量为400个标记;而对于大型查询,嵌套的JSON数据量会激增至8,000个标记以上,其中包含大量编号、元数据、空值字段以及没人需要的分页信息。这些数据插入到上下文中间后,原本清晰的指令就变成了嘈杂环境中的微弱声音。人们的注意力都被那堆JSON数据占据,格式规则便实际上消失了。
更糟糕的是,这种情况仅发生在处理大型响应时。小型查询仍能正常工作,因此要将“智能体行为异常”与“用户提出了复杂问题”联系起来,耗费了数周时间。
修复方案。 工具应返回事实信息,而非冗余数据。应在每个工具内部添加一个简单的转换层,用于提取代理所需的少量字段,将其紧凑地整理后丢弃其余部分。通过这种方式,某个工具的处理量从约8,000个标记减少到了约300个。
# Before: return response.json() # 8,000 tokens of noise
# After:
def summarize_response(data: dict) -> dict:
return {
"project": data["name"],
"status": data["status"],
"owner": data["owner"]["displayName"],
"due": data["dueDate"],
"open_risks": len(data.get("risks", [])),
} # ~300 tokens of signal
检测。记录每个工具输出的标记数量。如果某个工具返回的标记数超过整个系统提示语的长度,那它就会成为导致生成过长响应的污染源。
案例3:从未替换原始内容的修正
症状。用户先询问东北地区的信息,随即又纠正自己并改为询问西北地区。智能体正确地给出了西北地区的答案。在四次对话后,未经再次提问,它又回到了东北地区的话题上。
根本原因。默认情况下,对话历史是只允许追加内容的,因此错误的指令和修正内容都会永久保留在上下文中。通常模型会正确地重视较新的信息,但并非总是如此;随着对话内容的增加,这两个版本的信息会变得同样可信。智能体并非记错了,而是记入了过多信息。
解决方案。有两种选择。
- 立即修正,不要累积。发现错误时不要只是简单添加修正内容,而应将新值写入状态中的结构化事实块(区域设置为西北部),让智能体将其视为权威信息,这样就不必在每轮对话中反复讨论混乱的记录。
- 编辑历史记录。LangGraph允许在消息重新进入上下文之前对其进行删减或重写,因此被取代的指令可以直接删除。
更重要的教训是:对话历史记录只是日志,而非知识库。不要让智能体在每轮对话中都从记录中推导真相。
检测方法。在评估中加入自我修正场景:用户先说X,然后修正为Y,接着再提出五个问题。如果智能体又回到X,那就说明存在此缺陷。
案例4:RAG五次返回同一答案并隐藏了真实答案
症状。用户提出了文档中已有明确解答的问题,但系统却给出了含糊不清的回复。检索日志显示有六个内容被提取出来,看似检索功能正常运作。
根本原因。语料库中存在近乎重复的内容:正如真实企业常做的那样,某段政策文本被粘贴到了五份不同的文档中。相似度搜索功能因此能完美地找到五个几乎完全相同的片段,这些片段挤占了包含真实答案的那个不同片段的位置。相当于桌子上的五个座位都被同一个“客人”占用了。
解决方案。在检索时进行去重处理。即便使用简单的方法也能取得良好效果:获取20个候选结果,剔除与已选结果过于相似的部分,保留最具多样性的那组。如果你的向量存储支持最大边际相关性功能,其实已内置了相同逻辑。无需修改模型或提示词,政策相关问题的回答质量就能立即提升。
检测方法。记录检索到的内容并每周查看一次。如果某句话在一批结果中频繁出现两次,那就意味着你正在为冗余信息支付相关性成本。
案例5:忽略了关键规则的摘要
症状。为控制长时间会话的成本,团队加入了滚动总结功能:每隔N轮就会压缩较旧的消息。这是标准做法,也广受推荐。但后来有员工开始将草稿记录纳入正式报告,尽管用户早在11轮前就明确要求“排除草稿”。
根本原因。总结工具本应按既定规则工作。它保留了用户关心的核心内容(即区域报告及布局相关要求),而忽略了其认为次要的信息。然而“排除草稿”绝非次要事项,而是一项明确要求——这类约束不可变通,要么完整保留,要么完全剔除。在所有七次故障中,这一问题最值得担忧,因为在演示时总结功能看似毫无问题。
解决方案。在进行任何压缩操作之前,先将内存分为两个独立区域。
- 叙事部分可以被大幅简化。
- 约束条件、决策和偏好设置会被整理成结构化的列表,且绝不会被简化。该列表中的条目只能被添加或明确删除,且在每一轮中都会完整地纳入上下文中。
总结提示可以直接说明这一点:在简化之前,需将所有指令、约束条件和决策逐字复制到约束列表中。
检测方法。在开发阶段,需在每次总结操作前后对比约束列表的差异。如果某个约束仅出现在总结段落中,那就说明压缩机制破坏了你的规则。
案例6:导致智能体不信任有效工具的堆栈跟踪信息
症状。有一款工具曾短暂出现过一次超时情况,但在紧接着的下一次调用中便恢复正常工作。然而在会话的剩余时间里,客服人员一直告诉用户无法连接到报告系统,尽管实际上它已经成功连接到了该系统。自然地,用户更相信这些话而非实际数据。
根本原因。完整的错误追踪信息,即30秒后出现的TimeoutError错误,被记录在历史记录中且始终未消失。对模型而言,“报告系统不可靠”便成了一种既定事实,此后所有的回答都受到了这一因素的影响。一次短暂的错误最终演变成了永久性的问题。
修复方法。将错误视为事件而非事实。在成功重试后,用一行备注替换历史记录中的错误信息,例如“(临时错误,已通过重试解决)”。如果某个工具彻底失效,则应为处理程序提供结构清晰的状态信息,而非堆栈跟踪信息。堆栈跟踪应存放在日志中,绝不能出现在上下文信息里。
检测方法。在实时上下文中搜索字符串 Error、Traceback 和 Exception。每一个匹配项要么是正在发生的故障,要么是残留的、会干扰后续处理的错误信息,这两类都应被清除。
案例7:一个窗口中显示二十五个工具
症状。数月来,该工具的运行速度越来越慢,价格也不断上升,这在本就功能日益增多的情况下是意料之中的。它还会为简单的请求选择错误的工具,比如当用户想要项目摘要时却返回合规性导出文件。其实并没有什么功能出故障,只是整体变得愈发复杂混乱。
根本原因。该工具集已包含25种工具,每种工具都有冗长的描述和JSON架构,在用户输入第一个字之前就已经占据了大约12,000个标记。每次响应时,模型都需要考虑25种选项,其中许多选项的描述相互重叠,比如“获取项目数据”、“检索项目详情”和“取得项目信息”。选择工具本应是一项推理任务,但团队却将其变成了一个有25个类似答案的多项选择题。
解决方案。分为两个部分。
- 大力精简。合并那些功能重叠的工具,此处将数量从25减少到11,并围绕一个问题重写每个工具的描述:模型何时应该选择该工具而非其他类似工具?
- 根据上下文加载工具。并非每个节点都需要所有工具。在LangGraph中,可以轻松地为每个节点绑定仅相关的工具子集,因此生成报告的节点可能只会看到4种工具而非25种。
每轮的标记成本降低了约三分之一,错误选择工具的情况也几乎消失了。
检测方法。仅统计工具定义中的标记数量。如果这个数值超过了系统提示词以及典型对话的总长度,那么你的智能体就相当于在一场大部分内容都是错误的开卷考试中作答。
这七种方法的共同点
重新阅读这七点后,一个规律显现出来:没有任何组件损坏,也没有任何内容缺失。污染源始终是多余的、过时的或重复的内容。
正因如此,普通的调试方法在这里行不通。工程师们受训去寻找缺失的部分:遗漏的空值检查、未处理的异常。而处理上下文污染则需要相反的技能,即学会寻找本不应存在的东西。
如果你只坚持一个习惯,那就去阅读智能体接收到的原始上下文。跳过日志和追踪面板,直接查看模型在真实生产环境中实际接收到的文本。这对任何开发智能体的人来说都是最值得投入时间的环节,却几乎没人会这么做。
部署前的六十秒检查清单
- 是否有任何状态值是日期、状态或类似“当前”这样的表述?(情况1)
七个问题。任何“可能,但不确定”的答案都意味着问题尚待解决。