mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-17 05:45:51 +00:00
聚焦 token 管理的设计考量:两级计数策略、分层压缩(截断→微压缩→自动压缩)、 缓存感知的压缩设计和 slot 优化设计哲学。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
129 lines
6.5 KiB
Plaintext
129 lines
6.5 KiB
Plaintext
---
|
||
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 消耗
|