文章

Claude Code 成本调优实践:把好模型用在关键路径上

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 ID
  • opus → 查询 ANTHROPIC_DEFAULT_OPUS_MODEL
  • haiku → 查询 ANTHROPIC_DEFAULT_HAIKU_MODEL

注意:这三个 DEFAULT_* 变量是别名解析的【终点】,必须填完整 model ID,不能再套一层别名。

1.2 内置别名

除了三个家族别名,Claude Code 还内置了两个特殊别名:

别名解析结果备注
sonnetANTHROPIC_DEFAULT_SONNET_MODEL最常用
opusANTHROPIC_DEFAULT_OPUS_MODEL
haikuANTHROPIC_DEFAULT_HAIKU_MODEL
bestANTHROPIC_DEFAULT_OPUS_MODEL硬编码指向 Opus,无法单独配置
opusplanSonnet 默认,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所有子 Agenthaiku / 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 里到底哪些地方在消耗模型调用?

现在来看本文关注的主要模型消耗场景:

  1. 主循环:你的每次对话。由 ANTHROPIC_MODEL 控制,默认值取决于用户层级。这是核心资产,最值得用好模型。
  2. 子 Agent — Explore:代码搜索和文件定位。内置设置为 haiku,不需要额外配置。Claude Code 的设计者早就意识到【用重模型搜代码是浪费】。
  3. 子 Agent — Plan / General Purpose / Verification:架构设计、通用任务、验证检查。默认继承父模型(inherit),意味着主循环用 Opus 它们也跟着用 Opus。Plan Agent 值得保持较高能力,General Purpose 和 Verification 可以降级。
  4. 后台任务(共 12 个场景):主要由 ANTHROPIC_SMALL_FAST_MODEL 控制。具体包括:Token 计数、API Key 验证、离开摘要生成、API 配额检查、会话搜索、Hook Agent、Prompt Hook 默认模型、Skill 改进建议、Web 搜索分类/分发、Prompt Caching 禁用判断、Bedrock 区域路由、内部轻量查询,共 12 个场景。这些场景的共同特征:广度比深度重要,速度快比推理深重要,Haiku 级别通常够用。Claude Code 在大多数这类调用中直接使用小模型;少数场景会根据条件回退到主模型或 Sonnet。
  5. 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_MODELSonnet / Opus默认值取决于用户层级
子 Agent — Explore内置 Agent 配置(haiku)Haiku会被 CLAUDE_CODE_SUBAGENT_MODEL 覆盖
子 Agent — 其他CLAUDE_CODE_SUBAGENT_MODEL > Agent 自身配置inherit继承父模型
后台任务(共 12 个场景)ANTHROPIC_SMALL_FAST_MODELHaiku(fallback)必须用完整 ID
Plan 模式opusplan / haiku 特殊逻辑Sonnet→Opus / Haiku→Sonnet有 200K 边界条件

这里有三点很容易影响账单:

  • Explore Agent 默认已是 Haiku,搜代码不需要额外配置——这一块已经是最优解。
  • ANTHROPIC_SMALL_FAST_MODEL 覆盖了前面列出的 12 个后台场景。没设的话自动 fallback 到 Haiku 默认值。你需要确保 ANTHROPIC_SMALL_FAST_MODELANTHROPIC_DEFAULT_HAIKU_MODEL 至少有一条路径指向你的轻量模型。
  • 子 Agent 默认继承父模型。你主循环用 Opus,General Purpose 子 Agent 也会用 Opus。这里需要自己决定:是设 CLAUDE_CODE_SUBAGENT_MODEL 统一降级,还是在自定义 Agent 配置里逐个指定。

2.5 别名可以用在哪

掌握了场景层之后,别名的使用位置就很清楚了。别名(含部分修饰符组合,如 sonnet[1m])支持以下位置:

  • ANTHROPIC_MODELCLAUDE_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_MODELDEFAULT_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_MODELANTHROPIC_DEFAULT_HAIKU_MODELANTHROPIC_SMALL_FAST_MODEL 全部指向了 Sonnet,实际效果是——所有场景不分轻重,一律 Sonnet:

场景期望模型实际模型
主循环SonnetSonnet
Explore 搜代码HaikuSonnet
后台轻量任务Haiku / FlashSonnet
其他子 Agentinherit / SonnetSonnet

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。

六、调优验证

配完怎么确认?几个方法:

  1. Claude Code 启动日志。 启动 Claude Code 时会打印当前主模型名称。
  2. /model 命令。 显示当前主循环模型和可选列表,能看到别名解析到了哪个具体 model ID。
  3. 观察子 Agent。 触发一次 Explore(让 Claude Code 搜个函数定义),看响应速度和风格。如果 Explore 的回复推理很深、很详尽,可能没走 Haiku——检查 CLAUDE_CODE_SUBAGENT_MODEL 是否设了值。
  4. 网关侧日志。 在 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_MODELCLAUDE_CODE_SUBAGENT_MODEL、Skill 的 model 配置里优先用别名。切换模型时只改映射层环境变量,少改业务配置。

第三,确保轻量模型路径明确指向轻量模型。 不管是通过 ANTHROPIC_DEFAULT_HAIKU_MODEL 还是 ANTHROPIC_SMALL_FAST_MODEL,后台任务都不应该悄悄烧主模型。

速查表:

方案主循环子 Agent后台任务适用
A 全部压平别名最终都指向重模型调试、CI
B 省钱方案Sonnet继承 + Explore=HaikuHaiku(默认)日常开发
C 性能方案OpusSonnet(含 Explore)Haiku(默认)架构重构
D 激进省钱Sonnet统一 HaikuHaiku极致省钱

模型分层这件事的道理其实很朴素:不同任务对【能力】的需求就是不一样,没必要用大炮打蚊子。

Claude Code 已经帮你做了一部分优化,比如 Explore 默认 Haiku、后台 fallback 到轻量模型。我们要做的不是推翻它,而是顺着这套设计,把 provider 映射和子 Agent 策略补齐。

以上是我基于 Claude Code v2.1.88 行为梳理出来的一套配置思路。模型默认值、Agent 模型配置、[1m] 支持范围这些细节后续都可能变化,所以别只记结论。理解这套分层逻辑,再结合 API 侧日志看真实消耗,才比较稳。

笔者个人见解,欢迎大家反馈自己的配置方案和实际效果。如有错误,也欢迎指正。

参考

本文由作者按照 CC BY 4.0 进行授权