深入理解 Claude Code 的 Token 预算与压缩策略
开源版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 tokens
1.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 估算 |
更多推荐




所有评论(0)