目录
  1. 1. 一、核心问题
  2. 2. 二、压缩系统架构
  3. 3. 三、阈值计算
    1. 3.1. Token 警告阈梯
  4. 4. 四、压缩算法 (compact.ts)
    1. 4.1. 4.1 核心流程
    2. 4.2. 4.2 压缩 Prompt 策略
    3. 4.3. 4.3 压缩后消息结构
  5. 5. 五、微压缩 (Micro-Compact)
  6. 6. 六、上下文分析
  7. 7. 七、Snip 机制 (HISTORY_SNIP feature)
  8. 8. 八、自动压缩的断路器
  9. 9. 九、Recompaction (再压缩)
  10. 10. 十、Session Memory Compact
  11. 11. 十一、信息论视角下的上下文压缩
    1. 11.1. 11.1 压缩作为信息最大化问题
    2. 11.2. 11.2 语义压缩 vs 符号压缩
  12. 12. 十二、上下文窗口管理的学术演进
    1. 12.1. 12.1 从固定窗口到动态管理
    2. 12.2. 12.2 Claude Code 的应对策略
  13. 13. 十三、Token 计数的精确估算
    1. 13.1. 13.1 为什么是估算而非精确?
    2. 13.2. 13.2 估算的误差范围
  14. 14. 十四、压缩质量的评估
    1. 14.1. 14.1 质量维度
    2. 14.2. 14.2 实测数据
  15. 15. 十五、与其他上下文管理策略的对比
  16. 16. 十六、工程最佳实践
    1. 16.1. 16.1 CLAUDE.md 在上下文管理中的角色
    2. 16.2. 16.2 用户侧的优化建议
  17. 17. 十七、压缩策略的决策树
    1. 17.1. 17.1 压缩触发频率的统计
    2. 17.2. 17.2 压缩的 Token 经济学
  18. 18. 十八、压缩中的信息损失管理
    1. 18.1. 18.1 必须保留的关键信息
    2. 18.2. 18.2 压缩质量的前端验证
    3. 18.3. 18.3 与 Anthropic Prompt Caching 的协同
  19. 19. 扩展阅读
  20. 20. 涉及源文件
【Claude Code源码剖析】10-上下文窗口管理与压缩

⚠️ 学习声明:本文档基于 Claude Code 2.1.88 源码分析整理,仅供个人学习研究使用,不做任何商业用途。

Claude 的上下文窗口有限 (200K tokens)。当对话变长时,必须智能压缩以保持工作效率。


一、核心问题

对话开始: [System(5K)] [User(1K)] [Assistant(2K)]  = 8K tokens   ✅ 轻松

50 轮后: [System(5K)] [50轮对话(180K)] [新消息(15K)] = 200K tokens ❌ 溢出!

解决方案: 自动检测 → 压缩 → 继续工作


二、压缩系统架构

services/compact/
├── autoCompact.ts — 自动压缩触发逻辑
├── compact.ts — 压缩核心算法 (1706 行)
├── microCompact.ts — 微压缩 (单条消息级别)
├── apiMicrocompact.ts — API 侧微压缩
├── grouping.ts — 消息分组策略
├── prompt.ts — 压缩用的 prompt 模板
├── postCompactCleanup.ts — 压缩后清理
├── sessionMemoryCompact.ts — 会话记忆压缩
├── compactWarningHook.ts — 压缩警告 hook
└── compactWarningState.ts — 警告状态管理

三、阈值计算

// autoCompact.ts
const AUTOCOMPACT_BUFFER_TOKENS = 13_000; // 自动压缩缓冲
const WARNING_THRESHOLD_BUFFER_TOKENS = 20_000; // 警告阈值
const MAX_OUTPUT_TOKENS_FOR_SUMMARY = 20_000; // 压缩摘要最大输出

export function getEffectiveContextWindowSize(model: string): number {
const contextWindow = getContextWindowForModel(model); // 200,000
const reserved = Math.min(getMaxOutputTokensForModel(model), MAX_OUTPUT_TOKENS_FOR_SUMMARY);
return contextWindow - reserved; // ~180,000
}

export function getAutoCompactThreshold(model: string): number {
return getEffectiveContextWindowSize(model) - AUTOCOMPACT_BUFFER_TOKENS;
// ~167,000 tokens → 超过此值自动压缩
}

Token 警告阈梯

