为什么 Claude + OpenCode 完全没有缓存、费用暴增?

很多用户在把 OpenCode 接入 Claude 模型后,会发现一个很反直觉的问题:

同样是写代码、读项目、跑 Agent,Claude Code 的缓存命中率很高,但 OpenCode + Claude 可能几乎没有缓存,账单突然暴涨。

这不是玄学,核心原因是:Claude 的缓存机制、OpenCode 的提示词组织方式、以及 OpenAI 兼容网关的协议转换之间发生了错配。

一句话结论

OpenCode + Claude 费用暴增,通常不是 Claude 模型本身“突然变贵”,而是缓存没有真正生效。

常见原因有三个:

  1. Claude 需要请求里明确启用 cache_control
  2. OpenCode 可能没有正确插入或维护 Claude 缓存断点。
  3. 如果通过 /v1/chat/completions 这类 OpenAI 兼容接口中转 Claude,网关通常无法自动猜出应该在哪里加 Claude 缓存标记。

结果就是:每次请求都把系统提示词、项目上下文、工具说明、历史对话重新按完整输入 Token 计费。

社区观察

Linux.do 相关讨论中,有用户提到:

  • OpenCode 的 cache 写入量很低,不足总输入的十分之一。
  • Claude Code 的 cache 写入量可以长期保持在较高水平。
  • 也有人反馈,即使用 Codex 接入 OpenCode,缓存率也很低,说明问题不只在模型,也可能在工具的上下文组织和缓存适配上。

参考讨论:

  • https://linux.do/t/topic/1548049/8

OpenAI 为什么更容易有缓存?

OpenAI 的 Prompt Caching 是自动机制。

只要提示词足够长,并且前缀保持一致,OpenAI 后端会自动尝试复用缓存。开发者通常不需要额外写缓存标记。

你可以在响应日志里看这个字段:

{
  "usage": {
    "prompt_tokens_details": {
      "cached_tokens": 1920
    }
  }
}

如果 cached_tokens 很高,说明大量输入前缀被缓存复用了。

这也是 GPT-5.5 + Codex 这类组合容易省钱的原因。Codex 通常会把稳定内容放在前面,例如:

  • 系统指令
  • 工具说明
  • 项目规则
  • 不变的仓库上下文

然后把动态内容放在后面,例如:

  • 用户当前问题
  • 本轮工具结果
  • 临时文件片段

这正好符合 OpenAI 的缓存规则:稳定前缀越一致,缓存越容易命中。

OpenAI 官方文档也建议把静态内容放在提示词开头,把动态内容放在末尾,并通过 cached_tokens 观察缓存命中情况。

参考:

  • https://developers.openai.com/api/docs/guides/prompt-caching

Claude 为什么不一样?

Claude 的 Prompt Caching 不是简单的“什么都不用管”。

当前 Claude 文档支持通过 cache_control 启用缓存。常见写法是:

{
  "cache_control": {
    "type": "ephemeral"
  }
}

或者在特定内容块上设置缓存断点:

{
  "type": "text",
  "text": "这里是稳定的系统提示词、项目背景或工具说明",
  "cache_control": {
    "type": "ephemeral"
  }
}

Claude 的响应里通常看这些字段:

{
  "usage": {
    "input_tokens": 50,
    "cache_creation_input_tokens": 12000,
    "cache_read_input_tokens": 0
  }
}

含义大致是:

  • cache_creation_input_tokens:本次写入缓存的 Token。
  • cache_read_input_tokens:本次从缓存读取的 Token。
  • input_tokens:未走缓存的普通输入 Token。

如果你多次请求后仍然看到:

{
  "cache_creation_input_tokens": 0,
  "cache_read_input_tokens": 0
}

那就基本说明:缓存没有生效。

Anthropic 官方文档也明确说明,缓存有最小长度要求,并且需要检查 cache_creation_input_tokenscache_read_input_tokens 来验证缓存是否真的生效。

