上下文膨胀:你无法用 Grep 搜寻的 AI 内存泄漏
一个长时间运行的智能体(agent)会话最初以 2K 上下文开启,现在却在为 40K token 的“死状态”买单。第三轮的检索结果、智能体早已跳过的目录列表、工具调用返回的 JSON 转储(即便其答案只是一个整数)—— 所有这些都在随后的每一次推理调用中如影随形,全额计费,并拖累注意力。这种模式在结构上与内存泄漏完全一致:无引用的数据无限制增长。但没有剖析器(profiler)能发现它,因为泄漏并不存在于进程内存中。它存在于对话历史里,而大多数智能体框架在发布时都没有配备回收机制。
成本同时体现在两个地方。token 账单呈二次方增长 —— 一个 20 步的循环,每一步贡献 1,000 个 token,累计产生约 210,000 个输入 token,而不是 20,000 个,因为之前的每一轮对话都会在后续的每一次调用中重新计费。而且模型本身也开始退化:当积累了 50K token 的噪声时,即使是拥有 1M token 窗口的模型,在实际任务上的准确度也会出现两位数的下降。你在花更多的钱,让模型更差地去思考它在三轮前就已经解决的问题。
这篇文章讨论的是如何像处理堆(heap)一样对待这些历史数据:如何进行逐轮 token 归因以区分关键上下文与积累的废弃数据,何时对工具输出运行可达性分析(reachability analysis),生产环境中的“上下文垃圾回收(context GC)”环节到底长什么样,以及为什么架构上的修复方案是意识到对话历史是可变状态(mutable state)—— 而非审计日志(audit log)。
泄漏是真实的,而且代价双倍
第一个成本是金钱。Transformer 推理是无状态的:每次调用都会发送整个上下文、之前的每一条消息、每个工具结果以及完整的系统提示词。如果第三轮出现了一个 4KB 的 JSON 对象,它将在后续的每次调用中占据 token,并按全额输入价格计费。如果独立建模每轮成本,一旦计入上下文积累,从业者往往会低估多步工作流成本 3 到 5 倍。正确的思维模型应该是等差数列 n(n+1)/2,而不是 n。
第二个成本是质量。Chroma 在 2025 年的研究测试了 18 种前沿模型在不同输入长度下的表现,发现每一种模型 —— GPT-4.1、Claude Opus 4、Gemini 2.5 —— 都会随着输入的增加而退化,无论其宣称的窗口大小是多少。经典的“迷失在中间”(lost-in-the-middle)效应会导致埋在长上下文主体中的信息准确率下降 30% 以上。单个干扰项就会显著降低基准性能;四个干扰项则会产生复合影响。1M token 的窗口在 50K token 时仍会发生性能腐化。模型卡片上的数字并不是你实际得到的性能。
结构性的洞察在于,这两个成本是耦合的。最便宜的 token 是你从未发送过的 token;最清晰的 推理建立在模型真正能关注到的上下文之上。修剪(Pruning)并不是牺牲质量换取成本优化的权衡 —— 它本身就是一种质量优化,顺便降低了成本。将其视为成本项目的团队最终会投入不足,因为单个产品功能的资金节省很少能证明专门工程投入的合理性。而将其视为可靠性项目的团队最终会完成这项工作,因为如果不做,智能体就会随着每一轮对话变得越来越笨。
为什么你无法通过 Grep 发现它
传统的内存泄漏是可以找到的,因为数据存在于你可以附加剖析器的进程中。你可以导出堆,按保留大小排序,泄漏的对象就会赫然在目。对话历史泄漏没有任何这类便利。那个“对象”是你正在与之通信的服务器上 JSON 数组中的一个轮次。没有剖析器,没有垃圾回收根集(GC root set),没有保留大小列。智能体框架交给你一个 messages: [...] 数组,你的工作就是不断向其中追加内容。
更糟糕的是,这种泄漏在大多数团队追踪的指标中是无声的。延迟逐渐增加,但每一轮对话仍能完成。成本在月度账单上表现为流量的固定倍数,这看起来像业务增长,而不是浪费。第一个信号通常是质量回退 —— 智能体忘记了第一轮给出的约束,忽略了用户重复的指令,或者根据与用户声明偏好相矛盾的检索结果产生幻觉。当有人将其与上下文长度联系起来时,团队已经多付了几个月的冤枉钱。
打破这种沉默的诊断举措是逐轮 token 归因。为对话构建器增加监控,给每条消息打上来源标签 :system_prompt、user_turn、tool_result:<tool_name>、agent_reasoning、retrieval:<source>。然后,在每次推理调用时,按标签记录 token 计数。在生产环境中运行一周后,你会看到一个看起来极其像内存泄漏图的柱状图 —— 一两个标签类别单调增长,而 user_turn 类别则保持大致恒定。那就是你的泄漏点。
膨胀的类别是可以预见的。来自宽泛操作的工具结果 —— list_files、search_codebase、read_documentation —— 是最严重的,因为它们返回的内容超出了智能体的需求,而智能体只使用了其中一小部分。其次是检索结果的膨胀,因为向量搜索无论 top-1 是否足够,都会返回 top-k。第三是智能体推理轨迹的膨胀,因为早期步骤的思维链(chain-of-thought)几乎从不影响后续步骤。系统提示词本身很少膨胀,但往往分解得不好 —— 无论条件是否触发,每一条条件指令在每次调用时都要付费。
工具输出的可达性分析
一旦你发现了泄漏,接下来的问题就是哪些条目已经失效了。垃圾回收(GC)的类比非常贴切:如果一个工具结果仍在影响决策,它就是“活跃”的;反之,它就是“死亡”的。诀窍在于,对话历史中的活跃性不是静态可确定的——它取决于智能体如何使用该结果,而非结果包含的内容。
可达性的粗糙版本是“最近性”:丢弃早于 N 轮的任何内容。这对于聊天助手非常有效,因为用户的最新消息几乎总是最相关的信号。但对于必须记住会话开始时所述约束的智能体,这种方法就会失效。修复方法是采用带有“固定例外”的最近性策略:被显式标记为约束的消息(如系统提示词、用户陈述的偏好、任务定义)无论存续多久,都不符合回收条件。
- https://www.trychroma.com/research/context-rot
- https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents
- https://platform.claude.com/cookbook/tool-use-context-engineering-context-engineering-tools
- https://learn.microsoft.com/en-us/agent-framework/agents/conversations/compaction
- https://www.augmentcode.com/guides/ai-agent-loop-token-cost-context-constraints
- https://dev.to/waxell/ai-agent-context-window-cost-the-compounding-math-your-architecture-is-hiding-2227
- https://venturebeat.com/orchestration/how-xmemory-cuts-token-costs-and-context-bloat-in-ai-agents
- https://blog.jetbrains.com/research/2025/12/efficient-context-management/
- https://redis.io/blog/context-rot/
- https://www.nelsx.com/p/context-window-garbage-collection