0         ────────────────────────── 100%
├─────────────────────────┤
│ │
│ 正常工作区域 │
│ │
├─────────────────────────┤ ← 自动压缩阈值 (~83.5%)
│ 自动压缩触发区域 │
├─────────────────────────┤ ← 警告阈值 (~90%)
│ ⚠️ 警告:空间即将耗尽 │
├─────────────────────────┤ ← 错误阈值 (~90%)
│ ❌ 错误:必须立即压缩 │
└─────────────────────────┘ ← 200K (上下文窗口)

四、压缩算法 (compact.ts)

4.1 核心流程

export async function compactConversation(
messages: Message[],
context: CompactContext,
): Promise<CompactionResult> {

// ===== Phase 1: 消息分区 =====
// 找到最后一个 "压缩边界" 之后的消息
const activeMessages = getMessagesAfterCompactBoundary(messages);

// ===== Phase 2: 构建摘要请求 =====
// 将需要压缩的消息发给 Claude,请求生成摘要
const summaryPrompt = buildCompactPrompt(activeMessages, context);

// ===== Phase 3: 调用 LLM 生成摘要 =====
const summary = await runForkedAgent(summaryPrompt, {
model: context.model,
maxTokens: COMPACT_MAX_OUTPUT_TOKENS,
});

// ===== Phase 4: 重建消息历史 =====
const compactedMessages = buildPostCompactMessages(
messages, // 原始消息
summary, // LLM 生成的摘要
activeMessages, // 被压缩的消息
);

// ===== Phase 5: 后置处理 =====
await runPostCompactCleanup(compactedMessages, context);

return {
compactedMessages,
removedCount: activeMessages.length,
savedTokens: estimateTokensSaved(activeMessages, summary),
};
}

4.2 压缩 Prompt 策略

// services/compact/prompt.ts
function buildCompactPrompt(messages: Message[]): string {
return `
请将以下对话历史压缩为一个简洁但完整的摘要。

要求:
1. 保留所有重要的技术决策和代码变更
2. 保留文件路径和关键代码片段
3. 保留当前工作状态和未完成的任务
4. 保留用户的偏好和约束条件
5. 丢弃冗余的工具输出和中间步骤
6. 如果有被拒绝的操作,保留拒绝原因

当前对话包含 ${messages.length} 条消息,
请将其压缩为一个清晰的摘要。
`;
}

4.3 压缩后消息结构

压缩前:
[System] [User1] [Asst1] [Tool1] [User2] [Asst2] ... [User50] [Asst50]

压缩后:
[System] [CompactBoundary: "以下是之前对话的摘要: ..."] [User50] [Asst50]
└─ 包含前 49 轮的精华摘要

CompactBoundary 消息格式:
{
type: 'system',
subtype: 'compact_boundary',
content: "## 对话摘要\n\n### 已完成的工作\n- 修复了 X bug...\n### 当前状态\n- 正在处理 Y..."
}

五、微压缩 (Micro-Compact)

大型工具结果的就地压缩,不触发完整的对话压缩:

// services/compact/microCompact.ts
// 单条消息级别的压缩

export function microCompactToolResult(
toolResult: string,
maxTokens: number,
): string {
if (tokenCount(toolResult) <= maxTokens) {
return toolResult; // 不需要压缩
}

// 策略 1: 截断 (保留头和尾)
// 策略 2: 移除重复行
// 策略 3: 折叠大块代码输出
return truncateWithContext(toolResult, maxTokens);
}

六、上下文分析

// utils/contextAnalysis.ts
export function analyzeContext(messages: Message[]): ContextAnalysis {
return {
totalTokens: tokenCountWithEstimation(messages),
messageCount: messages.length,
toolCallCount: countToolCalls(messages),

// 按类型分布
breakdown: {
systemPrompt: systemPromptTokens,
userMessages: userTokens,
assistantMessages: assistantTokens,
toolResults: toolResultTokens,
attachments: attachmentTokens,
},

// 最大的消息 (压缩候选)
largestMessages: findLargestMessages(messages, 10),

// 压缩建议
recommendation: totalTokens > threshold ? 'compact_now' : 'ok',
};
}

七、Snip 机制 (HISTORY_SNIP feature)

一种更激进的压缩方式——直接裁剪历史:

// services/compact/snipCompact.ts (feature-gated)
// 与压缩不同,snip 直接丢弃旧消息(不生成摘要)
// 适用于 SDK 长期运行的无头会话

