--- title: "令牌预算" description: "200K 上下文窗口不是全部。理解 Claude Code 如何管理 token 预算:动态计算、近似 vs 精确计数、分层压缩策略和缓存优化。" keywords: ["Token 预算", "上下文窗口", "token 计算", "压缩策略"] --- ## 核心约束:200K 不是全部 Claude 的上下文窗口为 200K tokens(部分模型支持 1M),但实际可用于对话的空间远小于此: ``` 上下文窗口(200K) ├── 系统提示词(~15-25K) ├── 工具定义(~10-20K,含 MCP 工具) ├── 用户上下文(CLAUDE.md、git status 等) ├── 输出预留(AI 响应的空间) └── 剩余:对话历史空间(随对话增长而缩小) ``` **设计挑战**:对话历史不断增长,可用空间持续缩小。系统必须在"保留足够的上下文让 AI 理解对话"和"不超出 token 限制"之间持续平衡。 ### 上下文窗口的动态解析 上下文窗口大小不是硬编码的。系统按优先级从多个来源解析: 1. 用户环境变量覆盖(强制指定) 2. 模型名后缀标记(如 `[1m]` 表示 1M 窗口) 3. 模型自身的能力声明 4. 特定 beta 功能的支持情况 5. 兜底值:200K 这种分层解析意味着同一个系统可以适配不同模型和不同配置,而不需要为每种情况写特殊逻辑。 ## Token 计数:近似 vs 精确 Token 计数是所有预算决策的基础。系统采用两级策略: ### 近似估算(毫秒级) 基于一个简单的经验公式:大约每 4 个字节 ≈ 1 个 token。对不同内容类型有调整: - **JSON/JSONL**:更密集,每 2 字节 ≈ 1 token - **图片/文档**:固定估算值(基于尺寸上限的保守估计) - **普通文本**:每 4 字节 ≈ 1 token ### 精确计数(需要 API 调用) 使用 Anthropic 的 token 计数端点。不同 Provider 的支持程度不同: | Provider 类别 | 精确计数支持 | 注意事项 | |---------------|-------------|----------| | Anthropic 直连 | 原生支持 | 最准确 | | 云平台(Bedrock/Vertex) | 各自的 SDK 接口 | 需要额外依赖 | | 第三方兼容(OpenAI/Gemini/Grok) | 不支持 | 退回近似估算 | ### 为什么需要两级策略 近似估算用于**热路径**——每轮 agentic loop 都需要判断"是否需要压缩",这个检查必须足够快。精确计数用于**关键决策点**——压缩前后对比、费用计算等需要准确数字的场景。 **设计权衡**:近似估算可能偏差 10-20%,这意味着自动压缩的触发时机可能略有提前或延后。但这个偏差是可以接受的——提前压缩只多花一点 token,延后压缩最多触发一次 API 错误然后紧急压缩。 ## 分层压缩策略 系统不是等到"满了才压缩",而是采用了由轻到重的分层策略: ### 第一层:工具结果截断 单个工具的输出有硬性上限(通常 100K 字符)。超长的命令输出、文件内容在写入消息前就被截断。 这是最轻量的"压缩"——只是防止单个工具结果占用过多空间。 ### 第二层:微压缩(Micro-Compact) 在触发全量压缩之前,系统先尝试只压缩旧的工具调用结果: - 超过一定时间的工具结果被替换为简短占位符 - 图片/文档结果替换为 `[image]` / `[document]` 文本 - 每次替换释放 token,可能推迟全量压缩的触发 **设计考量**:为什么不直接全量压缩?因为全量压缩需要调用 AI 生成摘要,成本高且耗时。微压缩是确定性操作(简单替换),几乎零成本,可以频繁执行。 ### 第三层:自动压缩(Auto-Compact) 当对话接近 token 上限时,系统用 AI 自身来总结之前的对话: 1. **剥离非必要内容**:图片、文档附件被替换为文本标记 2. **生成摘要**:通过一个独立的 agent 调用生成对话摘要 3. **重建上下文**:用摘要替代原始对话,同时重新注入关键信息(最近操作的文件、活跃的计划等) **设计考量**:压缩后会重新读取最近操作的 5 个文件。这是因为在实际使用中,AI 最可能需要的就是刚刚操作过的文件——重新读取它们比让 AI 再次搜索更高效。 ### 压缩的安全阀 - **连续失败上限**:连续 3 次压缩失败后停止尝试(断路器模式) - **压缩来源的查询不触发压缩**:防止压缩本身触发无限递归 - **手动压缩**:用户可以随时通过 `/compact` 主动触发 ## 缓存感知的压缩设计 压缩操作本身需要调用 API 生成摘要——这是整个会话中最昂贵的操作之一。系统通过**复用主线程的缓存前缀**来优化: 系统 prompt + 工具定义 + 上下文消息通常不变,这部分可以通过 API 的 prompt cache 机制缓存。压缩时的摘要请求复用了这些缓存,使得缓存命中率从接近 0% 提升到接近 100%。 **设计洞察**:这不是一个独立的优化——它是整个缓存策略的一部分。系统 prompt 的组装策略("不变内容在前")和压缩时的缓存复用,都是为了最大化 prompt cache 的命中率。 ## 输出 Token 的 Slot 优化 一个容易被忽视但影响深远的优化:AI 输出的 token 上限默认只设为 8K,而不是模型支持的最大值(32K 或 64K)。 **为什么**?因为 API 服务端按 `max_tokens` 参数预留推理容量(slot)。99% 的请求实际输出不到 5K tokens,但如果所有请求都预留 32K 的 slot,会导致严重的容量浪费。 降到 8K 后,不到 1% 的请求会被截断——这些请求自动获得一次高上限的干净重试。 **设计哲学**:用 1% 的请求多一次重试的代价,换取 99% 请求的更快响应。这是一个典型的"优化常见路径,用重试处理边缘情况"的设计模式。 ## 选择性压缩 除了全量压缩,用户还可以选择只压缩对话的某一部分: - **压缩早期内容**(保留最近对话):适用于"开头说了很多背景,但后面只需要关注最近操作"的场景 - **压缩近期内容**(保留早期对话):适用于"早期的架构决策很重要,但最近的工具调用结果不需要"的场景 两种方向对缓存有不同的影响——保留前缀(早期内容)可以维持 prompt cache,修改前缀则破坏缓存。 ## 接下来 - **上下文压缩** — 深入了解压缩的触发条件和摘要生成机制 - **项目记忆** — 理解跨会话的记忆持久化设计 - **穷鬼模式** — 了解如何减少 token 消耗