成本优化

Claude 提示词缓存成本指南:写入、读取与命中率怎么计算

理解 Claude 缓存写入、读取、有效期与路由影响,计算盈亏平衡点,并用可复现的 Python 脚本验证缓存及实际计费。

缓存稳定前缀,再测量复用情况

Claude 提示词缓存复用的是已处理的提示词前缀,而非保存一份答案。每次调用仍会生成响应,输出 Token 仍需付费。应把稳定的指令、工具定义和参考材料放在会变化的用户输入之前,再根据前缀在有效期内的复用频率决定是否缓存。

本文以 Sonnet 4.6 和 20,000 Token 前缀演示计算,不把示例当作实际命中率。选择供应商见价格比较;客户端接入见Claude Code 配置指南。

技术行为和外部价格于 2026 年 10 月 17 日依据官方缓存文档核查。以下单价和最小长度仅针对 Sonnet 4.6,不能直接套用到所有 Claude 模型。

区分写入、读取和普通输入

Anthropic 格式响应中的 input_tokens 表示未缓存输入,cache_creation_input_tokens 表示新写入缓存的输入,cache_read_input_tokens 表示复用的输入,三类应分开计算。若提供 cache_creation.ephemeral_5m_input_tokens 和 cache_creation.ephemeral_1h_input_tokens,它们是总写入量按 TTL 拆分的明细,不要把明细和总数重复相加。

Sonnet 4.6 官方全球 API:美元/百万 Token
类别单价预算含义
普通输入$3.00未被读取缓存或写入缓存的输入
5 分钟写入$3.75首次写入比普通输入更贵
1 小时写入$6.00更长有效期对应更高写入成本
缓存读取$0.30命中部分按读取单价付费
输出$15.00提示词缓存不会免除输出费用

来源为官方标准全球价格。API Route 请查看当前模型与分组价格,同时核对两种写入时长;上游的价格倍数不能替代你实际使用的供应商报价。

根据请求间隔选择 5 分钟或 1 小时

默认 ephemeral 缓存有效期为 5 分钟,命中会刷新有效期。支持时,可用 cache_control: {"type":"ephemeral","ttl":"1h"} 指定 1 小时。Sonnet 4.6 文档给出的最小可缓存前缀为 1,024 Token。内容匹配和长度足够只是前提,不保证经过任意网关都会命中。

估算前先记录请求间隔。如果工作流会暂停 20 分钟,5 分钟缓存可能反复写入;连续活跃的对话则可能复用较短缓存。只有预期复用足以抵消额外写入成本时,才值得选长 TTL。即使尚未过期,前缀变化也可能产生冷请求。

在应用允许的情况下,把日期、请求 ID 和用户变化内容放在稳定前缀之后,保持工具定义及其顺序一致。生产环境不应为了达到阈值随意填充文本;下面的测试使用生成文本,只为便于观察行为。

计算盈亏平衡点与 Token 读取占比

设 P 为前缀 Token 数、N 为使用次数、W 为写入单价、R 为读取单价、I 为普通输入单价,价格单位均为美元/百万 Token。一次写入、N−1 次命中的前缀成本为 P × (W + (N−1)R) / 1,000,000,无缓存成本为 N × P × I / 1,000,000。完整账单还要加上每次独有的输入和输出。

20,000-token prefix, ten uses, Sonnet 4.6 direct rates:
Uncached: 10 * 0.02 * 3 = $0.6000
5m: 0.02 * (3.75 + 9 * 0.30) = $0.1290
1h: 0.02 * (6.00 + 9 * 0.30) = $0.1740

5m, two uses: $0.0810 vs $0.1200 uncached
1h, two uses: $0.1260 vs $0.1200 uncached
1h, three uses: $0.1320 vs $0.1800 uncached

前缀大小不同时,按 Token 而非请求次数计算更合适。可定义 读取占比 = read_tokens / (uncached_input_tokens + write_tokens + read_tokens),表示全部输入中由缓存提供的比例。如果另外统计了可缓存前缀 Token,也可以计算前缀命中率,但应注明分母,两者回答的问题不同。

在没有新增输入后缀、前缀等长且每次未命中都会写入的假设下,平均单价为 hR + (1−h)W。代入本文官方 Sonnet 4.6 单价,5 分钟缓存的 h 超过 21.74% 才比普通输入便宜,1 小时缓存需要超过 52.63%。这些是数学门槛,不是实测命中率;单价或计费规则改变时必须重算。

用两个请求复现缓存测试

把下面的 Python 3 标准库脚本保存为 cache_probe.py。它对固定系统前缀设置明确缓存断点,顺序发送两次完全相同的 Messages 请求。两次请求都可能计费,初次测试不要使用机密内容。

