多 Agent 系统的上下文修罗场:我们是怎么被 Token 逼疯的
多 Agent 系统跑久了,Agent 会开始"失忆"——上下文越堆越多、关键信息被稀释、模型开始跑偏。这篇文章聊上下文污染的成因和几种工程解法。
做多 Agent 系统之前,我以为最大的坑是 Agent 之间怎么通信。
做了之后才发现,通信根本不是最头疼的。真正让人抓狂的是——Agent 跑着跑着就开始"失忆"。
具体表现很诡异:第一轮问它,回答得很精准;跑了几轮工具调用之后,开始答非所问;再往后,它甚至开始忽略 system prompt 里的约束,自己编出一些不存在的工具。
一开始以为是模型的问题,换了更强的模型,无非是多撑几轮再崩。崩的方式不同,崩的结果一样。
后来想明白了:问题不在模型,在上下文。
Agent 的上下文窗口不是黑箱,它就是一块有限内存。 你往里塞什么、塞多少、什么时候换出,直接决定了 Agent 能活多久。
这篇文章不聊 Agent 的基本概念,直接聊上下文治理——那些让 Agent 在长任务里撑住的实际做法。
上下文是怎么腐烂的
先说腐烂的机制。
Agent 每轮 Loop 都在往上下文里追加东西:用户输入、工具调用结果、LLM 的推理链、函数返回的结构化数据……一个 Task 跑下来,几万个 Token 就满了。
这还不是最要命的。
真正麻烦的是,这些信息不是均匀分布的。工具调用日志可能占了 60% 的 Token,但里面有价值的信息可能只有 10%。推理链里模型反复自我修正的记录占据了大量空间,真正有用的结论就一句。
更糟糕的是,模型对上下文的注意力不是均匀的。Transformer 的自注意力机制里,n 个 Token 要做 n² 次计算。上下文越长,噪声越多,信噪比下降的速度远比想象中快。
这就是我理解中的上下文腐化——它不是"信息太多",是"关键信号被噪声淹没了"。
我踩过的一个具体坑:Agent 调了三次搜索工具,每次返回 10 条结果,三份返回结果加起来快 8000 Token。结果在后续推理中,模型去关注了第三次搜索里一个不太相关的段落,忽略了前两次的关键发现。输出被带偏了。
后来查了一下,"Lost in the Middle" 现象早就被研究透了——模型对上下文的开头和结尾更敏感,中间部分容易丢失。但工程上真正要把这个问题的解法落地,比看论文要复杂得多。
解法一:工具调用结果别当垃圾倒
最容易改善也最容易被忽略的一步:管住工具的输出。
很多 Agent 框架默认会把工具的全部返回结果原样塞回上下文。Search API 返回了什么,LLM 就看到什么。但实际情况是,一个搜索 API 可能返回 10 条结果,真正和当前任务相关的可能只有 2 条。
我之前犯过一个错误:写工具的时候只关注"能不能查全",没关注"查回来之后怎么用"。结果 Agent 被一堆不相关的返回结果带偏了。
现在的做法是三步:
- 工具内部先做一次裁剪。 搜到 10 条结果,先用规则或轻量 Reranker 只保留最相关的 3-4 条,再返回给 Agent。
- 工具描述里写清楚"什么时候不该用"。 好的工具描述不只写"这个工具能做什么",还要写"什么情况下别调这个工具"。
- 工具返回结果只保留摘要。 原始数据的细节已经在工具执行阶段被消化了,返回给 LLM 时只需要关键结论。
这一步做好了,Agent 的可用窗口能从 6-7 轮扩展到 15-20 轮。不夸张。
解法二:Context Compaction 不是偷懒,是续命
工具输出控制是做减法。但任务跑久了,上下文还是会满。这时候需要 Compaction。
Context Compaction 说人话就是:快满的时候,把当前上下文交给 LLM 做一次压缩,用摘要开启新一轮上下文。
但 Compaction 有一个非常棘手的取舍——该保留什么、该丢掉什么。
我试过几种策略,踩了不少坑。
第一次试的时候,我写了一个保守的压缩 prompt:"请保留所有重要信息"。结果 LLM 几乎把全部内容都留下来了,压缩了个寂寞。
第二次换成了激进的 prompt:"请只保留核心事实"。结果 LLM 把中间推理链里的关键假设给丢了,下一轮推理直接崩溃。
最后摸索出来的规则分三层:
- 绝不压缩的:system prompt、核心工具描述、当前任务目标
- 可摘要化的:过去的推理链、工具调用历史——让 LLM 只保留关键结论
- 可直接丢弃的:调试日志、重复的错误尝试、已完成的子任务细节
用这套规则跑了半个月,Agent 在长任务里的稳定性明显提升了。但前提是——每一轮的 LLM 调用都要有明确的"边界意识":哪些信息这一轮用完就可以丢了,哪些必须留到下一轮。
解法三:用 Sub-agent 隔离上下文
前面两种方法都是在同一条上下文链里做优化。但有一种场景,单链怎么优化都没用——任务本身就太复杂了。
比如一个任务需要同时搜索资料、读取文档、对比分析、生成报告。如果所有步骤都在同一个上下文里跑,到第三步的时候上下文就已经炸了。
这时候需要的不是优化上下文,是拆分上下文。
Sub-agent 的模式是:主 Agent 把子任务分给专门的 Sub-agent,每个 Sub-agent 在自己的上下文里跑完,返回来一段 500-1000 Token 的摘要。
主 Agent 的上下文始终是干净的——详细搜索过程被隔离在 Sub-agent 的上下文里,主 Agent 只拿到最有价值的信息。
我在多 Agent 系统里试过这个方案。实测下来,一个不需要 Sub-agent 时只能跑 8-10 轮的任务,拆成 3 个 Sub-agent 后,主 Agent 上下文的压力降低了 60% 以上。
代价也很清楚:多了一层调度和协调的逻辑。Sub-agent 返回的摘要质量直接决定主 Agent 能不能正确决策。摘要写得太粗,关键信息丢了;写得太细,还不如不拆。
几个容易忽略的细节
最后补几个小细节,实战中踩过的:
System Prompt 不是越详细越好。 我之前习惯把各种约束全部写进 system prompt,结果开头就占了 3000 Token。后来发现,只有 40% 的规则是 Agent 真正需要每一轮都看到的。剩下的其实可以做成工具描述或者 Skills,按需激活。
Token 消耗不是均匀增长的。 Multi-Query RAG 每次改写问题 + 多次检索,Token 消耗是指数级的。用之前要先算账:这个场景值不值得花这么多 Token。
Agent 的上下文和人的工作记忆真的一样。 你在写代码的时候脑子里同时能装几件事?Agent 也是。给它太多并行目标,它就会在任务之间反复横跳,哪件都做不好。
说到底,上下文治理不是一个"抄个方案就搞定"的事。它需要你理解自己的 Agent 是怎么工作的——哪种信息会膨胀、哪种信息容易丢失、模型在什么阶段开始跑偏。
没有银弹,只有工程。
但至少,先别让工具调用结果把自己淹死。
评论
发表评论