参考:

  • https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching

OpenCode + Claude 为什么容易出问题?

原因一:OpenCode 可能没有为 Claude 写入 cache_control

如果工具底层只是把上下文拼成普通对话,然后发给 Claude,但没有加:

"cache_control": {
  "type": "ephemeral"
}

Claude 就不会按你预期缓存那段长上下文。

这和 OpenAI 不同。OpenAI 是自动缓存,Claude 需要工具或网关明确表达缓存意图。

原因二:OpenAI 兼容接口不天然支持 Claude 缓存语义

很多用户为了方便,会让 OpenCode 走 OpenAI 兼容接口:

/v1/chat/completions

这类请求体通常长这样:

{
  "model": "claude-opus-...",
  "messages": [
    {
      "role": "system",
      "content": "..."
    },
    {
      "role": "user",
      "content": "..."
    }
  ]
}

问题是,标准 OpenAI 请求体里没有 Anthropic 的 cache_control 语义。

如果中转网关只是把 OpenAI 格式转换成 Claude 格式,它也很难凭空知道:

  • 哪一段是稳定系统提示词?
  • 哪一段是项目上下文?
  • 哪一段应该作为缓存断点?
  • 哪些动态工具结果不能放进缓存前缀?

所以,除非网关专门支持 Claude 缓存字段透传或自动插入,否则缓存很容易丢失。

原因三:提示词前缀被动态内容污染

即使工具加了缓存控制,缓存也要求前缀稳定。

下面这些内容如果被放在前缀里,会破坏缓存:

  • 当前时间戳
  • 每轮变化的工具调用结果
  • 临时文件列表
  • 随机排序的 JSON 字段
  • 动态生成的系统说明
  • 每轮都变化的项目扫描结果

结果就是:看起来你每次都在发“同一个项目上下文”,但模型看到的前缀其实每次都不同,缓存自然命不中。

为什么费用会暴增?

代码 Agent 的输入通常很大。

一次请求可能包含:

  • 长系统提示词
  • 工具定义
  • 项目目录结构
  • 相关文件内容
  • 历史对话
  • 最近的工具执行结果

如果有缓存,后续请求可能只需要为少量新增内容付普通输入 Token 费用。

如果没有缓存,每次都相当于重新发送几万甚至几十万 Token。

所以费用差距不是 10% 或 20%,而可能是数倍甚至一个数量级。

Claude 官方缓存价格机制里,缓存读取 Token 通常远低于普通输入 Token。也就是说,缓存命中率一旦从 90% 掉到接近 0%,账单会非常明显地上升。

怎么确认自己有没有中招?

看日志,不要只看感觉。

OpenAI / GPT 系列

重点看:

usage.prompt_tokens_details.cached_tokens

如果多轮请求后 cached_tokens 长期为 0,说明没有命中缓存。

Claude 系列

重点看:

usage.cache_creation_input_tokens
usage.cache_read_input_tokens
usage.input_tokens

可以粗略计算缓存读取占比:

缓存读取占比 =
cache_read_input_tokens /
(cache_read_input_tokens + cache_creation_input_tokens + input_tokens)

如果 cache_read_input_tokens 长期接近 0,说明后续请求没有有效复用缓存。

用户应该怎么做?

1. 不要默认认为 OpenCode 已经支持 Claude 缓存

只改 base_urlapi_key,不代表 Claude 缓存已经启用。

你需要确认:

  • OpenCode 当前版本是否支持 Claude prompt caching。
  • 请求体里是否真的带了 cache_control
  • 中转网关是否会保留或生成 Claude 缓存字段。
  • 控制台日志里是否出现 cache_read_input_tokens

2. 优先使用支持 Claude 缓存的工具链

如果你主要使用 Claude 做代码 Agent,建议优先选择明确支持 Claude 缓存机制的工具。

比如:

  • Claude Code
  • 支持 Claude cache breakpoint 的客户端
  • 能透传 Anthropic 原生 /v1/messages 请求的网关
  • 明确支持 cache_control 的中转配置

