深入理解 Claude Code 三层上下文压缩策略
背景
在阅读 learn-claude-code s06 章节 时,作者介绍了一套三层上下文压缩方案。本文整理了对这套方案的几个关键疑问及解答,帮助更深入地理解其设计逻辑。
核心问题:为什么要压缩上下文?
上下文窗口是有限的。Claude Code 在执行任务时会频繁调用工具:
read_file读一个 1000 行的文件 → 消耗 ~4000 tokenbash跑命令 → 返回几百行输出
读 30 个文件、跑 20 条命令,轻松突破 100k token。不压缩,AI Agent 根本没法在大型项目里持续工作。
三层压缩策略详解
Every turn:
+------------------+
| Tool call result |
+------------------+
|
v
[Layer 1: micro_compact] (静默,每轮执行)
将 3 轮前的旧 tool_result
替换为 "[Previous: used {tool_name}]"
|
v
[Check: tokens > 50000?]
| |
no yes
| |
v v
continue [Layer 2: auto_compact]
保存完整对话到 .transcripts/
LLM 做摘要,替换全部消息
|
v
[Layer 3: compact tool]
LLM 主动调用,触发同样的摘要机制
疑问一:第一层替换为占位符,LLM 不就丢失信息了?
这是最自然的疑虑,答案是:确实丢失了原始内容,但大多数情况下没关系。
核心假设:信息已经被"消化"进后续对话
当 LLM 读完一个文件后,通常会紧接着输出分析:
tool_result: "def foo(): ... [1000行代码] ..."
↓
assistant: "这个文件实现了 X 功能,关键逻辑在第 42 行,有个潜在 bug 是..."
这段 assistant 回复本身就是对文件内容的提炼和压缩。 3 轮之后,即使原始 tool_result 被替换成占位符,LLM 依然能从后续消息里"记起"分析过什么。
但这个假设并不总是成立
| 场景 | 是否有信息损失 |
|---|---|
| 读了文件,立刻做了详细总结 | ✅ 基本无损 |
| 读了文件,只说了"好的,继续" | ❌ 有损,细节丢了 |
| 需要反复对照某文件的原始内容 | ❌ 有损,需重新读取 |
| 命令输出很长但只用到其中一行 | ✅ 压缩大部分没关系 |
工程上的取舍
不压缩 → 上下文撑满,LLM 无法继续工作
压缩旧结果 → 损失部分细节,但至少还能干活
如果 LLM 发现需要某个被压缩掉的内容,它可以再调用一次工具重新读取。而完整历史通过 transcript 保存在磁盘上,信息没有真正丢失,只是移出了活跃上下文。
疑问二:上下文足够大的未来,这套方案还有必要吗?
上下文变大确实会让压缩"不那么紧迫",这个趋势已经在发生:
- GPT-4 最初 8k token,现在主流模型普遍 128k~200k
- Gemini 1.5 Pro 达到了 1M token
但有几个原因让压缩机制可能永远有存在价值:
1. 成本问题不会消失
上下文越大,每次推理费用越高。即使技术上"装得下",把 100 万 token 的历史每轮全量送给 LLM,成本是指数级的。
2. 注意力机制的"中间遗忘"问题
研究发现,LLM 对超长上下文里中间段的内容注意力会显著下降(Lost in the Middle 现象)。窗口大不等于全部内容都被有效利用。
3. 任务规模会跟着扩张
上下文从 8k 涨到 200k,人们就开始让 LLM 处理整个项目;涨到 1M,就开始让它处理整个公司的代码库和文档。
窗口变大,任务也变大,瓶颈只是后移,不会消失。
4. 这套思路会演化成更高级的形态
与其说"压缩会消失",不如说它会进化成记忆系统——把不活跃的信息存到外部数据库,用 RAG 按需检索。本质是压缩思路的升级版,而不是被淘汰。
上下文变大,会让"压缩"从必须变成可选,但只要有成本、有注意力瓶颈、有更大的任务,它就不会真正消失——只会换一种更高级的形式继续存在。
疑问三:第二层和第三层好像没有区别?
两层用的是完全相同的压缩机制,区别只有一个:谁来触发。
| 第二层 auto_compact | 第三层 compact tool | |
|---|---|---|
| 触发者 | 系统自动检测 token 数 | LLM 主动调用工具 |
| 触发时机 | token 超过阈值(如 50000) | LLM 自己判断"该压缩了" |
| 用户可见 | 无感知,静默发生 | LLM 会明确说"我要压缩一下" |
为什么要分两层?
因为阈值检测是个粗糙的信号,不够聪明:
- token 才 30000,没到阈值,第二层不触发。但 LLM 预判接下来要读 20 个大文件,可以主动调用 compact 提前清理,给后续任务腾出空间。
- 有时 token 超了阈值,但当前正处于关键推理阶段,强制压缩反而打断思路。第三层让 LLM 自己拿捏时机。
第二层是被动防御(到上限才压),第三层是主动规划(LLM 自己预判并决策)。
总结
这套三层压缩策略体现了一个工程设计上的经典模式:
同一个机制,配两种触发方式——一个兜底、一个灵活。
类似的例子比如 GC(垃圾回收):内存到阈值自动触发,但程序也可以手动调用 System.gc()。本质是一回事,但给了开发者(或 LLM)一个"主动干预"的口子。
理解了这个模式之后,再看其他系统设计会发现到处都是这个思路。