Claude Code 的 Token 到底花在哪?看懂 /usage、上下文、缓存与额度
有一次,我在用 Claude Code 处理一个中等规模的重构任务,任务进行到一半,能感觉到会话明显"变重"了——响应变慢,Assistant 开始在消息里提示上下文接近上限。我打开 /usage,看到一串数字:total cost、API duration、wall duration、input tokens、output tokens、cache read、cache write、code changes……
每个数字都有意义,但它们回答的不是同一个问题。
这篇文章就从这里开始:/usage 展示的那些数字,各自在衡量什么,又各自不代表什么。 把这件事搞清楚,才能真正追踪 Token 都花在哪里,并在保留任务质量的前提下做出有效的优化。
一、先把四个视角分开
在读任何具体数字之前,要先建立一个基本的分类意识。在 Claude Code 里,以下四件事经常被混淆,但它们是彼此独立的:
| 视角 | 衡量什么 | 在哪里体现 |
|---|---|---|
| 已消耗 Token | 本次会话里真实发生的、已经计费的 Token 量 | /usage 的 input/output/cache 字段 |
| 当前活动上下文 | 当前请求中模型可使用的内容 | /usage 不直接显示,需要结合上下文状态判断 |
| API 美元估算 | 基于消耗量换算出的 API 计费金额 | /usage 的 total cost 字段 |
| 订阅额度 | 订阅计划的使用容量 | 账户侧信息;本文未测试相关命令或后台 |
这四个视角互相关联,但任何一个都不能代替另外三个。
最常见的误解是把"API 成本估算"当作"订阅额度消耗"来理解。两者的计算逻辑完全不同:API 成本是按 Token 乘以单价估算的;订阅额度是 Anthropic 针对订阅计划独立设计的配额,其折算方式不完全透明,也不一定和 API 价格成线性关系。
如果你用 Claude Code 的 OAuth 接入方式(即通过 claude.ai 账号使用),那么你在 /usage 里看到的 cost 数字是一个估算参考,不一定等于你的订阅在扣多少额度。
二、/usage 各字段拆解
我在本地用 Claude Code 2.1.208 运行了 /usage,以下是字段的含义,私人数值不记录。
total cost
这是本次会话所有模型调用加起来的估算 API 美元成本。注意关键词:估算、本次会话。
- 它反映的是累积的计费行为,不是你在订阅里还剩多少。
- 它是会话维度的,不是账户或项目维度的。
- 如果你 compact 了上下文,total cost 不会清零——已经发生的消耗已经发生了。
API duration vs. wall duration
- API duration:模型实际处理请求花费的时间。
- wall duration:整个会话从开始到现在的实际挂钟时间,包括你在键盘上发呆的时间、工具执行的时间、等待文件读取的时间。
wall duration 远大于 API duration 是正常的。如果两者差距很小,说明会话几乎全部时间都在等模型,本地工具或人工干预非常少。
input tokens
每次调用模型时发送的 Token 数量的累计。这是理解"Token 花在哪里"最重要的数字,因为输入端往往是成本的大头,也是最有优化空间的地方。
input tokens 包括:
- 你发的每一条消息
- 系统提示(CLAUDE.md、Skill、Plugin 注入的内容)
- 工具定义(所有注册工具的 schema)
- 工具调用结果(Bash 输出、文件读取内容、MCP 返回值)
- Subagent 的回传内容
- 当前请求仍然保留的对话内容
保留在活动上下文中的内容会影响后续请求;compaction、缓存和上下文管理都会改变实际携带的内容,不能按“每轮完整重发”做简单推算。
output tokens
模型生成的 Token 数量的累计。一般来说,output 的单价高于 input,但量级通常比 input 小很多。如果你的 output tokens 异常高,可能是模型在生成非常长的代码块或解释,或者触发了某种循环生成。
cache read tokens
这是从 Prompt Cache 中读取的 Token 数量,不是普通输入。
Anthropic 的 Prompt Cache 机制会缓存对话前段不变的内容(比如系统提示、长文档),后续调用命中缓存时,这部分不需要重新处理,按 cache read 价格计费(比 input 便宜)。
cache read 表示发生了缓存复用。应按官方字段定义分别解读 input、cache read 和 cache write,不自行设计加总公式来推导账单。
cache write tokens
这是写入缓存、供后续可能复用的 Token 数量。具体价格、保留时间和最终收益取决于当前官方规则与后续是否命中。
code changes
显示本次会话里对代码文件做出的增删行数统计。这是一个工作产出指标,和 Token 消耗没有直接的线性关系。有时候写了很多 Token 来分析问题,最终改动很少;有时候一次大规模重构会让 code changes 数字很高而对话很短。
这个数字代表什么 / 不代表什么
| 字段 | 代表什么 | 不代表什么 |
|---|---|---|
| total cost | 本会话 API 调用的美元估算 | 你的订阅扣了多少额度 |
| input tokens | 每次调用累计发送的 Token 总量 | 当前上下文窗口大小 |
| cache read | 发生缓存复用的内容量 | 可脱离官方定义自行加总的账单数字 |
| cache write | 写入缓存、供后续复用的内容量 | 必然带来节省 |
| output tokens | 模型生成内容的累计量 | 一定能控制的成本——任务需要多长就多长 |
| wall duration | 会话总挂钟时长 | API 实际计算时间 |
| code changes | 代码行级变动统计 | 工作价值或 Token 效率的直接度量 |
| /usage 整体 | 已发生的会话记账视图 | 剩余额度、账户余额、组织级用量 |
三、上下文:活动的,而非累计的
理解 input tokens 之后,还要单独理解"活动上下文"的概念,因为它和消耗量是两回事。
活动上下文是当前请求中模型可使用的内容,可能包括保留的对话、当前消息、系统指令、工具定义和工具结果。
Claude Code 里的 Token Counter(如果开启)会在发送前估算这个数字。这是一个前向视图——"这次请求有多重"。
/usage 里的 input tokens 是一个后向视图——"这个会话历史上发送过多少"。
compaction 改变的是上下文,不是消耗
当上下文接近上限,Claude Code 会触发 compaction(压缩),把对话历史总结成更短的摘要,降低活动上下文的大小。
这是一个很重要的认知:compaction 之后,活动上下文变小了,但 /usage 里的消耗数字不会减少。之前调用发生的 Token 消耗已经记录在案,它们不会因为摘要操作而消失。
compaction 的作用是让会话得以继续,而不是让已发生的成本"撤销"。
四、Token 增长的来源追踪
知道了视角和字段之后,真正的诊断工作来了:当你发现 input tokens 增长很快,到底是什么在推高它?
以下是主要的贡献来源,按照可观察性和可控程度排列:
1. 对话历史本身
每次模型调用,完整的对话历史都要重发。会话越长,每一轮的基础成本越高。这是输入成本增长最根本的来源。
追踪方法:在会话早期和晚期各记录一次 input tokens,差值的增长速率就反映了历史积累的速度。
2. 文件读取内容
当你让 Claude Code 读取文件(无论是直接 cat、通过 @file 引用,还是通过工具读取),文件的完整内容会进入上下文,并在后续每次调用中继续存在。
一个大文件被读取后,相关内容可能增加活动上下文;具体保留多少、保留多久取决于 Claude Code 的上下文管理。
追踪方法:关注你引入了哪些大文件,尤其是日志文件、构建输出、完整的依赖树。问自己:模型真的需要读这整个文件吗?
3. 工具和命令的输出
Bash 命令、文件系统操作、测试运行——这些工具的输出内容会作为工具结果进入上下文。一次 npm test 失败并输出了 200 行的错误堆栈,200 行就进了上下文。
这是最容易被忽视的增长来源。用户往往盯着对话内容,却忽略了工具输出的体积。
追踪方法:留意运行了哪些会产生大量输出的命令。测试、构建、lint、grep 大目录——都有可能。
4. MCP Tool 定义与返回结果
如果你启用了 MCP(Model Context Protocol)服务器,Tool 定义会占用上下文,执行后的返回结果也可能进入后续请求。工具越多、schema 越长,潜在的基础开销越大。
追踪方法:检查你启用了多少个 MCP 服务器,每个服务器注册了多少工具。如果有不常用的 MCP,考虑按需启用。
5. Skill 和 Plugin 注入
CLAUDE.md、Skill 和 Plugin 可能向会话加入指令、Tool 定义或返回内容。这些内容会影响上下文,但加载方式取决于功能和版本。
追踪方法:检查你的 CLAUDE.md 有多长,启用了多少 Skill 和 Plugin。去掉不需要的,压缩指令文本。
6. Subagent 回传
当 Claude Code 启动 Subagent(子代理)执行子任务时,Subagent 完成任务后的回传内容会返回给主 Agent,并进入主会话的上下文。如果 Subagent 完成的是一个大规模分析任务,它可能回传几千 Token 的结果。
追踪方法:关注 Subagent 任务的规模和回传内容的体积。如果 Subagent 只需要给出一个结论,告诉它只返回摘要而非完整过程。
五、有边界的对比诊断法
知道来源之后,还需要一套方法来实际衡量不同因素的贡献。我用的方法很简单,但有边界:
原则:干净基线 → 一次只改变一个因素 → 执行相近任务 → 比较结果。
不承诺:这个方法能帮你定性判断哪个因素影响大,但不能给出精确的归因——因为模型调用次数、缓存命中率、任务本身的复杂度都会有波动,你观察到的差值是这些因素的复合结果。
实操步骤
第一步:建立干净基线
新开一个 Session,什么也不做,只运行 /usage,记录初始数字(通常是零或接近零)。这是你的基线。
第二步:只引入一个变量
例如,想测试"启用某个 MCP 服务器对 input tokens 的影响":
- 会话 A:启用该 MCP,执行一个简单任务(比如问一个代码问题),记录
/usage。 - 会话 B:禁用该 MCP,执行相近的任务,记录
/usage。 - 比较两次 input tokens 的差值。
第三步:相近任务
任务要尽量相近,但不需要完全一致——要的是量级感,不是精确数字。如果 A 任务和 B 任务的复杂度差异很大,比较没有意义。
第四步:重复观察
如果结论很重要,多跑几次,观察差值是否稳定。如果差值波动很大,说明还有你没控制到的变量。
有边界的声明:这个方法适合帮你判断"A 因素大概比 B 因素贡献多 2 倍的 input tokens",不适合拿来做精确的财务估算或 SLA 声明。
六、在保留任务质量的前提下优化
优化的前提是:不降低任务质量。如果为了省 Token 让模型工作不完整、漏掉关键信息、或者频繁出错,那节省下来的成本会被返工的成本抵消。
以下是几类有效的优化方向:
缩小文件读取范围
不需要读整个文件时,只读你需要的部分。与其 cat large_file.py,不如 sed -n '100,200p' large_file.py 只读关键段落。让模型先通过结构性工具(如 grep、find)定位,再精确读取。
压缩工具输出的噪声
测试失败时,你真正需要的往往是第一个错误和堆栈,不是所有 200 行。可以在 Bash 命令里加 | head -50 或 2>&1 | tail -30 来截断输出。日志文件的读取尤其要注意这一点。
减少重复的 Tool 定义
如果你启用了很多 MCP 服务器,每个 Tool 的 schema 都是固定成本。审查一下哪些工具你实际上在用,哪些只是"备着的"。不常用的 MCP 服务器可以按需启用,而不是常态启用。
控制 Subagent 的回传体积
在 Subagent 的任务描述里明确说明"只返回最终结论和关键发现,不需要过程日志"。如果 Subagent 的任务是分析一批文件,让它返回摘要表格,而不是每个文件的完整分析。
在任务边界 compact 或新开 Session
这是最直接有效的上下文控制手段。当一个子任务完成,你准备开始下一个不相关的子任务时,这是 compact 的好时机。如果新任务和当前上下文几乎没有关联,直接新开 Session 比 compact 更干净。
记住:compact 不会让已发生的成本消失,但它能阻止上下文继续膨胀,防止后续每轮调用的边际成本持续升高。
精简 CLAUDE.md 和 Skill 内容
审查你的 CLAUDE.md,去掉过时规则、重复说明和不再适用的约束。保持仓库指令精炼,可以减少不必要的上下文负担。
七、我没有测试的边界
为了诚实,以下内容在我的验证范围之外,我不对它们的具体行为做声明:
/usage-credits:本文没有验证它当前是否可用,也没有验证其输出含义。- 组织级 Analytics:Anthropic 为 Team 和 Enterprise 计划提供的组织级用量仪表盘,我没有访问过。
- OpenTelemetry 集成:Claude Code 支持 OTEL 导出,可以把调用数据发送到外部监控系统,我未配置测试。
- statusline:本文没有配置或测试相关状态栏显示。
- 长项目跨 Session 归因:如果你想统计一个为期两周的功能开发总共消耗了多少 Token,需要跨多个 Session 手动汇总或借助外部工具,这不是
/usage能直接告诉你的。
如果你用的是这些功能,请以你自己的实际观察为准,不要依赖本文的推断。
八、完整诊断流程
当你发现会话"变重",或者对 Token 消耗有疑问,可以按照以下流程逐步排查:
Step 1:打开 /usage,记录当前数字
→ 重点看 input tokens 的绝对值和增长速率
→ 区分 input / cache read / cache write,不要混淆
Step 2:回顾本次会话引入了什么
→ 读了哪些大文件?有多大?
→ 运行了哪些产生大量输出的命令?
→ 启动了哪些 Subagent?回传了多少内容?
Step 3:检查固定开销
→ CLAUDE.md 有多长?
→ 启用了多少 MCP 服务器和工具?
→ 启用了哪些 Skill 和 Plugin?
Step 4:判断当前是否处于任务边界
→ 当前子任务是否已经完成?
→ 下一个任务是否依赖当前上下文?
→ 如果不依赖:考虑 compact 或新开 Session
Step 5:如果要做对比实验
→ 新开干净 Session 作为基线
→ 只改变一个变量
→ 执行相近任务,比较 /usage 差值
→ 重复观察,确认结论稳定
Step 6:优化
→ 缩小文件读取范围
→ 截断工具输出
→ 精简 CLAUDE.md 和 Skill
→ 按需启用 MCP
→ 控制 Subagent 回传体积
→ 在任务边界 compact 或新建 Session
尾声
/usage 是一扇窗,看到的是会话里已经发生的记账记录。它不是账户余额,不是剩余额度,也不是当前上下文大小——这些是四件不同的事。
把它们分清楚,是有效管理 Token 的第一步。
驱动 input tokens 增长的,可能不只是对话框里的文字,还包括没截断的工具输出、大文件内容、MCP schema 和 Subagent 回传。这些都是值得检查的来源。
优化不是为了省每一分钱,而是让会话保持在可控范围,并在合适的任务边界主动 compact 或新开 Session。