设置目标路由的 ANTHROPIC_BASE_URL 和 ANTHROPIC_MODEL。API Route 使用 https://global.api-route.com、已启用的 Claude 模型及私密的 ANTHROPIC_AUTH_TOKEN;官方直连使用 https://api.anthropic.com 和 ANTHROPIC_API_KEY。两种凭据变量只能设置一种。终端配置示例展示了如何隐藏密钥输入。

import json
import os
import urllib.request
import urllib.error

base = os.environ.get("ANTHROPIC_BASE_URL", "https://global.api-route.com").rstrip("/")
model = os.environ.get("ANTHROPIC_MODEL", "claude-sonnet-4-6")
token = os.environ.get("ANTHROPIC_AUTH_TOKEN")
api_key = os.environ.get("ANTHROPIC_API_KEY")
if bool(token) == bool(api_key):
    raise SystemExit("Set exactly one of ANTHROPIC_AUTH_TOKEN or ANTHROPIC_API_KEY")

# Fixed, non-sensitive prefix, comfortably above Sonnet 4.6's minimum.
# Use the same bytes on both requests; no timestamps or random IDs.
prefix = "Follow the project rules below.\n" + "\n".join(
    f"Rule {i}: Validate inputs, document assumptions, and keep errors actionable."
    for i in range(400)
)
payload = {
    "model": model,
    "max_tokens": 32,
    "system": [{"type": "text", "text": prefix,
                "cache_control": {"type": "ephemeral"}}],
    "messages": [{"role": "user", "content": "Reply with OK."}],
}
headers = {"Content-Type": "application/json", "anthropic-version": "2023-06-01"}
headers.update({"Authorization": f"Bearer {token}"} if token else {"x-api-key": api_key})
for attempt in (1, 2):
    req = urllib.request.Request(base + "/v1/messages",
        data=json.dumps(payload).encode(), headers=headers, method="POST")
    try:
        with urllib.request.urlopen(req, timeout=120) as res:
            result = json.load(res)
    except urllib.error.HTTPError as exc:
        # Error bodies can contain provider details; inspect them privately.
        raise SystemExit(f"HTTP {exc.code}; inspect provider logs privately") from None
    usage = result.get("usage", {})
    print(json.dumps({"attempt": attempt, "model": result.get("model"), "usage": usage}))
    if "cache_read_input_tokens" not in usage or "cache_creation_input_tokens" not in usage:
        print("Cache fields absent: result is inconclusive; check provider billing logs.")
python cache_probe.py

检查两份实际 usage:第一次有正的缓存写入、第二次有正的缓存读取,才说明这条路由当时发生了复用。如果前一次测试已经预热,第一次请求也可能命中。不要预先断言具体 Token 数或缓存结果。

将响应中的用量与实际扣费对账

  1. 记录模型、路由或分组、时间、返回的 Token 分类及可用的请求 ID,避免把凭据写入记录。
  2. 在调用日志找到对应请求,分别乘以该路由的单价,与已结算金额对照,并考虑文档说明的舍入方式。
  3. 之后自行等待一个明确超过 TTL 的空闲间隔,再运行脚本观察是否重新写入;脚本本身不等待,也不会无限循环调用。
  4. 修改系统前缀后再测试冷请求,固定模型与路由,便于解释差异。
  5. 如需无缓存基线,移除 cache_control 后发送相同提示词,但仍要检查 usage:网关可能自行启用缓存。日志确认符合条件后,才能把它视作基线。

缺少缓存字段不能得出明确结论。读取为零可能源于前缀太短、过期、内容变化、路由切换、不支持缓存控制,或者协议转换遗漏字段。应让供应商按具体请求对账,不能把字段缺失理解为免费输入或缓存必然失效。

路由变化为什么可能降低复用

回退可能把下一次请求送到另一个上游缓存。不要假定不同供应商、账户或端点共享已写入缓存。切换路由可能有助于可用性,同时增加冷前缀成本,因此要统计重试和回退后的总扣费。

OpenRouter 文档说明了感知缓存的粘性路由及回退、供应商顺序的限制,不能据此推断其他服务也采用同一机制。对 API Route,应使用同一密钥、模型和选定路由做重复测试,再检查实际缓存计费;本文不承诺缓存亲和性或命中率。

做生产决策时,应采样常见会话长度与空闲间隔,汇总整个样本的普通输入、读取、写入和输出费用,也计入产生费用的失败请求,再与定义清楚的基线比较。即使命中占比较高,输出密集型任务也可能仍然昂贵。

可先创建 API Route 账户测试代表性提示词,查看当前价格,并按快速入门验证首个请求。日志确认符合预期后,再为持续用量充值。