深入理解 Claude Code 的 Token 预算与压缩策略

张开发
2026/4/15 9:28:51 15 分钟阅读

分享文章

深入理解 Claude Code 的 Token 预算与压缩策略
开源版Claude Code可运行源码 觉得有用可以给个star 谢谢啦本文详细讲解 Claude Code 如何在有限的上下文窗口中管理 Token 预算以及当空间不足时采用的各种压缩策略。每种策略都配有具体的代码示例和实际场景说明。目录Token 预算基础知识上下文窗口与阈值计算微压缩 (Microcompact)自动压缩 (Autocompact)会话记忆压缩 (Session Memory Compact)手动压缩与部分压缩压缩后的恢复机制实战案例1. Token 预算基础知识1.1 什么是 TokenToken 是大语言模型处理文本的基本单位。你可以简单理解为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 4200K tokens1M tokensClaude Opus 4200K tokens1M tokensClaude Haiku200K 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 ): PromiseCompactionResult { 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.tsToken 估算

更多文章