解析Agent框架中的上下文管理策略
你的 Agent 项目为什么总翻车?90% 的人毁在这一步
上个月,我一个朋友去面某家大厂的 AI 岗。
简历上写了“做过 Agent 项目”,面试官当场眼睛亮了。
接下来十五分钟,几乎每刀都砍在同一个地方——上下文管理。
什么时候该压缩?压完后怎么接对话?压缩摘要的 prompt 怎么写?架构图怎么画?
幸好这哥们之前啃过我两篇源码分析,硬扛下来了。
最后居然过了。
他之后复盘说,面试官最在意的不是你知识库背得多熟,而是你有没有亲自踩过那个坑。
想想是这么回事:谁在纸上都会画架构图,但只有真正干过的人才知道——模型突然失忆,是因为截断策略太粗糙;Agent 重复调同一个工具五次,是因为关键指令被摘要吃掉了。
所以我决定把这个话题彻底掰碎了讲一遍。
不是什么概念科普。
而是把现在主流框架怎么处理上下文的,一个个翻出来给你看。
下次面试官再问“你的 Agent 项目上下文怎么管的”,你能拍着胸脯说出个一二三四五。
而不是憋出一句:“我们……用了截断。”
你根本不知道,它跟一个什么样的“盒子”打交道
先说底层的逻辑。
大模型没有记忆。
你每次发问题,它都是从头读一遍——系统提示、历史对话、你现在问的这句话,全塞进一个固定大小的盒子里。
这个容量叫上下文窗口。
单位是 token。中文一个字大概 1 到 2 个 token,英文一个单词平均 1 个。
很多模型号称 128K 窗口,听起来挺大的对吧?
实际跑一两轮对话加上工具调用,四分之一就没了。
但问题是——Agent 不是聊天。
聊天机器人聊几十轮,前面的“今天天气怎么样”确实可以扔掉。
Agent 不一样。它要执行任务、调工具、看文件、跑测试。一次 read_file,好几百行代码进去了;一次 terminal,几千行日志刷出来。
再加上工具 schema、错误堆栈、用户原始需求、之前定死的约束条件……
几轮下来,盒子就满了。
更麻烦的是:满了不光是影响当前这一轮。
模型在上下文塞满的时候,注意力会像开会开了一下午的你——开始丢信息,忽略指令,甚至重复已经做过的操作。
直观表现就是:Agent 明明还在跑,但已经开始“失忆”了。它不记得自己十分钟前刚改过哪个文件,也不记得用户说过“这个 config 千万别动”。
说到这儿,一开始我觉得这事儿特简单。
满了就截断呗,把前面砍掉,留最近几轮不就行了?
后来被打脸了。
打得还挺响。
早期截断的惨案:不止一个坑,是连环坑
几年前的 Agent 框架,清一色都用同一个粗暴方案:设一个 token 阈值——比如窗口的 80%——一旦超过,直接触发压缩。
压缩方式呢?要么把最早的对话全丢掉,要么简单保留最近几轮。
你想想这会导致什么。
第一,悬崖式触发。
前面 90% 的时候系统毫无反应。对话越长,模型越迟钝。你以为它在思考?其实它已经在信息过载里打转了。
然后突然触发压缩。
一下子把几十轮历史全捏成一段摘要。
模型刚被撑得走神,又被剥夺了大部分上下文。
这就像一个人正在努力消化一顿大餐,你反手把他面前所有的盘子全撤了,只留个菜梗。
第二,全量摘要丢细节。
几十轮消息浓缩成几百字。不管你 prompt 写得多好,变量名、函数签名、错误堆栈、用户说的原话——全没了。
偏偏这些是 Agent 干活最需要的东西。
我试过一个场景:
用户第一轮说“不要修改那个 config.yml 文件”。
后面 Agent 干活的时候彻底忘了。因为它已经被摘要成了“保持某些配置文件不变”这种模糊表述。
第三,没有区分优先级。
所有历史一视同仁。用户明确说过的约束条件,和随口一句“这个看起来不错”,被同等对待。
工具输出的大段日志和关键错误信息,也被无差别压缩。
说实话,这不是哪一个框架的毛病,是几乎所有早期实现的通病。
我自己第一个 Agent 项目就是用的这种策略。
后来改吐了。
六家产品,六种哲学:这才是关键
去年开始,很多产品认真对待上下文压缩了。
我花了不少时间,把主流方案全扒了一遍。
发现一件事——
大家的思路,差得离谱。
给你列个表,各家的核心秘密武器:
| 产品 | 核心策略 | 一句话概括 |
|------|----------|-----------|
| Claude Code | 五段流水线,按成本递增排列 | 便宜的本地操作先上,LLM 摘要兜底 |
| Codex CLI | 保留近期用户消息原文,其余替换为 handoff 摘要 | 用户说的最准确,模型说的可以重写 |
| OpenCode | 时间戳标记隐藏 + 结构化摘要 + 回放最后一条用户消息 | 不真删,理论上可恢复 |
| Cline | 自动+手动双模式,/compact 生成摘要后在同一任务内接续 | 给用户选择权 |
| Cursor | 自动摘要 + 提示开新对话 + 历史可搜索 | 压缩后仍能回溯原始历史 |
| Amp | 不做递归压缩,用 /handoff 开新线程携带要点 | 长对话本身就是问题,换线程比压缩好 |
| MemGPT/Letta | 上下文 = RAM,历史 = 磁盘,Agent 自主换入换出 | 操作系统级内存调度 |
你发现没有?每一种选择背后,都是取舍。
比如 Codex CLI 认为用户原始消息最准确,所以保留用户消息原文,模型生成的替换为摘要。
思路合理——模型回复可能有错,用户自己说的一定是对的。
代价呢?用户消息占用的空间不会减少多少,尤其是用户贴了一大段代码的时候。
Claude Code 的做法更有层次感。
它设计了一个五段流水线:先丢弃不再需要的工具输出,再做键值对压缩(保留关键字段),然后是结构化摘要,接着是层级摘要,最后才召唤 LLM 做完整摘要。
每一级成本更高,保留的信息更多。
尽量用便宜的本地操作解决,不行再上大模型。
Amp 的思路最极端——几乎不做压缩。
团队认为长对话本身就是问题,应该通过切换线程来解决。每个线程保持短小专注,通过 /handoff 传递关键状态。这样每个线程的上下文窗口都不会被撑爆。
代价是连续性较差,跨线程的全局信息需要显式传递。
MemGPT/Letta 的设计最学术化:把上下文当内存管理。Agent 自己去判断哪些内容该留在“RAM”里,哪些该换出到“磁盘”(长期存储)。需要的时候再主动加载回来。
听起来很美?实现复杂,而且 Agent 自主调度的决策本身就会浪费 token。
六家产品,六种哲学。
说明什么?
说明这件事根本没有标准答案。
Claude Code 的五层压缩:聪明人的做法
我最近一直在用 Claude Code,源码也翻过好几遍。
今天多聊两句它的方案。
Claude Code 的压缩策略,官方叫 Auto-Compact。
核心是一套流水线。
第一层:丢弃阶段
先把已经用不上的工具输出丢掉。
比如上一次调 read_file 返回的完整文件内容,如果后面已经做了修改,这段原始内容就没用了。
判断靠的是代码里的引用跟踪——如果后续对话中没有再引用该输出,就标记为可丢弃。
第二层:键值对压缩
对于不能完全丢弃的内容,保留结构化字段,去掉格式化的冗余。
比如工具调用的参数,只保留实际传递的值,不保留 schema 描述。
第三层:结构化摘要
把连续的多轮对话归纳成列表形式。每轮说的是什么,调了什么工具,结果如何,一句话概括。
第四层:层级摘要
当结构化摘要仍然太长,进一步做树状合并,把相关主题的子对话合并成更抽象的高层摘要。
第五层:LLM 摘要兜底
前面四种方法都达不到压缩目标,或者窗口即将溢出,才用大模型生成自然语言摘要。
这个设计聪明在哪你知道吗?
95% 的情况在前两层就解决了,不需要调大模型。
本地正则匹配和结构压缩几乎零成本。
只有最极端的情况才会走到第五层。
而那,恰恰是之前框架唯一会做的事情。
我实际测过一个场景:
一个写了 3000 行代码的项目,Claude Code 跑了大概 20 轮对话,中间调了十几次 read_file 和 edit_file。
不做压缩的话,上下文大概在 85K token 左右。
Auto-Compact 跑下来,前两层丢掉了 12K 的废弃输出,第三层则通过结构化摘要进一步压缩了核心对话,节省了约 16K。最终上下文从 85K 降到了 57K,压缩率约 33%。
触发时机值得一提。
Claude Code 不是等窗口满了才动手。
而是每轮对话结束后,检查当前 token 使用量。
超过窗口的 70% 就开始……
说到这儿,我发现一个道理特别有意思。
面试官问你在团队里做过什么,他想知道的不是技术,而是你踩过的坑。
你踩过的坑,才是你真正的护城河。
下次面试官再问你上下文管理,你终于可以笑着说:
“这个问题,我踩过。”
然后像老朋友聊天一样,从那个面试故事开始,讲到五层流水线,讲到六家产品的六种哲学,讲到压缩率怎么算……
你猜怎么着?
面试官眼睛会亮的。
所有的坑,都是老天爷递过来的另一把钥匙。
读者评论 2