3. 尽量走 Claude 原生协议

如果条件允许,Claude 模型优先使用 Anthropic 原生格式,而不是强行伪装成 OpenAI 格式。

OpenAI 兼容接口很方便,但它不一定能表达 Claude 的所有能力。缓存就是典型例子。

4. 把稳定内容放前面,动态内容放后面

无论 OpenAI 还是 Claude,都建议这样组织上下文:

稳定内容放前面:

  • 系统提示词
  • 项目规范
  • 编码规范
  • 工具说明
  • 固定仓库摘要
  • 长期不变的文档

动态内容放后面:

  • 当前用户问题
  • 本轮工具结果
  • 临时错误日志
  • 当前 diff
  • 刚读取的文件片段

5. 给编程工具单独创建 API Key

建议为 OpenCode、Claude Code、Codex、Cursor、Windsurf 等工具分别创建 API Key。

这样你可以单独观察:

  • 哪个工具消耗最高
  • 哪个模型缓存命中最低
  • 哪个 Token 组更适合当前任务
  • 是否某个工具在重复发送超大上下文

6. 在控制台核对 Token 组和缓存策略

不同 Token 组的模型来源、缓存策略、价格费率和可用线路可能不同。

如果你使用 AIOAGI,请在控制台确认:

  • 当前 API Key 属于哪个 Token 组
  • Token 组是否支持目标 Claude 模型
  • 是否有缓存优惠说明
  • 日志里是否能看到缓存读写字段
  • 实际扣费是否符合预期

推荐排查顺序

如果你已经遇到 Claude + OpenCode 费用异常,可以按这个顺序查:

  1. 查看控制台请求日志。
  2. 确认 cache_read_input_tokens 是否长期为 0。
  3. 确认请求是否经过 /v1/chat/completions 这类 OpenAI 兼容路径。
  4. 检查网关是否支持 Anthropic cache_control
  5. 检查 OpenCode 是否有 Claude 缓存相关配置。
  6. 对比同一项目在 Claude Code 中的缓存命中情况。
  7. 如果 OpenCode 无法改善,换用支持缓存的客户端或切换模型/分组。
  8. 为 OpenCode 单独设置额度上限,避免继续烧余额。

常见误区

误区一:只要模型支持缓存,就一定会自动省钱

不一定。

OpenAI 的缓存更偏自动,Claude 需要请求正确表达缓存意图。工具没有适配,缓存就可能完全不生效。

误区二:第一次请求很贵,说明缓存没用

第一次请求通常是写入缓存,费用可能不会低。真正省钱的是后续多次读取缓存。

要看第二次、第三次之后的 cache_read_input_tokens

误区三:用了中转站就一定能自动处理缓存

不一定。

中转站能做协议转换,但不一定能理解你的提示词结构。它不知道哪一段应该缓存,哪一段不应该缓存。

误区四:缓存会影响模型回答质量

Prompt caching 缓存的是输入前缀的计算结果,不是缓存答案。命中缓存后,模型仍会根据当前完整请求生成新回答。

总结

Claude + OpenCode 完全没有缓存、费用暴增,本质上是工具链没有把 Claude 的缓存机制用起来。

OpenAI 系列通常更容易自动命中缓存;Claude 则需要客户端、网关和提示词结构一起配合。

如果你是普通用户,最实用的建议是:

  • 用日志确认缓存,不要凭感觉。
  • Claude 编程任务优先使用支持缓存的工具。
  • 不要盲目把 Claude 塞进 OpenAI 兼容接口就直接长时间跑。
  • 给编程 Agent 单独建 API Key,并设置额度上限。
  • 发现 cache_read_input_tokens 长期为 0,就立刻停下来排查。

缓存命中率不是小优化。对长上下文代码 Agent 来说,它直接决定你是在正常使用,还是在按全量上下文反复付费。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