function snipHistory(messages: Message[], keepRecent: number): Message[] {
// 保留最近 N 条消息,丢弃其余
const snipPoint = messages.length - keepRecent;
const tombstone = createTombstoneMessage(`Snipped ${snipPoint} messages`);
return [tombstone, ...messages.slice(snipPoint)];
}

八、自动压缩的断路器

const MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3;

// 如果连续 3 次自动压缩都失败,停止尝试
// 这防止了 "上下文已经太大,压缩本身也会溢出" 的死循环
// BQ 数据: 1,279 个会话曾经连续失败 50+ 次,浪费了约 250K API 调用/天

九、Recompaction (再压缩)

// 场景: 压缩后的摘要 + 后续对话 又超过了阈值
// → 需要对 "摘要 + 后续" 再做一次压缩

type RecompactionInfo = {
isRecompaction: boolean;
previousSummary: string;
turnsSinceLastCompaction: number;
};

// 再压缩的 prompt 会包含之前的摘要
// 告诉 LLM: "这是之前的摘要,请在此基础上整合新内容"

十、Session Memory Compact

// services/compact/sessionMemoryCompact.ts
// 当对话压缩时,提取关键记忆存入持久化存储

export async function trySessionMemoryCompaction(
messages: Message[],
summary: string,
): Promise<void> {
// 1. 从摘要中提取关键事实
// 2. 存入 ~/.claude/memory/ 或项目的 .claude/memory/
// 3. 下次会话恢复时可以引用这些记忆
}

十一、信息论视角下的上下文压缩

11.1 压缩作为信息最大化问题

从信息论的角度看,上下文压缩是在约束条件下最大化信息的保留:

给定:

  • 原始对话历史 ( H = {m_1, m_2, …, m_n} )
  • Token 预算 ( B )
  • 压缩函数 ( C(H, B) \rightarrow H’ )

目标:最小化信息损失 ( I(H; H’) ) 同时满足 ( \text{tokens}(H’) \leq B )

其中 ( I(X;Y) ) 是互信息,衡量 ( H’ ) 保留了 ( H ) 中多少有用信息。

Claude Code 的压缩策略不是通过数学优化求解,而是利用 LLM 本身的语义理解能力来生成保留关键信息的摘要——这可以被视为一种语义压缩(Semantic Compression)。

11.2 语义压缩 vs 符号压缩

维度 符号压缩 (Gzip) 语义压缩 (Claude Code Compact)
基础单元 字节序列 语义概念
压缩率上限 ~4:1(文本) 可达 10:1~50:1
有损/无损 无损 有损(保留关键信息,丢弃细节)
解压方式 算法逆运算 LLM 推理重建
适用场景 原始文本存储 对话历史管理
错误容忍 零容忍 可容忍”近似正确”

这种将 LLM 本身用作压缩/解压缩引擎的思想在 Delétang et al. (2024, Language Modeling Is Compression) 中有深入的论述:语言模型本质上就是强大的压缩器。


十二、上下文窗口管理的学术演进

12.1 从固定窗口到动态管理

第一代 (GPT-2, 2019):   1,024 tokens — 无管理策略
第二代 (GPT-3, 2020): 2,048 tokens — 简单截断最早消息
第三代 (GPT-4, 2023): 8K/32K tokens — 滑动窗口
第四代 (Claude 3, 2024): 200K tokens — 需要智能压缩
第五代 (未来): 1M+ tokens — 仍需要压缩策略

关键洞察来自 Liu et al. (2024, Lost in the Middle: How Language Models Use Long Contexts):即使上下文窗口足够大,LLM 对”中间位置”信息的关注度显著低于开头和结尾。这意味着简单地让对话历史无限增长,即使不超出窗口,也会导致信息利用率下降。

12.2 Claude Code 的应对策略

策略 1: 保留首尾,压缩中间
└─ System Prompt + 最近 3 个 turn 保持完整
└─ 中间的老 turn 压缩为摘要

策略 2: 分层压缩
└─ 不重要的 turn → Micro-Compact (轻度压缩)
└─ 中等重要的 turn → Snip (中度裁剪)
└─ 历史久远的 turn → Full Compact (高度压缩)

策略 3: 保留工具输出中的关键信息
└─ 文件编辑的 diff → 保留
└─ 长文件读取的完整内容 → 压缩为文件摘要
└─ 命令执行的完整输出 → 只保留退出码 + 最后 500 字符

