开源版Claude Code可运行源码 觉得有用可以给个star 谢谢啦
本文详细讲解 Claude Code 如何在有限的上下文窗口中管理 Token 预算,以及当空间不足时采用的各种压缩策略。每种策略都配有具体的代码示例和实际场景说明。
目录
- Token 预算基础知识
- 上下文窗口与阈值计算
- 微压缩 (Microcompact)
- 自动压缩 (Autocompact)
- 会话记忆压缩 (Session Memory Compact)
- 手动压缩与部分压缩
- 压缩后的恢复机制
- 实战案例
1. Token 预算基础知识
1.1 什么是 Token?
Token 是大语言模型处理文本的基本单位。你可以简单理解为:
1 token ≈ 4 个英文字符 1 token ≈ 1-2 个中文字符示例:
"Hello, world!" → 约 4 tokens "你好,世界!" → 约 5-6 tokens1.2 Token 估算方法
Claude Code 使用两种方式估算 Token 数量:
粗略估算(快速但不精确):
// src/services/tokenEstimation.ts function roughTokenCountEstimation(content: string, bytesPerToken = 4): number { return Math.round(content.length / bytesPerToken); } // JSON 文件使用更激进的比率(更多单字符 token) function bytesPerTokenForFileType(ext: string): number { return ext === 'json' ? 2 : 4; }精确计数(调用 API):
// 找到最后一个有 usage 信息的消息 // usage.input_tokens + usage.output_tokens = 实际使用量 // 然后加上后续消息的粗略估算 function tokenCountWithEstimation(messages: Message[]): number { const lastUsage = findLastUsage(messages); const messagesAfter = getMessagesAfter(lastUsage); return getTokenCountFromUsage(lastUsage) + roughEstimate(messagesAfter); }1.3 为什么需要预算管理?
Claude 的上下文窗口是有限的:
| 模型 | 标准窗口 | 扩展窗口 (1M) |
|---|---|---|
| Claude Sonnet 4 | 200K tokens | 1M tokens |
| Claude Opus 4 | 200K tokens | 1M tokens |
| Claude Haiku | 200K tokens | 不支持 |
当上下文超出窗口,API 会返回prompt_too_long错误。所以我们需要主动管理,在接近限制前压缩。
2. 上下文窗口与阈值计算
2.1 核心常量配置
// src/utils/context.ts const MODEL_CONTEXT_WINDOW_DEFAULT = 200_000; // 默认 200K const COMPACT_MAX_OUTPUT_TOKENS = 20_000; // 压缩时预留给摘要 const MAX_OUTPUT_TOKENS_DEFAULT = 32_000; // 默认输出上限 const MAX_OUTPUT_TOKENS_UPPER_LIMIT = 64_000; // 输出绝对上限 // src/services/compact/autoCompact.ts const AUTOCOMPACT_BUFFER_TOKENS = 13_000; // 自动压缩缓冲区 const WARNING_THRESHOLD_BUFFER_TOKENS = 20_000; // 警告阈值缓冲 const MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3; // 熔断阈值2.2 阈值计算公式
有效上下文窗口 = 上下文窗口 - 预留给摘要的空间 自动压缩阈值 = 有效上下文窗口 - 缓冲区 警告阈值 = 有效上下文窗口 - 警告缓冲区具体数值(以 200K 模型为例):
// 有效窗口 = 200,000 - 20,000 = 180,000 // 自动压缩阈值 = 180,000 - 13,000 = 167,000 // 警告阈值 = 180,000 - 20,000 = 160,000 function getAutoCompactThreshold(model: string): number { const contextWindow = getContextWindowSize(model); // 200,000 const reservedForSummary = getReservedTokensForSummary(); // 20,000 const effectiveWindow = contextWindow - reservedForSummary; // 180,000 return effectiveWindow - AUTOCOMPACT_BUFFER_TOKENS; // 167,000 }2.3 可视化理解
┌────────────────────────────────────────────────────────────────────────┐ │ 200K 上下文窗口 │ ├────────────────────────────────────────────────────────────────────────┤ │ │ │ 0K 80K 120K 160K 180K 200K │ │ │──────────│──────────│──────────│──────────│──────────│ │ │ │ │ │ │ │ │ │ │ │ 正常使用区域 │ ⚠️警告 │ 🚨危险区 │ ❌禁区 │ │ │ │ │ │ 区域 │ 自动压缩 │ 预留给 │ │ │ │ │ │ │ │ AI 输出 │ │ │ │ │ │ │ │ │ │ │ │ │ ▲ 阈值标记: │ │ 160K = 警告阈值 (开始提醒用户) │ │ 167K = 自动压缩阈值 (触发压缩) │ │ 180K = 有效窗口边界 │ │ 200K = 绝对上限 │ │ │ └────────────────────────────────────────────────────────────────────────┘3. 微压缩 (Microcompact)
微压缩是最轻量级的压缩策略,用户几乎无感知。
3.1 原理
不改变对话结构,只是清空旧的工具结果内容,保留工具调用的"骨架"。
3.2 可压缩的工具
// src/services/compact/microCompact.ts const COMPACTABLE_TOOLS = [ 'Read', // 文件读取 'Bash', // Shell 命令 'Grep', // 内容搜索 'Glob', // 文件搜索 'WebSearch', // 网页搜索 'WebFetch', // 网页获取 'Edit', // 文件编辑 'Write', // 文件写入 ];3.3 示例:时间触发微压缩
场景:你和 Claude 对话了 2 小时,中间去吃了午饭(空闲 1 小时)。
触发条件配置:
const TIME_BASED_MC_CONFIG = { enabled: true, gapThresholdMinutes: 60, // 空闲超过 60 分钟触发 keepRecent: 5, // 保留最近 5 个工具结果 };压缩前的对话:
┌─────────────────────────────────────────────────────────────────────┐ │ [11:00] User: 读取 config.json │ │ [11:00] Assistant: [调用 Read 工具] │ │ Tool Result: { │ │ "database": "postgresql", │ │ "host": "localhost", │ │ "port": 5432, │ │ ... (500 行配置) │ │ } │ │ │ │ [11:05] User: 读取 package.json │ │ [11:05] Assistant: [调用 Read 工具] │ │ Tool Result: { │ │ "name": "my-app", │ │ "dependencies": { ... }, │ │ ... (200 行) │ │ } │ │ │ │ [11:10] User: 运行测试 │ │ [11:10] Assistant: [调用 Bash 工具: npm test] │ │ Tool Result: │ │ PASS src/auth.test.ts │ │ PASS src/user.test.ts │ │ ... (100 行测试输出) │ │ │ │ ─────────── 午餐时间 (1小时空闲) ─────────── │ │ │ │ [12:30] User: 继续帮我看看 auth.ts │ └─────────────────────────────────────────────────────────────────────┘压缩后的对话:
┌─────────────────────────────────────────────────────────────────────┐ │ [11:00] User: 读取 config.json │ │ [11:00] Assistant: [调用 Read 工具] │ │ Tool Result: [Old tool result content cleared] ← 被清空 │ │ │ │ [11:05] User: 读取 package.json │ │ [11:05] Assistant: [调用 Read 工具] │ │ Tool Result: [Old tool result content cleared] ← 被清空 │ │ │ │ [11:10] User: 运行测试 │ │ [11:10] Assistant: [调用 Bash 工具: npm test] │ │ Tool Result: [Old tool result content cleared] ← 被清空 │ │ │ │ [12:30] User: 继续帮我看看 auth.ts │ │ [12:30] Assistant: [调用 Read 工具] │ │ Tool Result: (完整的 auth.ts 内容) ← 保留(最近的) │ └─────────────────────────────────────────────────────────────────────┘节省效果:
- 压缩前:~3000 tokens(假设)
- 压缩后:~200 tokens
- 节省:~2800 tokens (93%)
3.4 缓存微压缩
另一种微压缩利用 Claude API 的缓存编辑功能,可以删除工具结果而不使缓存前缀失效:
// 使用 cache_edits API 删除指定内容 // 好处:保持缓存有效,响应更快4. 自动压缩 (Autocompact)
当 Token 使用接近阈值时,自动触发完整的对话压缩。
4.1 触发条件
// src/services/compact/autoCompact.ts async function shouldAutoCompact( messages: Message[], model: string ): Promise<{ shouldCompact: boolean; tokenCount: number }> { // 条件 1:必须启用自动压缩 if (!isAutoCompactEnabled()) return { shouldCompact: false }; // 条件 2:检查熔断器(连续失败 3 次则跳过) if (consecutiveFailures >= MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES) { return { shouldCompact: false }; } // 条件 3:计算当前 Token 使用量 const tokenCount = await tokenCountWithEstimation(messages); const threshold = getAutoCompactThreshold(model); // 条件 4:超过阈值才压缩 return { shouldCompact: tokenCount > threshold, tokenCount }; }4.2 压缩流程
┌─────────────────────────────────────────────────────────────────────┐ │ 自动压缩流程 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ 1. 检测阈值 │ │ │ │ │ └──→ tokenCount > 167,000 ? │ │ │ │ │ ├─ No → 继续正常对话 │ │ │ │ │ └─ Yes → 进入压缩流程 │ │ │ │ │ 2. 执行 PreCompact Hooks │ │ │ │ │ └──→ 用户自定义的压缩前钩子 │ │ │ │ │ 3. 优先尝试会话记忆压缩 │ │ │ │ │ └──→ trySessionMemoryCompaction() │ │ │ │ │ ├─ 成功 → 使用会话记忆生成摘要 │ │ │ │ │ └─ 失败 → 回退到传统压缩 │ │ │ │ │ 4. 传统压缩 (compactConversation) │ │ │ │ │ ├──→ 替换图片/文档为标记 │ │ ├──→ 调用 Claude 生成对话摘要 │ │ └──→ 创建压缩边界消息 │ │ │ │ │ 5. 恢复关键内容 │ │ │ │ │ ├──→ 恢复最近读取的文件(最多 5 个) │ │ ├──→ 恢复已调用的技能 │ │ └──→ 恢复 MCP 指令 │ │ │ │ │ 6. 执行 PostCompact Hooks │ │ │ │ │ └──→ 用户自定义的压缩后钩子 │ │ │ │ │ 7. 清理缓存 │ │ │ │ │ └──→ 重置各种状态缓存 │ │ │ └─────────────────────────────────────────────────────────────────────┘4.3 示例:完整的自动压缩过程
压缩前的对话(假设 170K tokens):
┌─────────────────────────────────────────────────────────────────────┐ │ [消息 1-50] 讨论项目架构,阅读了 20 个文件 │ │ [消息 51-100] 实现登录功能,多次编辑 auth.ts │ │ [消息 101-150] 调试 API 问题,运行了很多测试 │ │ [消息 151-180] 重构用户模块,修改了 5 个文件 │ │ │ │ 📊 Token 统计: │ │ - 对话文本:~30K tokens │ │ - 文件内容:~100K tokens │ │ - 命令输出:~40K tokens │ │ - 总计:~170K tokens ← 超过 167K 阈值! │ └─────────────────────────────────────────────────────────────────────┘压缩后的对话(约 60K tokens):
┌─────────────────────────────────────────────────────────────────────┐ │ [系统消息] ═══════ COMPACTION BOUNDARY ═══════ │ │ Pre-compact tokens: 170,000 │ │ Messages summarized: 180 │ │ │ │ [摘要消息] │ │ ## 会话摘要 │ │ │ │ ### 项目概述 │ │ 这是一个 React + TypeScript 的电商后台项目。 │ │ │ │ ### 已完成的工作 │ │ 1. 讨论并确定了项目架构(微服务 + API Gateway) │ │ 2. 实现了用户认证功能: │ │ - JWT token 生成和验证 │ │ - 登录/登出 API │ │ - 密码加密存储 │ │ 3. 调试并修复了 3 个 API bug │ │ 4. 重构了用户模块,提取了公共组件 │ │ │ │ ### 关键决策 │ │ - 使用 bcrypt 而非 MD5 进行密码哈希 │ │ - API 响应统一使用 { code, data, message } 格式 │ │ - 用户角色使用 RBAC 模型 │ │ │ │ ### 待处理问题 │ │ - 需要添加刷新 token 机制 │ │ - 用户列表分页性能待优化 │ │ │ │ [附件:最近读取的文件] │ │ - src/auth/auth.service.ts (完整内容) │ │ - src/users/user.controller.ts (完整内容) │ │ - src/config/database.ts (完整内容) │ │ │ │ 📊 压缩后 Token 统计: │ │ - 摘要:~5K tokens │ │ - 恢复的文件:~15K tokens │ │ - 预留空间:~40K tokens │ │ - 总计:~60K tokens ← 大量空间可用! │ └─────────────────────────────────────────────────────────────────────┘4.4 压缩时保留什么、丢弃什么
| 保留 | 丢弃 |
|---|---|
| 对话摘要(关键决策、完成的工作) | 原始对话历史 |
| 最近读取的 5 个文件 | 旧的文件内容 |
| 已调用的技能内容 | 详细的工具输出 |
| 计划文件 | 图片/文档(替换为标记) |
| MCP 指令 | 中间的调试信息 |
| SessionStart hooks 结果 | 重复的错误信息 |
5. 会话记忆压缩 (Session Memory Compact)
这是一种更智能的压缩方式,优先于传统压缩执行。
5.1 原理
利用 Claude Code 的 Memory 系统,将对话中的关键决策和学习提取出来保存,然后只保留最近的对话。
5.2 配置参数
// src/services/compact/sessionMemoryCompact.ts const DEFAULT_SM_COMPACT_CONFIG = { minTokens: 10_000, // 至少保留 10K tokens minTextBlockMessages: 5, // 至少保留 5 条文本消息 maxTokens: 40_000, // 最多保留 40K tokens(硬上限) };5.3 保留消息的计算逻辑
function calculateMessagesToKeepIndex( messages: Message[], config: SMCompactConfig ): number { let tokenCount = 0; let textMessageCount = 0; // 从最新消息向前遍历 for (let i = messages.length - 1; i >= 0; i--) { const msg = messages[i]; tokenCount += estimateTokens(msg); if (hasTextContent(msg)) { textMessageCount++; } // 满足最小要求后继续,直到达到硬上限 if (tokenCount >= config.minTokens && textMessageCount >= config.minTextBlockMessages) { // 继续添加,直到达到 maxTokens if (tokenCount > config.maxTokens) { return i + 1; // 返回这个索引之后的消息 } } } return 0; // 保留所有消息 }5.4 示例:会话记忆压缩 vs 传统压缩
场景:200 条消息的对话,约 170K tokens
传统压缩结果:
[摘要] 整个对话被压缩成一段摘要 [附件] 恢复 5 个最近的文件 → 丢失了对话的"流动感",AI 只知道结论会话记忆压缩结果:
[Memory 文件] 保存了关键决策到 ~/.claude/memory/ - 项目使用 PostgreSQL 而非 MySQL(原因:需要 JSON 支持) - API 使用 REST 而非 GraphQL(原因:团队更熟悉) [保留的消息 151-200] 最近 50 条对话完整保留 → 关键决策持久化,最近对话完整,体验更好6. 手动压缩与部分压缩
6.1 手动压缩命令
# 在 Claude Code 中执行 /compact用户可以:
- 选择压缩的范围
- 添加自定义反馈(告诉 AI 哪些信息重要)
- 选择压缩方向
6.2 部分压缩的两种方向
from方向(保留前面,压缩后面):
┌─────────────────────────────────────────────────────────────────────┐ │ [消息 1-50] ← 保留(缓存有效) │ │ ─────────── 选择的消息 ─────────── │ │ [消息 51-100] ← 被压缩成摘要 │ └─────────────────────────────────────────────────────────────────────┘ 优点:保持 API 缓存有效,响应更快up_to方向(压缩前面,保留后面):
┌─────────────────────────────────────────────────────────────────────┐ │ [消息 1-50] ← 被压缩成摘要 │ │ ─────────── 选择的消息 ─────────── │ │ [消息 51-100] ← 保留(完整内容) │ └─────────────────────────────────────────────────────────────────────┘ 优点:保留最近的完整上下文 缺点:缓存失效,需要重新计算6.3 部分压缩代码逻辑
// src/services/compact/compact.ts async function partialCompact( messages: Message[], pivotMessage: Message, direction: 'from' | 'up_to' ): Promise<CompactionResult> { const pivotIndex = messages.findIndex(m => m.uuid === pivotMessage.uuid); if (direction === 'from') { // 压缩 pivot 之后的消息 const toSummarize = messages.slice(pivotIndex + 1); const toKeep = messages.slice(0, pivotIndex + 1); // ... } else { // 压缩 pivot 之前的消息 const toSummarize = messages.slice(0, pivotIndex); const toKeep = messages.slice(pivotIndex) .filter(m => !isCompactBoundaryMessage(m)); // 过滤旧边界 // ... } }7. 压缩后的恢复机制
7.1 恢复配置
// src/services/compact/compact.ts const POST_COMPACT_CONFIG = { MAX_FILES_TO_RESTORE: 5, // 最多恢复 5 个文件 TOKEN_BUDGET: 50_000, // 总恢复预算 MAX_TOKENS_PER_FILE: 5_000, // 每个文件最多 5K tokens MAX_TOKENS_PER_SKILL: 5_000, // 每个技能最多 5K tokens SKILLS_TOKEN_BUDGET: 25_000, // 技能总预算 };7.2 恢复的内容类型
// 按优先级恢复 const attachmentsToRestore = [ // 1. 计划文件(如果在 plan mode) createPlanFileAttachment(planPath), // 2. 计划模式说明 createPlanModeInstructions(), // 3. 最近读取的文件(按时间排序) ...getRecentlyReadFiles() .filter(f => !isPlanFile(f) && !isClaudeMd(f)) .slice(0, 5) .map(f => createFileAttachment(f)), // 4. 已调用的技能 ...getInvokedSkills() .map(s => createSkillAttachment(s)), // 5. 延迟工具定义(重新发布) createDeferredToolsDelta(), // 6. MCP 指令(重新发布) createMcpInstructionsDelta(), ];7.3 恢复示例
┌─────────────────────────────────────────────────────────────────────┐ │ [压缩边界消息] │ │ │ │ [摘要消息] ...对话摘要... │ │ │ │ [附件消息 1] 📎 File: src/auth/auth.service.ts │ │ (最近 5 分钟前读取) │ │ ```typescript │ │ export class AuthService { │ │ // ... 完整内容 ... │ │ } │ │ ``` │ │ │ │ [附件消息 2] 📎 File: src/users/user.entity.ts │ │ (最近 10 分钟前读取) │ │ ```typescript │ │ @Entity() │ │ export class User { │ │ // ... 完整内容 ... │ │ } │ │ ``` │ │ │ │ [附件消息 3] 📎 Skill: commit │ │ (已调用的技能定义) │ │ │ │ [附件消息 4] 📎 MCP Instructions Update │ │ (重新发布 MCP 服务器指令) │ │ │ │ 现在你可以继续对话,AI 仍然知道关键文件的内容 │ └─────────────────────────────────────────────────────────────────────┘7.4 压缩后清理
// src/services/compact/postCompactCleanup.ts function postCompactCleanup(): void { // 重置微压缩状态 resetMicrocompactState(); // 清理用户上下文缓存 getUserContext.cache.clear(); // 重置 Memory 文件缓存 resetGetMemoryFilesCache('compact'); // 清理系统提示段缓存 clearSystemPromptSections(); // 清理安全分类器审批记录 clearClassifierApprovals(); // 清理推测执行检查 clearSpeculativeChecks(); // 清理会话消息缓存 clearSessionMessagesCache(); }8. 实战案例
案例 1:日常开发中的自动压缩
场景描述:
小明正在用 Claude Code 开发一个 Node.js 后端项目,已经对话了 3 小时。
对话演进:
[小时 1] 讨论架构,阅读了 15 个文件 Token 使用:~50K [小时 2] 实现 CRUD API,运行测试 Token 使用:~120K [小时 3] 调试问题,查看日志 Token 使用:~165K ⚠️ 接近阈值 (167K) [继续对话] 小明: 帮我看看为什么用户列表 API 返回 500 Token 使用:~170K 🚨 触发自动压缩!压缩过程(用户视角):
Claude: 检测到上下文接近限制,正在压缩对话... [===========================] 压缩中... ✅ 压缩完成! - 压缩前:170,234 tokens - 压缩后:58,102 tokens - 保留了 5 个最近的文件 - 关键决策已保存到会话记忆 现在让我来看看用户列表 API 的问题...小明的体验:
- 几乎无感知,对话继续
- AI 仍然记得项目架构和之前的决策
- 最近编辑的文件仍然可用
案例 2:长时间空闲后的微压缩
场景描述:
小红在上午用 Claude Code 写了一些代码,然后去开会(2 小时),回来继续。
时间线:
10:00 - 读取 config.json, utils.ts, index.ts 10:30 - 运行测试(大量输出) 10:45 - 最后一条消息 ────── 开会 2 小时 ────── 13:00 - 回来继续对话微压缩触发:
// 检测空闲时间 gapMinutes = (13:00 - 10:45) = 135 分钟 threshold = 60 分钟 // 135 > 60,触发时间触发微压缩 // 清理旧的工具结果,保留最近 5 个效果:
压缩前: config.json 内容 (500 行) → 约 2000 tokens utils.ts 内容 (300 行) → 约 1200 tokens index.ts 内容 (200 行) → 约 800 tokens 测试输出 (1000 行) → 约 4000 tokens 总计:~8000 tokens 压缩后: config.json: [Old tool result content cleared] → ~10 tokens utils.ts: [Old tool result content cleared] → ~10 tokens index.ts: [Old tool result content cleared] → ~10 tokens 测试输出: [Old tool result content cleared] → ~10 tokens 总计:~40 tokens 节省:~7960 tokens (99.5%)案例 3:手动选择性压缩
场景描述:
小李在讨论两个不相关的功能,想只保留最近的讨论。
对话结构:
[消息 1-50] 功能 A:支付系统(已完成) [消息 51-80] 功能 B:用户通知(进行中)手动压缩操作:
小李: /compact Claude: 请选择压缩方式: 1. 压缩全部对话 2. 选择性压缩 小李: 2 Claude: 请选择要保留的起始消息: (显示消息列表) 小李: (选择消息 51) Claude: 选择压缩方向: 1. from - 保留消息 51 之前,压缩之后 2. up_to - 压缩消息 51 之前,保留之后 小李: 2 (up_to) Claude: 正在压缩消息 1-50... ✅ 压缩完成! - 功能 A 的讨论已压缩为摘要 - 功能 B 的讨论完整保留压缩后结构:
[摘要] 功能 A(支付系统)已完成,关键点: - 使用 Stripe API - 支持信用卡和支付宝 - 添加了退款功能 [消息 51-80] 功能 B 的完整对话(保留)总结
Claude Code 的压缩策略是一个分层防御系统:
| 层级 | 策略 | 触发条件 | 影响程度 |
|---|---|---|---|
| 1 | 微压缩 | 空闲超时 / 工具结果累积 | 最小 |
| 2 | 会话记忆压缩 | Token 接近阈值 | 中等 |
| 3 | 自动压缩 | Token 超过阈值 | 较大 |
| 4 | 手动压缩 | 用户主动触发 | 可控 |
最佳实践:
- 监控 Token 使用:使用
/cost命令查看当前使用量 - 主动压缩:在重要讨论前主动
/compact,确保有足够空间 - 写好 CLAUDE.md:关键信息写入 CLAUDE.md,压缩后仍然可用
- 利用 Memory:重要决策保存到 Memory,跨会话可用
参考文件
| 文件 | 功能 |
|---|---|
src/services/compact/autoCompact.ts | 自动压缩入口 |
src/services/compact/microCompact.ts | 微压缩实现 |
src/services/compact/compact.ts | 核心压缩逻辑 |
src/services/compact/sessionMemoryCompact.ts | 会话记忆压缩 |
src/utils/context.ts | 上下文窗口配置 |
src/services/tokenEstimation.ts | Token 估算 |