Claude Code 成本调优实践:把好模型用在关键路径上
最近两个月Token总是不够用,感觉 Claude Code 在偷我 Token!动了换个其他开源 Coding Agent 的心思。
了解了一圈,看到了 omp(oh-my-pi,一个 AI 编程工具)的模型角色设计。
omp 把模型分为几个命名槽位,比如 default(主模型 / 日常对话)、smol(快而便宜的任务)、slow(深度推理)、plan(规划模式)。每个槽位可以独立绑定模型,agent 在特定时刻自动从对应槽位取模型——用户只需要把合适的模型固定到对应的角色上。
这是一个很好的思路。反观我的实际用法——主对话、Explore 搜代码、后台 hook、离开摘要(recap),所有场景都跑在同一个重模型上,等于统统用大炮。
用大炮打蚊子,Token 用量肯定爆炸~
Claude Code 没有 omp 那种明面上的【角色系统】,但模型配置里留了不少入口,够我们手动做一套高中低三级搭配。下面我从别名机制、场景分层、再到可直接抄的配置方案这条线展开。
本文基于 Claude Code v2.1.88 的实际行为验证。模型配置这块迭代比较快,不同版本的默认值和边界条件可能会有差异,具体还是以你手上的版本为准。
一、别名层:把【能力诉求】和【具体模型】解耦
1.1 为什么需要别名
同一个模型在不同提供商的 API 里可能对应不同的 ID。比如 Sonnet 4.6 在 Anthropic 官方 API 是 claude-sonnet-4-6,在 AWS Bedrock 则是 us.anthropic.claude-sonnet-4-6。如果是 DeepSeek、Qwen 这类完全不同的模型体系,ID 差异更大。
如果 Claude Code 内部到处写死具体 model ID,每换一个 provider 或模型都得改配置——这显然不现实。
Claude Code 的做法类似 Java 的面向接口编程:内部只表达【我需要 Sonnet 级别的能力】,不写死具体 model ID,具体实现通过环境变量注入。别名映射层的价值就在这里——把【能力诉求】和【具体模型】解耦。
三个核心别名:
sonnet→ 查询ANTHROPIC_DEFAULT_SONNET_MODEL,获取具体的 model IDopus→ 查询ANTHROPIC_DEFAULT_OPUS_MODELhaiku→ 查询ANTHROPIC_DEFAULT_HAIKU_MODEL
注意:这三个 DEFAULT_* 变量是别名解析的【终点】,必须填完整 model ID,不能再套一层别名。
1.2 内置别名
除了三个家族别名,Claude Code 还内置了两个特殊别名:
| 别名 | 解析结果 | 备注 |
|---|---|---|
sonnet | ANTHROPIC_DEFAULT_SONNET_MODEL | 最常用 |
opus | ANTHROPIC_DEFAULT_OPUS_MODEL | — |
haiku | ANTHROPIC_DEFAULT_HAIKU_MODEL | — |
best | ANTHROPIC_DEFAULT_OPUS_MODEL | 硬编码指向 Opus,无法单独配置 |
opusplan | Sonnet 默认,plan 模式自动切 Opus | 特殊语义 |
1.3 模型修饰符:[1m]
[1m] 不是别名,而是【模型修饰符】——可以附加在 model ID 或别名后面,告诉 Claude Code 调整 context window 和 compact 阈值。这个后缀是开放的,未来模型上下文窗口增长时可能出现 [3m]、[5m] 等。
通俗来说,它就是个 client-side 的标记位:Claude Code 检测到 [1m] 后,会把本地 context window 视为 1,000,000,并在发 API 前把后缀 strip 掉。
这里有个容易踩坑的细节:Claude Code 看到 [1m] 后,会先相信这个后缀声明,把本地上下文窗口按 1M 处理,而不是先去判断这个模型到底支不支持 1M。
这意味着 [1m] 有两层含义:
- 对 Claude Code 客户端:这是一个明确的配置指令。它会影响本地 context window 计算、compact 阈值等策略。
- 对真实模型/provider:这不是能力证明。provider 或网关是否真的能吃下 1M Token,需要你自己确认。
这个东西有点像你在配置里声明”这个集装箱能装 1M Token”,Claude Code 会按这个声明安排装货,但最后仓库门能不能进,还是仓库说了算。
所以给第三方模型加 [1m] 之前,建议先做一次实际验证:丢一个超过 200K Token 的长上下文请求,看 provider 侧是否真的接受。
如果只是为了”看起来支持 1M”就随手加,反而可能让 Claude Code 推迟 compact,最后在 API 侧才失败。
二、场景层:模型消耗的调优切面
别名解决了【某个名字指向什么模型】,场景层解决【什么时候用哪个名字】。
2.1 模型的【能力-成本】光谱
Claude Code 的别名体系(opus / sonnet / haiku)本质上是对模型能力的抽象:
| 层级 | 典型模型 | 适合场景 | 核心取舍 |
|---|---|---|---|
| 高能力 / 高成本 | Opus 级 | 深度推理、架构决策、复杂调试 | 一次错误转向的成本大于模型费用 |
| 中能力 / 中成本 | Sonnet 级 | 日常编码、代码生成、中等复杂度任务 | 主力工作模型,性价比最好 |
| 低能力 / 低成本 | Haiku 级 | 搜索分类、摘要、hook 处理、广度任务 | 速度优先,不追求深度推理 |
对于用第三方模型(DeepSeek 等)的用户,这个抽象层同样适用——把三个别名分别指向你的高、中、低能力模型即可。
2.2 三个场景分配键
场景层最值得关注的是三个环境变量,它们决定了大部分可调优的模型消耗分布:
| 键 | 控制场景 | 示例值 | 支持别名 | 备注 |
|---|---|---|---|---|
ANTHROPIC_MODEL | 主循环对话 | sonnet / opus / deepseek-v4-pro[1m] | 是 | 最重要的场景,直接影响编码体验 |
CLAUDE_CODE_SUBAGENT_MODEL | 所有子 Agent | haiku / sonnet | 是 | 设了之后全局覆盖所有子 Agent(包含 Explore),无法按 Agent 类型分别设置。走完整别名解析,支持 sonnet[1m]、opusplan 等 Agent Tool model 参数不支持的写法 |
ANTHROPIC_SMALL_FAST_MODEL | 后台轻量任务 | claude-haiku-4-5-20251001 | 否 | 必须填完整 model ID;未设时 fallback 到 ANTHROPIC_DEFAULT_HAIKU_MODEL |
2.3 SMALL_FAST_MODEL 和 DEFAULT_HAIKU_MODEL 的关系
两个变量都跟 Haiku 有关,容易搞混,但职责很清晰:
ANTHROPIC_SMALL_FAST_MODEL:只管后台任务。设了就生效,没设则 fallback 到DEFAULT_HAIKU_MODEL。不支持别名。ANTHROPIC_DEFAULT_HAIKU_MODEL:管两件事——既是 haiku 别名的解析目标,又是 SMALL_FAST 没设时的 fallback。
因此只设
ANTHROPIC_DEFAULT_HAIKU_MODEL就能同时覆盖 haiku 别名和后台任务。只有当你希望后台任务用跟 haiku 别名不同的模型时,才需要额外设ANTHROPIC_SMALL_FAST_MODEL。
2.4 Claude Code 里到底哪些地方在消耗模型调用?
现在来看本文关注的主要模型消耗场景:
- 主循环:你的每次对话。由
ANTHROPIC_MODEL控制,默认值取决于用户层级。这是核心资产,最值得用好模型。 - 子 Agent — Explore:代码搜索和文件定位。内置设置为
haiku,不需要额外配置。Claude Code 的设计者早就意识到【用重模型搜代码是浪费】。 - 子 Agent — Plan / General Purpose / Verification:架构设计、通用任务、验证检查。默认继承父模型(
inherit),意味着主循环用 Opus 它们也跟着用 Opus。Plan Agent 值得保持较高能力,General Purpose 和 Verification 可以降级。 - 后台任务(共 12 个场景):主要由
ANTHROPIC_SMALL_FAST_MODEL控制。具体包括:Token 计数、API Key 验证、离开摘要生成、API 配额检查、会话搜索、Hook Agent、Prompt Hook 默认模型、Skill 改进建议、Web 搜索分类/分发、Prompt Caching 禁用判断、Bedrock 区域路由、内部轻量查询,共 12 个场景。这些场景的共同特征:广度比深度重要,速度快比推理深重要,Haiku 级别通常够用。Claude Code 在大多数这类调用中直接使用小模型;少数场景会根据条件回退到主模型或 Sonnet。 - Plan 模式:如果选了
opusplan别名,进入 plan 模式且当前上下文没有超过 200K Token 时,Claude Code 会从 Sonnet 切到 Opus,并且这条路径不会自动带上[1m]后缀。如果选的是 haiku,plan 模式下 haiku 会被自动替换为 Sonnet(haiku 推理深度不够做规划)。注意:以上 Plan 模式行为基于 v2.1.88 的观察,不属于公开 API 契约,后续版本可能变化。
子 Agent 模型优先级(从高到低):CLAUDE_CODE_SUBAGENT_MODEL 环境变量 > Agent 自身 frontmatter 配置 > inherit(继承主模型)。
汇总成一张表:
| 场景 | 控制键 | 默认模型 | 支持别名 | 备注 |
|---|---|---|---|---|
| 主循环 | ANTHROPIC_MODEL | Sonnet / Opus | 是 | 默认值取决于用户层级 |
| 子 Agent — Explore | 内置 Agent 配置(haiku) | Haiku | — | 会被 CLAUDE_CODE_SUBAGENT_MODEL 覆盖 |
| 子 Agent — 其他 | CLAUDE_CODE_SUBAGENT_MODEL > Agent 自身配置 | inherit | 是 | 继承父模型 |
| 后台任务(共 12 个场景) | ANTHROPIC_SMALL_FAST_MODEL | Haiku(fallback) | 否 | 必须用完整 ID |
| Plan 模式 | opusplan / haiku 特殊逻辑 | Sonnet→Opus / Haiku→Sonnet | — | 有 200K 边界条件 |
这里有三点很容易影响账单:
- Explore Agent 默认已是 Haiku,搜代码不需要额外配置——这一块已经是最优解。
ANTHROPIC_SMALL_FAST_MODEL覆盖了前面列出的 12 个后台场景。没设的话自动 fallback 到 Haiku 默认值。你需要确保ANTHROPIC_SMALL_FAST_MODEL或ANTHROPIC_DEFAULT_HAIKU_MODEL至少有一条路径指向你的轻量模型。- 子 Agent 默认继承父模型。你主循环用 Opus,General Purpose 子 Agent 也会用 Opus。这里需要自己决定:是设
CLAUDE_CODE_SUBAGENT_MODEL统一降级,还是在自定义 Agent 配置里逐个指定。
2.5 别名可以用在哪
掌握了场景层之后,别名的使用位置就很清楚了。别名(含部分修饰符组合,如 sonnet[1m])支持以下位置:
ANTHROPIC_MODEL、CLAUDE_CODE_SUBAGENT_MODEL--model启动参数- settings.json 的
model字段 /model命令- Skill 的
model配置 - settings.json 的
availableModels白名单
注意:Agent Tool 的 model 参数只接受 sonnet / opus / haiku 三个裸别名。ANTHROPIC_SMALL_FAST_MODEL 不支持别名(上方 2.2 表格已标注),必须填完整 model ID。
2.6 模型解析链路(深入参考)
上面是概念层,下面是对应的代码路径。理解之后可以自己推导各种配置组合的实际效果:
主循环模型:
graph TD
A[ANTHROPIC_MODEL / --model / /model] --> B[parseUserSpecifiedModel]
B --> C{是别名?}
C -->|sonnet| D[getDefaultSonnetModel]
C -->|opus / best| E[getDefaultOpusModel]
C -->|haiku| F[getDefaultHaikuModel]
C -->|否| G[直接当 model ID 返回]
D --> G
E --> G
F --> G
子 Agent 模型(优先级从高到低):
graph LR
A[CLAUDE_CODE_SUBAGENT_MODEL] -->|有值| B[parseUserSpecifiedModel]
A -->|无值| C[Agent frontmatter 配置]
C -->|无配置| D[inherit 继承主模型]
B --> E[返回 model ID]
C --> E
D --> E
后台任务模型:
graph TD
A[getSmallFastModel] --> B{SMALL_FAST_MODEL 有值?}
B -->|有| C[直接返回<br/>不走别名解析]
B -->|无| D[DEFAULT_HAIKU_MODEL]
D -->|有值| E[返回 model ID]
D -->|无值| F[硬编码默认值]
F --> E
三、配置方式:settings.json 还是 export?
在抄方案之前,先交代一个背景:本文所有方案统一用 settings.json 的 env 字段来写,而不是 shell 的 export。
为什么?对本文讨论的模型变量来说,两者在普通本地启动下等价——Claude Code 最终也是把 settings.json 的 env 注入到 process.env。但 settings.json 支持分层配置,方便不同项目用不同模型,不必每次启动都设一遍环境变量。
日常最常用的三层:
| 级别 | 路径 | 作用域 |
|---|---|---|
| 用户级 | ~/.claude/settings.json | 全局,所有项目生效 |
| 项目级 | <project>/.claude/settings.json | 当前项目,可提交 git 共享 |
| 本地级 | <project>/.claude/settings.local.json | 当前项目个人偏好,自动 gitignore |
优先级:用户级 < 项目级 < 本地级。同一配置项在更高优先级的文件中覆盖低优先级的值。
模型配置是用户偏好,放在用户级即可。如果某个项目需要特殊配置(比如重构中临时切 Opus),在项目级覆盖。
一个典型的用户级 settings.json 长这样:
1
2
3
4
5
6
7
{
"env": {
"ANTHROPIC_MODEL": "sonnet",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "claude-sonnet-4-6",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "claude-haiku-4-5-20251001"
}
}
如果你更习惯 shell export,把 "KEY": "value" 转成 export KEY="value" 也可以,普通本地启动下对模型选择基本等价。
四、调优实战:四个典型配置方案
架构和场景都讲完了,下面是实操。四个方案均以用户级 settings.json 的 env 格式呈现。
4.1 方案 A:全部压平(调试 / 测试用)
所有场景用同一个模型。适用:CI 环境、刚接入第三方模型想验证连通性、对成本不敏感。
1
2
3
4
5
6
7
8
{
"env": {
"ANTHROPIC_MODEL": "deepseek-v4-pro[1m]",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "deepseek-v4-pro[1m]",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "deepseek-v4-pro[1m]",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "deepseek-v4-pro[1m]"
}
}
不设 ANTHROPIC_SMALL_FAST_MODEL——后台任务 fallback 到 ANTHROPIC_DEFAULT_HAIKU_MODEL,已经指向同一个模型。CLAUDE_CODE_SUBAGENT_MODEL 也不设:Explore 默认 haiku,Plan / Verification / General Purpose 多数走 inherit,还有少数内置 Agent 指定自己的模型。
但因为 opus / sonnet / haiku 全部映射到同一个 model ID,别名怎么绕都会绕回同一个模型。简单粗暴,但有明显浪费——Explore 搜代码和主对话花一样的钱。
4.2 方案 B:主 Sonnet + 后台 Haiku(省钱方案)
最推荐的个人开发配置。把别名层的映射补齐,主循环走 Sonnet,其余交给默认行为:
1
2
3
4
5
6
7
{
"env": {
"ANTHROPIC_MODEL": "sonnet",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "your-main-model-id",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "your-fast-model-id"
}
}
如果你用的是 Anthropic 官方 API,
DEFAULT_*映射变量有内置默认值,可以跳过——只设ANTHROPIC_MODEL一个变量就够了。
后台任务不设 ANTHROPIC_SMALL_FAST_MODEL,自动 fallback 到 Haiku。子 Agent 不设 CLAUDE_CODE_SUBAGENT_MODEL,Explore 自动走 Haiku,其余走 inherit 继承 Sonnet。
我的判断是:主对话值得用中等模型,后台任务用默认 Haiku 就够了。Explore Agent 内置 Haiku,不用操心;Plan / General Purpose 继承 Sonnet,也能保住推理质量。
4.3 方案 C:主 Opus + 子 Sonnet + 后台 Haiku(性能方案)
主循环用最强模型,子 Agent 统一降级,后台任务最便宜。适用于复杂项目、架构重构。
1
2
3
4
5
6
7
8
9
{
"env": {
"ANTHROPIC_MODEL": "opus",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "your-strong-model-id",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "your-main-model-id",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "your-fast-model-id",
"CLAUDE_CODE_SUBAGENT_MODEL": "sonnet"
}
}
Anthropic 官方 API 用户可跳过
DEFAULT_*映射变量。
分层效果:主对话 Opus 深度推理 → 子 Agent 统一 Sonnet → 后台 Haiku。
此方案下 Explore 也会从 Haiku 变成 Sonnet。
这是个小小的浪费,但换来的好处是所有子 Agent 至少都有 Sonnet 级能力。对于架构重构这种任务,我觉得这笔账是划算的。
4.4 方案 D:主 Sonnet + 子 Agent 全降级(激进省钱)
在方案 B 的基础上更进一步——不仅后台任务用轻量模型,子 Agent 也统一降级,只留主循环用 Sonnet:
1
2
3
4
5
6
7
8
9
{
"env": {
"ANTHROPIC_MODEL": "sonnet",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "your-strong-model-id",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "your-main-model-id",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "your-fast-model-id",
"CLAUDE_CODE_SUBAGENT_MODEL": "haiku"
}
}
如果模型体系只有两档(比如 DeepSeek 的 pro 和 flash),把
DEFAULT_OPUS_MODEL和DEFAULT_SONNET_MODEL指向同一个模型即可。
和方案 B 的关键区别是 CLAUDE_CODE_SUBAGENT_MODEL=haiku——Explore、Plan、General Purpose、Verification 全部走轻量模型。代价也很明确:复杂规划和验证任务被压在轻量模型上跑。
日常写小需求问题不大,但大重构或疑难排障时,我会临时删掉 CLAUDE_CODE_SUBAGENT_MODEL,让子 Agent 回到继承模式(等同于退回方案 B)。
如果想独立控制后台任务模型(不与 haiku 别名绑定),可以额外设 ANTHROPIC_SMALL_FAST_MODEL。不过只设 DEFAULT_HAIKU_MODEL 已经通过 fallback 覆盖了后台任务,多数情况下够用。
上面四个方案是通用模板。下面说说我自己踩过的坑——一个看起来合理、实际上抹平了所有分层的反面配置。
五、踩坑实录:我曾经把所有路径都指向了 Sonnet
回到开头说的 Token 消耗问题,我为了省事,把所有模型入口都指向了同一个 Sonnet 模型。用 settings.json 表达,大概就是这样:
1
2
3
4
5
6
7
8
9
10
{
"env": {
"ANTHROPIC_MODEL": "sonnet",
"ANTHROPIC_SMALL_FAST_MODEL": "claude-sonnet-4-6",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "claude-sonnet-4-6",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "claude-sonnet-4-6",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "claude-sonnet-4-6",
"CLAUDE_CODE_SUBAGENT_MODEL": "claude-sonnet-4-6"
}
}
这套配置把 CLAUDE_CODE_SUBAGENT_MODEL、ANTHROPIC_DEFAULT_HAIKU_MODEL、ANTHROPIC_SMALL_FAST_MODEL 全部指向了 Sonnet,实际效果是——所有场景不分轻重,一律 Sonnet:
| 场景 | 期望模型 | 实际模型 |
|---|---|---|
| 主循环 | Sonnet | Sonnet |
| Explore 搜代码 | Haiku | Sonnet |
| 后台轻量任务 | Haiku / Flash | Sonnet |
| 其他子 Agent | inherit / Sonnet | Sonnet |
Claude Code 原本已经做好的轻重分层被全部抹平了——典型的”大炮打蚊子”。
我后来改成了【Sonnet + deepseek-v4-flash】这套分层:
1
2
3
4
5
6
7
8
{
"env": {
"ANTHROPIC_MODEL": "sonnet",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "claude-sonnet-4-6",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "deepseek-v4-flash",
"CLAUDE_CODE_SUBAGENT_MODEL": "haiku"
}
}
这套写法依赖我使用的网关同时支持 Claude 和 DeepSeek 模型。如果你用的是 Anthropic 官方 API,不要照抄
deepseek-v4-flash,换成自己 provider 可用的轻量模型 ID。
效果和方案 D 一致:主循环 Sonnet,其余路径全部落到 Flash(后台任务 fallback 到 DEFAULT_HAIKU_MODEL,子 Agent 通过 CLAUDE_CODE_SUBAGENT_MODEL=haiku 解析到同一个模型)。代价也一样——复杂任务时我会临时删掉 CLAUDE_CODE_SUBAGENT_MODEL,退回方案 B。
六、调优验证
配完怎么确认?几个方法:
- Claude Code 启动日志。 启动 Claude Code 时会打印当前主模型名称。
/model命令。 显示当前主循环模型和可选列表,能看到别名解析到了哪个具体 model ID。- 观察子 Agent。 触发一次 Explore(让 Claude Code 搜个函数定义),看响应速度和风格。如果 Explore 的回复推理很深、很详尽,可能没走 Haiku——检查
CLAUDE_CODE_SUBAGENT_MODEL是否设了值。 - 网关侧日志。 在 API 日志里按 model ID 分组统计调用次数和 Token 消耗。预期不是某个固定比例,而是能看到主循环、子 Agent、后台任务落在不同 model ID 上。后台任务的 model ID 应该是你指定的快速模型。
七、总结
Claude Code 没有 omp 那种正式的角色系统,但通过两层配置键的组合,已经可以手动做出高中低三级模型搭配。最后收一下我自己的配置原则:
第一,不同场景不要硬塞同一级模型。 Explore 搜代码、后台 hook 处理、离开摘要这些场景的【单位 Token 价值】远低于主对话。Claude Code 的默认行为(Explore 已设 Haiku、SMALL_FAST fallback 到 Haiku)已经帮你做了一部分优化,你只需要按自己的 provider 把映射层补齐。
第二,能用别名就不要硬编码 model ID。 在 ANTHROPIC_MODEL、CLAUDE_CODE_SUBAGENT_MODEL、Skill 的 model 配置里优先用别名。切换模型时只改映射层环境变量,少改业务配置。
第三,确保轻量模型路径明确指向轻量模型。 不管是通过 ANTHROPIC_DEFAULT_HAIKU_MODEL 还是 ANTHROPIC_SMALL_FAST_MODEL,后台任务都不应该悄悄烧主模型。
速查表:
| 方案 | 主循环 | 子 Agent | 后台任务 | 适用 |
|---|---|---|---|---|
| A 全部压平 | 重 | 别名最终都指向重模型 | 重 | 调试、CI |
| B 省钱方案 | Sonnet | 继承 + Explore=Haiku | Haiku(默认) | 日常开发 |
| C 性能方案 | Opus | Sonnet(含 Explore) | Haiku(默认) | 架构重构 |
| D 激进省钱 | Sonnet | 统一 Haiku | Haiku | 极致省钱 |
模型分层这件事的道理其实很朴素:不同任务对【能力】的需求就是不一样,没必要用大炮打蚊子。
Claude Code 已经帮你做了一部分优化,比如 Explore 默认 Haiku、后台 fallback 到轻量模型。我们要做的不是推翻它,而是顺着这套设计,把 provider 映射和子 Agent 策略补齐。
以上是我基于 Claude Code v2.1.88 行为梳理出来的一套配置思路。模型默认值、Agent 模型配置、[1m] 支持范围这些细节后续都可能变化,所以别只记结论。理解这套分层逻辑,再结合 API 侧日志看真实消耗,才比较稳。
笔者个人见解,欢迎大家反馈自己的配置方案和实际效果。如有错误,也欢迎指正。