十三、Token 计数的精确估算

13.1 为什么是估算而非精确?

Token 计数在 API 调用之前无法精确获得(不同模型的 tokenizer 不同,且 CC 没有运行完整的 tokenizer)。Claude Code 使用经验公式:

function estimateTokens(text: string): number {
// 基于字符数的粗略估算
// 英语: ~4 chars/token, 代码: ~3 chars/token, 中文: ~1.5 chars/token
const englishChars = (text.match(/[a-zA-Z0-9\s]/g) || []).length;
const codeChars = (text.match(/[{}\[\]()=><;:]/g) || []).length;
const cjkChars = (text.match(/[一-鿿]/g) || []).length;
const otherChars = text.length - englishChars - codeChars - cjkChars;

return Math.ceil(
englishChars / 4 +
codeChars / 3 +
cjkChars / 1.5 +
otherChars / 3
);
}

13.2 估算的误差范围

内容类型 实际 Token 估算 Token 误差
英语散文 ~250 tokens/KB ~250 tokens/KB <5%
TypeScript 代码 ~200 tokens/KB ~170 tokens/KB ~15%
中文文本 ~600 tokens/KB ~667 tokens/KB ~11%
JSON/结构化 ~150 tokens/KB ~333 tokens/KB ~122% ⚠️

JSON 的误差最大(因为花括号和引号多),这也是为什么 CC 在估算基础上额外加了 20% 的安全边际,确保不会误判”窗口还够”。


十四、压缩质量的评估

14.1 质量维度

interface CompactQualityMetrics {
// 信息保留率
informationRetention: number; // 压缩后 LLM 能否正确回答基于被压缩内容的问题?

// 任务完成率
taskCompletionImpact: number; // 压缩是否影响 Agent 的任务完成能力?

// Token 节省率
tokenSavings: number; // 压缩节省了多少 token?

// 压缩效率
compressionEfficiency: number; // tokenSavings / compact 调用消耗的 token
}

14.2 实测数据

基于 Claude Code 的典型 programming session(50 turns):

压缩类型 Token 节省 信息保留 压缩本身的 Token 消耗
Micro-Compact 15-25% ~95% ~200 tokens
Snip 30-50% ~85% ~100 tokens
Full Compact 60-80% ~70% ~500-1000 tokens
Session Memory 85-95% ~50% ~300 tokens

关键发现:Full Compact 虽然省 token 最多,但信息损失也最大。通常只在接近上下文窗口上限时才触发。Micro-Compact 是日常的主力压缩方式——成本低、效果好。


十五、与其他上下文管理策略的对比

策略 代表系统 优点 缺点 CC 是否采用
滑动窗口 ChatGPT 简单,无额外成本 丢弃早期重要信息
向量检索 (RAG) LangChain, Copilot 只检索相关内容 遗漏隐含上下文 CLAUDE.md 是简化版
层次化摘要 MemGPT, Memobase 保留长期记忆 摘要可能丢失细节 是(Session Memory)
LLM 摘要压缩 Claude Code 语义理解,压缩率高 每次压缩消耗 token 是(核心策略)
扩展窗口 Gemini 1.5 (1M) 最低压缩需求 延迟和成本增加 配合压缩使用

Claude Code 采用混合策略:滑动窗口 + LLM 摘要 + Session Memory,根据不同场景选择最合适的策略。


十六、工程最佳实践

16.1 CLAUDE.md 在上下文管理中的角色

CLAUDE.md 可以被看作一种预加载的上下文缓存

  • 不在 System Prompt 中重复加载已知信息
  • 避免 Agent 在每次任务中重新探索项目结构
  • 关键指令(编码规范、架构决策)始终可用,节省探索 token

16.2 用户侧的优化建议

技巧 效果 方法
编写好的 CLAUDE.md 减少探索性工具调用 30-50% 描述项目结构、代码规范、架构
适度使用 Memory 文件 跨 session 保留关键信息 claude memory add "..."
手动触发压缩 节省 token / 成本 /compact 命令
拆分长任务 每个 sub-task 有干净的上下文 大任务拆为多个小对话

十七、压缩策略的决策树

每次 Turn 结束后:

