今年不少开发者第一次用 Claude Code 跑真实项目时,都会遇到同一个困惑:功能确实做出来了,但月底看账单,Token 消耗量比自己预估的高出一大截。是代码写得太啰嗦?还是模型太笨、反复试错?
如果只盯着“模型输出了多少代码”,很难找到真正的原因。Claude Code 这类编程 Agent 的成本模型和普通 API 调用完全不同:每执行一步,都需要把系统提示、项目记忆、相关文件内容、工具输出以及全部对话历史重新发送一遍。会话越长,上下文越大,每次调用的开销就越贵。烧钱的核心不是那几行生成结果,而是被反复重放的输入内容。
这篇文章要解决的问题很具体:Claude Code 的 Token 到底花在哪,以及如何在不牺牲代码质量的前提下把成本压下来。我会从成本结构、会话管理、上下文控制、模型选择、权限配置和团队规范六个层面展开,附带可以直接使用的配置文件、成本审计脚本和排查清单。无论你是个人开发者还是团队技术负责人,下面这些方法都可以直接落到工作流里。
1. 为什么 Claude Code 的 Token 消耗总比想象中高
先看一个很常见的真实场景:你想让 Claude Code 把一个模块从旧写法升级到新语法。任务开始时,模型先列目录,再读取三四个核心文件,几万 Token 进入上下文。然后它开始修改,跑测试,失败,读报错日志,再改。中间你可能还会让它顺便看一下无关配置文件,或者解释一段历史代码的来龙去脉。
整个会话 40 轮下来,每一轮都在把几十个文件的内容重新发送一次。假设平均每轮输入 3 万 Token,40 轮就是 120 万 Token,而最终产物可能只是几百行代码变更。
这就是直觉和计费逻辑的最大差异:你为“过程”付费,不是为“结果”付费。模型不会记住上一轮说了什么,它必须把当前所有依赖的信息都塞进下一次请求里。如果上下文内容还在随轮次增长,总消耗会呈现超线性增长,而不是简单的线性叠加。
另一个容易被低估的因素是语言。英文通常 3 到 4 个字符约等于 1 个 Token,中文常见情况下一个汉字就接近 1 个 Token。也就是说,同样一段需求描述,中文可能比英文多消耗 30% 到 80% 的 Token。这不是劝你放弃中文交流,而是提醒你:不要让无关的长文本长期留在上下文里。
这一节可以浓缩成一句话:优化 Token 成本的核心,不是减少模型的输出,而是减少每一轮重复重放的输入。后面的所有方法都围绕这句话展开。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Token、上下文窗口与成本模型
2.1 Token 是什么
Token 是模型处理文本的最小单位,可以近似理解为“词块”。模型不会逐字阅读代码,而是把文本切分成 Token 后再计算。不同模型的分词规则不同,但大致可以参考:英文中一个普通单词约为 1 到 2 个 Token,四个字符约 1 个 Token;中文每个汉字约 1 到 2 个 Token;代码的密度介于两者之间,长标识符和字符串字面量会比短变量名更费 Token。
理解这一点对成本优化很重要。因为你在 Claude Code 里看到的 Git diff、错误日志、README 文件,最后都会按照 Token 计费,而不是按照“文件数量”或“行数”计费。
2.2 上下文窗口
模型一次请求能处理的最大 Token 数称为上下文窗口。Claude 系列模型各有不同的窗口上限,Claude Code 在接近上限时会自动压缩历史。这意味着它不会无限膨胀,但压缩本身也有成本:压缩会丢失细节,可能导致后面的步骤返工,返工又会产生新的 Token 消耗。
所以,最理想的用法不是“等接近上限再清理”,而是在任务边界主动重置上下文。
2.3 成本模型的三个计费面
商用模型大体按两部分计费:输入(你发给模型的所有文本)和输出(模型生成的文本)。输出单价比输入高,但在 Claude Code 场景里,输出总量通常远小于输入总量。
| 计费类型 | 相对单价 | 常见出现位置 |
|---|---|---|
| 原始输入 | 较高 | 首次读取文件、新增对话内容、缓存未命中 |
| 缓存命中输入 | 较低 | 每轮重放的稳定前缀、CLAUDE.md、未变化的文件 |
| 输出 | 最高 | 模型生成的代码与文本 |
具体价格请以官方定价页为准,这里只需要记住一点:控制成本的主要战场在输入侧。
2.4 提示缓存:重复前缀的重要影响
主流模型都支持提示缓存:如果一段相同前缀在上一次请求中已经出现过,这次可以直接按缓存读取计费,价格远低于原始输入。Claude Code 的长会话天然会产生大量重复前缀,所以会话结构越稳定,缓存命中率越高,单轮成本越低。
这也是为什么频繁修改 CLAUDE.md、在同一个会话里切换多个不相关任务,会让成本明显上升——前缀一变,缓存就失效了。
3. 成本审计:先知道钱花在哪里
没有数据的优化都是猜测。优化 Token 成本之前,应该先建立一套统计手段,知道每个会话、每个项目到底消耗了多少 Token。
3.1 从本地会话日志统计 Token
Claude Code 会把每次会话的完整记录写到本地,通常是 ~/.claude/projects/ 目录下,按项目名分目录,每个会话对应一个 JSONL 文件。JSONL 每一行是一次事件,其中可能包含 message.usage 字段,记录了该次请求的输入、输出和缓存 Token。
由于字段路径在不同版本里可能略有差异,下面这个脚本采用递归查找的方式,只要 JSON 里存在 usage 对象,就把里面的数值字段全部累加。这样即使结构变化,也能稳定统计。
python复制# 文件路径:scripts/audit_cost.py
# 用法:python scripts/audit_cost.py
import glob
import json
from collections import Counter
from pathlib import Path
def walk(obj, counter):
if isinstance(obj, dict):
if "usage" in obj and isinstance(obj["usage"], dict):
for k, v in obj["usage"].items():
if isinstance(v, (int, float)):
counter[k] += v
for v in obj.values():
walk(v, counter)
elif isinstance(obj, list):
for item in obj:
walk(item, counter)
def main():
counter = Counter()
pattern = str(Path.home() / ".claude" / "projects" / "**" / "*.jsonl")
for path in glob.glob(pattern, recursive=True):
with open(path, "r", encoding="utf-8", errors="ignore") as f:
for line in f:
try:
walk(json.loads(line), counter)
except json.JSONDecodeError:
continue
for k, v in sorted(counter.items()):
print(f"{k:30s} {v:>15,}")
if __name__ == "__main__":
main()
运行后你会看到类似下面的输出:
text复制cache_creation_input_tokens 1_234_000
cache_read_input_tokens 12_800_000
input_tokens 5_900_000
output_tokens 860_000
这些数字能直接告诉你:会话里占比最高的是重复读取的缓存内容,还是新增的原始输入。找出哪个项目、哪个会话的数值异常偏高,再针对性地优化。
3.2 观察单次会话的成本
交互界面的底部状态栏通常会显示当前会话的上下文占用和估算成本;如果看不到,可以检查是否开启了简化界面,或者直接看 --verbose 模式下的请求日志。日常使用中,养成“一个任务结束就看一眼成本”的习惯,比事后看总账单更有效。
3.3 官方控制台与团队限额
团队场景下,管理员可以在 Anthropic 控制台查看整体 API 用量,并为成员设置额度与告警。建议在项目启动初期就配置好预算上限,而不是等月底出账再复盘。
4. 第一层优化:会话生命周期管理
4.1 一个会话只做一个任务
Claude Code 的记忆存在于会话内部。如果你让它在同一个会话里连续完成“重构 A 模块”“排查 B 接口”“写 C 模块的单测”三件事,那么做第三件事时,前两件事的文件和对话历史仍然占着上下文。
正确的做法是:一个会话只对应一个边界清晰的任务。任务完成,把关键结论写入项目文档,然后开启新会话。新会话只携带少量必要的背景,其他内容一律不加载。
4.2 /clear 与 /compact 的适用时机
Claude Code 提供了两个手动手段:
/clear 清空整个对话历史,上下文占用归零,适合在切换任务时使用。/compact 把当前对话压缩成一段摘要后继续,适合在同一任务的中途使用——比如你刚让模型分析完三个文件,接下来要开始改代码,就不需要把读文件的完整内容继续背在身上。
判断标准很简单:换任务用 /clear;同一任务进入新阶段且不需要旧文件细节时,用 /compact。如果任务已经快做完,干脆直接开新会话。
4.3 两段式任务:先写计划,再分阶段执行
这是目前实践中性价比最高的一种用法。第一段让模型只做只读分析和规划,产出一份 PLAN.md 文件;第二段开始前清空会话,让模型读取 PLAN.md,按其中的阶段逐个执行。
bash复制# 第一阶段:只做分析,产出一份计划文件
claude -p "阅读 src/main/java/com/example/service 下的核心类,
输出一份升级到 Java 17 的改造计划,写到 docs/plan-java17.md"
# 第二阶段:分阶段实施,每阶段开新会话,让模型先读计划
claude --model sonnet --max-turns 30
这种玩法的好处是:分析阶段输出少,实现阶段上下文干净,计划文件又保证了任务连续性。最贵的“反复读文件”只发生在第一段,后续每步只需要读取计划文件和少量目标文件。
4.4 用 max-turns 设置回合上限
--max-turns 限制模型在一次任务中的工具调用轮数。如果模型进入反复试错状态,这个参数能强制停止,避免无限烧 Token。
bash复制claude --max-turns 30
注意不要把这个参数设得太小,否则任务没做完就被截断,返工成本反而更高。更推荐的做法是:先把任务拆成可以在 15 到 30 轮内完成的小单元,再配合回合上限兜底。
5. 第二层优化:控制输入上下文的“面积”
5.1 精简 CLAUDE.md
CLAUDE.md 是 Claude Code 的项目记忆文件,在每次会话启动时都会加载到系统上下文中。换句话说,它每轮请求都会出现一次,属于最典型的重复前缀。文件写得越长,固定成本就越高。
只放对所有任务都成立的约定,比如技术栈、构建命令、目录结构和必须遵守的规范。第一次进入项目可以让模型运行 /init 自动生成,但生成结果往往偏长,建议人工精简。
markdown复制# 文件路径:CLAUDE.md
## 项目概述
- 技术栈:Java 17 + Spring Boot 3 + Maven
- 构建命令:mvn clean package
- 测试命令:mvn test
## 目录约定
- src/main/java:业务代码
- src/test/java:单元测试
## 硬性约束
- 不要修改 src/main/resources/generated/**
- 不要新增第三方依赖,除非先在 issue 中说明
- 公共方法必须写 Javadoc
这里有个容易被忽略的点:CLAUDE.md 里写具体实现细节,不如“把规则放进文档,让模型去读文档”。因为每次会话都必须加载 CLAUDE.md,但只有少数任务才需要详细规范。详细内容应该放到单独的文件里,需要时再让模型按路径读取。
5.2 用 .claudeignore 隔离噪音
.claudeignore 的作用类似 .gitignore。被忽略的文件不会出现在模型的可读范围内,也不会被一次性塞进上下文。
gitignore复制# 文件路径:.claudeignore
node_modules/
dist/
build/
coverage/
*.log
.tmp/
docs/archive/
target/
在实际项目中,构建产物和依赖目录的噪音文件量非常大。没有忽略规则时,模型列目录可能看到几千个文件;有了规则后,它只会看到真正需要关心的源码和配置文件。
5.3 指导模型按需读取
不要让模型一次性“了解整个项目”。更省成本的方式是让它先看目录结构,再决定读取哪些文件。
在提示词里明确指定范围,效果远好于开放式描述:
text复制先运行 ls src/main/java/com/example/service 查看该目录下的文件列表,
然后只读取你判断与登录逻辑相关的 3 个文件。
不要在未读取文件之前编写代码。
同样,遇到会输出大量内容的命令时,要求模型限制输出长度。比如与其让它运行 ls -R,不如明确要求使用 ls | head -100,避免工具输出塞满上下文。
5.4 大文件与小 diff
一个上千行的文件被读取时,会消耗上万甚至几万 Token。如果多个轮次反复读取同一个大文件,成本马上失控。
处理思路有三条:一是尽量保持文件粒度合理,单个文件职责单一;二是修改时要求模型使用局部替换,而不是把整个文件重写一遍;三是当真不需要文件细节时,用 /compact 把旧内容摘出去。
6. 第三层优化:提示词设计与任务拆解
6.1 目标-约束-产出三段式
给 Claude Code 写任务时,信息的组织方式直接决定它探索多少范围。推荐使用“目标、约束、产出”三段式,目标说明做什么,约束说明边界,产出说明交付形式。
text复制目标:把 utils/date.py 中的所有 datetime 用法替换为 dateutil,
并保证现有测试全部通过。
约束:
1. 只修改 utils/date.py 及其测试文件;
2. 不要修改其他模块;
3. 不要新增第三方依赖。
产出:修改后的代码、mvn test 的运行结果、变更清单。
有了边界,模型就不会为了“保险”去读无关模块。没有边界时,它的默认行为往往是扩大排查范围,Token 就这样被消耗掉了。
6.2 禁止开放式任务
“优化一下这个项目”“看看这个模块有哪些问题”这类描述对 Agent 来说成本极高,因为模型不知道边界在哪里,只能自己定义范围,然后大量读取文件。
改进方法是把它变成选择题或填空题:明确到文件、明确到行为、明确到验收标准。任务描述越具体,探索性读取越少。
6.3 任务是成本的单位
很多人把 Token 成本理解成“和模型的对话长度”,其实更准确的单位是任务。一个边界清晰的小任务,哪怕中间有两三次试错,总成本也是可控的。一个边界模糊的大任务,模型会反复扩大上下文,最终成本远高于几个小任务之和。
所以拆任务的收益不只是进度可控,还是成本可控。建议把“能否在 20 轮内完成”作为任务大小的经验判断标准。
7. 第四层优化:模型选择、子代理与自定义指令
7.1 按任务难度分层选模型
Claude 系列有不同的模型定位,价格和推理能力也不同。按任务难度选择模型,是成本优化里最直接的一环:
- 困难任务:架构设计、复杂重构、疑难 Bug 定位,必要时使用能力最强的模型,但要限制在关键决策上。
- 日常任务:常规 CRUD、接口实现、单元测试,使用默认的均衡模型即可。
- 杂活:重命名、格式化、生成注释、整理摘要,考虑使用轻量模型。
在交互中可以用 /model 切换,也可以在启动时用 --model 指定。具体型号以你使用的版本为准,思路是按任务稀缺程度分配模型,而不是一个模型跑到底。
7.2 子代理隔离上下文
Claude Code 支持自定义子代理。子代理的作用很像“外包员工”:它会读取大量文件、消耗自己的上下文,只把一份精炼摘要返回给主会话。这样一来,主会话不会因为读过几十个文件而膨胀。
markdown复制# 文件路径:.claude/agents/context-digester.md
---
name: context-digester
description: 阅读一组文件并产出精炼摘要,适合在需要了解大量文件时使用
tools: Read, Grep
model: haiku
---
读取用户给出的文件列表,输出结构化摘要:
- 每个文件的核心职责
- 对外提供的公共接口
- 与当前任务相关的关键逻辑
- 潜在风险点
不输出与任务无关的细节。
当任务需要“先了解全貌”时,让子代理去读材料、主会话只接收结论,能显著压低主上下文的 Token 成本。这个模式在分析遗留代码时尤其好用。
7.3 自定义指令与技能
Claude Code 支持把常用的、比较长的指令保存为自定义命令,放在 .claude/commands/ 目录下。这样既可以复用团队规范,又不需要把大段指令写进 CLAUDE.md。
markdown复制# 文件路径:.claude/commands/code-review.md
---
description: 按团队规范做一轮代码审查
---
只审查当前任务涉及的文件:
1. 不要修改代码,只输出问题列表;
2. 按严重程度排序;
3. 每个问题给出最小修复建议,不要贴整段代码。
类似的,可以把更长的手册式内容放到技能目录中,按需加载。核心思想是:高频且简短的内容留在 CLAUDE.md,低频但篇幅大的内容放到按需加载的文件里。
8. 第五层优化:权限模式、hook 与完整配置示例
8.1 每弹一次权限确认都是一次完整调用
在 Claude Code 运行过程中,如果模型要执行未授权的命令,会停下来询问你。每一次确认都意味着一次完整的上下文发送和响应,相当于多烧一轮 Token。频繁授权这个隐形成本,比很多人想象中要高。
因此在可信项目中,应该尽量在配置里预授权高频且安全的操作,减少交互往返。
8.2 settings.json 示例
项目级配置放在 .claude/settings.json 中,下面是一份适合日常开发的示例:
json复制{
"model": "sonnet",
"permissions": {
"allow": [
"Read(./src/**)",
"Edit(./src/**)",
"Read(./docs/**)",
"Bash(git status)",
"Bash(git diff)",
"Bash(mvn test *)"
],
"deny": [
"Read(./target/**)",
"Read(./node_modules/**)",
"Bash(rm -rf *)"
]
},
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "node ./scripts/log-cost.mjs"
}
]
}
]
}
}
这里用 allow 列表放行高频操作,用 deny 列表阻止危险命令和噪音目录。hooks 中的 Stop 事件可以在每次任务结束时执行一个脚本,用于记录成本或发送提醒。
权限规则的具体语法和可用事件随版本演进,请以你的安装版本为准。配置完成后,可以用 /status 检查当前会话的权限模式是否生效。
8.3 权限模式的正确用法
Claude Code 提供不同的权限模式,核心原则是最小权限:
- 默认模式:每次执行新类型操作都询问,适合不熟悉项目结构的时候。
- 接受编辑模式:自动放行指定范围内的文件修改,适合在可信目录中开发。
- 计划模式:只允许读取和分析,不允许修改,适合做方案设计。
- 绕过权限模式:跳过所有确认,只应在隔离的容器或 CI 环境中使用,并且要确保命令本身在 deny 列表里有兜底。
需要特别提醒的是,涉及生产环境变更、删除操作或敏感数据时,不要为了省 Token 而放宽权限。先在小范围测试,保证有备份和回滚方案,再把权限放宽一层。
9. 运行结果与效果验证
9.1 启动命令
完成上述配置后,用以下命令启动一个受控会话:
bash复制claude --model sonnet --max-turns 30
进入项目目录后,Claude 会自动读取 CLAUDE.md、.claudeignore 和 .claude/settings.json。可以先用 /status 确认配置已加载,再开始任务。
9.2 对比验证步骤
验证优化效果不要拍脑袋,建议做一个简单的前后对比:
- 准备一个固定任务,比如“给某个模块补充单元测试并跑通”。
- 优化前:直接在一个新会话里执行,记录轮次、输入 Token、输出 Token 和完成质量。
- 优化后:应用精简后的 CLAUDE.md、.claudeignore、权限配置和任务拆解方式,再执行同样的任务。
- 用第 3 节的审计脚本汇总两次消耗,比较 input、cache_read、output 三项指标。
下面是一个示意性的对比,数字仅用于说明量级,实际以你的任务和模型版本为准:
| 指标 | 优化前(示例) | 优化后(示例) |
|---|---|---|
| 会话轮数 | 42 | 18 |
| 输入 Token 合计 | 980,000 | 320,000 |
| 输出 Token | 55,000 | 38,000 |
| 返工次数 | 9 | 3 |
9.3 判断标准
判断优化是否成功,不只看 Token 数字下降,还要看产出质量是否稳定。如果为了省上下文把必要信息都删掉了,模型频繁返工,总成本反而会上升。真正健康的指标是“同质量下,轮次更少、输入更少、返工更少”。
如果发现轮次没减少,先别急着调参数,回到任务描述上:任务边界是否清晰,文件范围是否明确。大多数 Token 浪费都发生在任务描述含糊的阶段。
10. 常见问题与排查思路
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 账单飙升但没做几个功能 | 一个会话长时间不清理,历史不断重放 | 查看会话轮数和上下文占用 | 按任务拆会话,切换任务前使用 /clear |
| 模型反复读取同一个大文件 | 上下文被压缩后丢失细节,或指令没给够约束 | 查看工具调用日志中的 Read 次数 | 明确告知文件已读,提供摘要文件,拆分大文件 |
| 轮次暴涨,频繁请求授权 | 权限模式过严,每次操作都要询问 | 统计权限确认频率 | 在 settings.json 中预授权高频安全操作 |
| compact 后丢失关键信息,导致返工 | 摘要保留的信息不完整 | 对比压缩前后的需求描述 | 压缩前把关键结论写入 CLAUDE.md 或计划文件 |
| 中文任务描述导致 Token 偏高 | 中文 Token 密度高于英文 | 对比同任务中英文描述 | 长文档保留中文,代码注释和变量名使用英文 |
| 任务被 max-turns 截断 | 任务过大或回合上限太小 | 查看停止原因 | 拆小任务,或在关键节点写入计划文件后分阶段续跑 |
| 模型把无关目录读进上下文 | 缺少 .claudeignore 规则 | 查看读取文件列表 | 添加忽略规则,扩大 deny 列表 |
11. 团队协作与工程最佳实践
这些优化方法在个人项目里能看到效果,在团队项目中价值更大,因为它在统一开发方式和成本基线。
第一,把 .claude/settings.json、.claudeignore 和精简后的 CLAUDE.md 纳入版本库,作为项目模板的一部分。新成员加入后,不需要自己摸索配置,也不会因为个人偏好不同导致成本差异很大。
第二,把成本纳入代码评审的关注点。每次使用 Claude Code 完成一个任务后,记录一下该任务的 Token 消耗和轮次,积累一段时间后,你会发现哪些类型的任务天然高成本,进而调整任务拆分方式。
第三,建立会话生命周期约定。比如“一个任务一个会话”“任务结束必须把结论写入文档”“切换任务前必须 /clear”,这些约定听起来很简单,但能避免大多数无意识消耗。
第四,对于需要自动执行的场景,比如 CI 里的代码审查或测试修复,使用一次性隔离环境,限制权限和回合数,并保证所有操作可审计。不要在生产机器上以宽权限运行 Agent。
第五,注意安全边界。不要把数据库凭证、密钥、个人信息写进 CLAUDE.md 或提示词中;hook 脚本不要输出敏感信息;任何涉及删除、覆盖、数据变更的任务,都先在测试环境验证,并准备回滚方案。
12. 总结与后续学习方向
这篇文章真正想讲清楚的,其实只有三句话:
第一,输入是成本大头,输出不是。第二,历史重放和文件重读是输入成本的大头。第三,任务边界决定历史长度,所以控制任务的颗粒度就是控制成本。
如果你现在就想动手,可以先做三件事:给当前项目补一份 .claudeignore 并精简 CLAUDE.md;跑一次第 3 节的审计脚本,看看过去一周的 Token 流向;下一次任务按“单任务单会话 + 明确文件范围 + 先计划后实现”的方式执行。
更深入的内容,可以继续研究提示缓存原理、hooks 自动化、子代理架构,以及模型性能与成本的平衡。这些方向都能把成本优化从“技巧”变成“工程能力”。
把成本意识写进日常工作流,比任何单个技巧都管用。下次开新会话前,对照上面的清单检查一遍,账单会给你正向反馈。建议收藏备用,方便团队里需要时直接对照操作。