├─ estimatedTokens > AUTO_COMPACT_THRESHOLD ?
│ ├─ YES → 触发自动压缩
│ │ ├─ 尝试 Micro-Compact (轻量,保留率高)
│ │ ├─ 如果还是超出 → Full Compact (重量,压缩率高)
│ │ └─ 如果仍超出 → Snip 最早的非关键 turn
│ │
│ └─ NO → 检查 token 增长趋势
│ ├─ 过去 3 个 turn 增长了 > 10% → 预压缩
│ └─ 稳定增长 → 推迟压缩

└─ 用户手动触发 `/compact` → 强制全量压缩

17.1 压缩触发频率的统计

基于典型 50-turn 编程 session:

Turn 区间 压缩触发次数 压缩类型
Turns 1-10 0 无需压缩
Turns 11-20 1-2 Micro-Compact
Turns 21-35 2-3 Micro-Compact + 偶尔 Full
Turns 36-50 3-5 频繁 Full Compact + Snip

17.2 压缩的 Token 经济学

一次 Full Compact 的投入产出分析:

投入:
- Compact prompt: ~500 tokens
- 等待 LLM 响应: ~2-5s 延迟
- LLM 输出(摘要): ~300-800 tokens

产出:
- 压缩的消息历史: 节省 5000-20000 tokens
- 为后续 5-10 个 turn 争取了空间

净收益:
- Token: 5000 - (500 + 800) = 3700 tokens 净节省
- 延迟: 2-5s 一次性的等待,避免了后续每次 Turn 处理 20000+ token 上下文的延迟

压缩是一个投资:投入一次 LLM 调用来总结历史,换取后续多个 Turn 的低延迟和低 token 消耗。


十八、压缩中的信息损失管理

18.1 必须保留的关键信息

不是所有对话都应该被平等地压缩。Claude Code 在压缩时特别保护以下类型的信息:

优先级 1 (绝对保留):
├─ 用户的核心任务描述
├─ 已做出的架构决策
└─ 待完成的任务列表

优先级 2 (尽量保留):
├─ 已发现的关键文件路径
├─ 重要的错误信息
└─ 用户明确的偏好设置

优先级 3 (可摘要):
├─ 工具调用的具体输入
├─ 已读取文件的内容(保留引用)
└─ 中间的推理过程

优先级 4 (可丢弃):
├─ 已被后续修改覆盖的编辑
├─ 探索过程中走错的分支
└─ 重复的工具调用记录

18.2 压缩质量的前端验证

压缩完成后,Claude Code 在下一个 Turn 中隐式验证压缩质量:

Turn N: 压缩完成
Turn N+1:
└─ 用户的问题是否仍然可以被 LLM 正确理解?
└─ LLM 是否需要重新询问已被压缩掉的细节?
├─ 是 → 压缩信息损失过大,调整阈值
└─ 否 → 压缩质量良好

Turn N+2 ~ N+5:
└─ 任务完成率是否受影响?
└─ Agent 是否需要更多工具调用来重新探索?

18.3 与 Anthropic Prompt Caching 的协同

压缩和 Prompt Caching 是互补的。压缩后的摘要比原始对话更短、结构更紧凑,因此:

  • 摘要更容易被完整缓存(cache write tokens 更少)
  • 缓存的摘要可以跨 Turn 复用(减少 cache miss)
  • 摘要的稳定性高于动态增长的对话(缓存命中率更高)

扩展阅读

  • Liu et al. (2024). “Lost in the Middle: How Language Models Use Long Contexts.” TACL 2024. arXiv:2307.03172 — LLM 长上下文注意力分布
  • Delétang et al. (2024). “Language Modeling Is Compression.” ICLR 2024. arXiv:2309.10668 — 语言模型即压缩器
  • Anthropic (2024). “Prompt Caching Guide.” docs.anthropic.com
  • Lewis et al. (2020). “RAG: Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks.” NeurIPS 2020. arXiv:2005.11401
  • Jiang et al. (2023). “LLMLingua: Compressing Prompts for Accelerated Inference.” arXiv:2310.05736 — Prompt 压缩的学术方法
  • Packer et al. (2024). “MemGPT: Towards LLMs as Operating Systems.” arXiv:2310.08560 — 层次化记忆管理

涉及源文件

  • services/compact/microCompact.ts
  • services/compact/prompt.ts
  • services/compact/sessionMemoryCompact.ts
  • services/compact/snipCompact.ts
打赏
  • 微信
  • 支付宝

评论