☰
Claude API 缓存命中率优化:4步把账单砍半的实操指南
2026/10/4 7:27:25 网站建设 项目流程

1. 为什么你的 Claude API 账单总是超出预期

用 Claude API 做应用的朋友,十个里有八个跟我抱怨过同一件事:明明感觉没发多少请求,月底账单出来却吓一跳。我刚开始接 Claude API 做批量文本处理的时候也踩过这个坑,一个晚上跑掉了几十美元,查了半天日志才发现,问题根本不在请求数量上,而在缓存命中率上。

Claude API 的计费模型里有一个非常关键但容易被忽略的机制:Prompt Caching(提示缓存)。简单说,如果你在多次请求里重复发送了相同的前缀内容(比如一大段系统提示词、一份固定的知识库文档、一套固定的输出格式说明),Anthropic 的服务器可以把这部分内容缓存起来,后续命中缓存的 token 按远低于正常输入 token 的价格计费。写这篇文章时的价格体系下,缓存写入的 token 大约是标准输入价的 1.25 倍,但缓存读取的 token 只有标准输入价的 10% 左右。这个差距意味着什么?意味着你的缓存命中率从 0% 提到 80%,同样的业务量,输入侧成本能砍掉一大半。

但现实是,大部分人根本没意识到自己在浪费钱。系统提示词每次请求都完整重发、文档内容顺序随机、时间戳塞在 prompt 开头、多轮对话没有做前缀对齐——这些操作都会让缓存彻底失效,你付的是全价,服务器那边一次缓存都没命中。更坑的是,Claude API 不会主动告诉你"这次没命中缓存",你只能从返回的 usage 字段里自己看cache_creation_input_tokens和cache_read_input_tokens这两个数字。

这篇文章就是把我自己从"月月超支"到"成本砍半"的完整过程拆开讲。我会从缓存机制的原理讲起,然后给出 4 个可落地的优化步骤,每一步都配上具体的代码改法和实测数据。不管你是刚接 Claude API 的新手,还是已经跑了一段时间但成本压不下来的老手,都能从里面找到能直接抄的作业。核心关键词就三个:Claude API、缓存命中率、计费优化,全文围绕这三个词展开,不跑题。

2. 先把缓存机制吃透,不然优化都是瞎猜

2.1 Prompt Caching 到底缓存的是什么

很多人对缓存的第一个误解是:以为缓存的是"整个请求"。不是的。Claude API 的缓存机制是前缀缓存(prefix caching),它只缓存你请求内容里从头开始的一段连续前缀。你可以把它想象成图书馆的索引卡片——如果你的请求开头 2000 个 token 和上一次请求完全一样,那这 2000 个 token 就能命中缓存;但只要第 3 个 token 变了,从第 3 个 token 往后的所有内容都得重新计算,缓存全部失效。

这个特性决定了优化的核心思路:把稳定不变的内容放在最前面,把每次都变的内容放到最后面。听起来简单,但实际操作里,很多人恰恰把顺序搞反了。

举个我踩过的真实例子。我早期做合同摘要工具,prompt 结构是这样的:

当前时间:2024-11-15 14:32:01 请对以下合同进行摘要... [合同全文 3000 字]

每次请求时间戳都在变,而且它在最前面。结果就是:后面 3000 字的合同全文,一次缓存都没命中过。后来我把时间戳挪到合同全文后面,缓存命中率直接从 0 跳到 70% 以上。就这么一个顺序调整,当月账单少了将近四成。

2.2 缓存的生命周期与最小长度门槛

缓存不是永久有效的。Anthropic 的缓存默认存活时间是5 分钟,每次命中缓存会刷新这个计时。也就是说,如果你的应用请求间隔超过 5 分钟,缓存就过期了,下次请求又得重新写入。对于高频调用的场景(比如批量处理、实时对话),这个 5 分钟完全够用;但如果你是低频调用(比如每小时跑一次定时任务),那缓存基本帮不上忙,这时候优化重点就得放在别的地方。

还有一个硬性门槛:缓存的最小长度是 1024 个 token(部分模型是 2048)。低于这个长度的前缀,即使你标记了缓存,系统也不会真的缓存。这个数字很关键——如果你的系统提示词只有 500 个 token,那你想缓存也缓存不了。解决办法是把多个稳定的内容块合并成一个足够长的前缀,比如把系统提示词、few-shot 示例、输出格式说明拼在一起,凑够 1024 token 以上。

2.3 计费差异:为什么命中率直接等于省钱

把计费逻辑摊开看,你就明白为什么值得花时间优化了。假设标准输入价格是每百万 token 3 美元(具体价格以官方为准,这里只做比例说明):

Token 类型相对价格说明
标准输入 token1x没命中缓存,全价
缓存写入 token1.25x第一次建立缓存,略贵
缓存读取 token0.1x命中缓存,便宜 90%
输出 token约 5x输出永远最贵

看这张表就清楚了:缓存读取的成本只有标准输入的十分之一。假设你每次请求有 5000 个 token 的固定前缀,跑 1000 次:

  • 完全不缓存:5000 × 1000 × 1x = 500 万 token 的全价
  • 缓存命中 90%:第一次写入 5000 × 1.25x,之后 999 次读取 5000 × 999 × 0.1x

算下来,缓存方案的成本大约是纯全价的 11% 左右。这个差距不是"省一点",是"省一个数量级"。所以缓存命中率这个指标,本质上就是你的成本杠杆,提上去就是真金白银。

2.4 哪些场景最适合做缓存优化

不是所有场景都值得折腾缓存。根据我的经验,下面这几类场景收益最大:

  • 固定系统提示词的对话应用:系统提示词越长、越固定,收益越大
  • 批量文档处理:同一套指令处理成百上千份文档,指令部分完全可缓存
  • RAG 检索增强:固定的知识库片段 + 固定的回答格式,缓存空间很大
  • 多轮对话:历史对话作为前缀,天然适合缓存,但要注意前缀对齐

反过来,如果你的 prompt 每次都是全新的、没有任何重复前缀,那缓存优化对你意义不大,重点应该放在压缩 prompt 长度上。判断标准很简单:你的请求里有没有一段内容,在多次请求中反复出现?有,就值得优化。

3. 四步优化法:把缓存命中率从 30% 拉到 90%

3.1 第一步:重构 Prompt 结构,稳定内容前置

这是最基础也最有效的一步。核心原则一句话:从前往后,稳定性递减。

我推荐的 prompt 分层结构是这样的,从上到下依次是:

  1. 系统角色定义(几乎永不变)
  2. 固定知识库 / 参考文档(长期不变)
  3. Few-shot 示例(基本不变)
  4. 输出格式约束(基本不变)
  5. 本次任务的具体指令(每次可能变)
  6. 本次任务的输入数据(每次都变)

前四层是缓存的主力,后两层是变量。关键操作是给前四层打上缓存标记。在 Claude API 里,通过cache_control参数来标记:

import anthropic client = anthropic.Anthropic(api_key="your-key") response = client.messages.create( model="claude-sonnet-4-5", max_tokens=1024, system=[ { "type": "text", "text": "你是一位资深合同分析师,擅长提取关键条款...(此处省略 2000 字系统提示)", "cache_control": {"type": "ephemeral"} } ], messages=[ { "role": "user", "content": [ { "type": "text", "text": "以下是固定知识库内容...(此处省略 3000 字)", "cache_control": {"type": "ephemeral"} }, { "type": "text", "text": f"请分析这份合同:{contract_text}" } ] } ] )

注意cache_control标记的位置——它标记的是"到这里为止的前缀可以被缓存"。所以你要把它打在稳定内容的最后一个块上,而不是变量内容上。我见过有人把cache_control打在变量内容后面,结果缓存里混进了每次都变的数据,命中率永远是 0。

提示:一个请求最多可以打 4 个缓存断点。合理利用这 4 个断点,可以把不同稳定层级的内容分开缓存,命中粒度更细。

3.2 第二步:消除前缀里的"隐形变量"

这一步是最容易被忽略、但杀伤力最大的。很多人的 prompt 结构看起来没问题,稳定内容也在前面,但命中率就是上不去。原因往往藏在细节里——前缀里混进了肉眼不易察觉的变量。

我整理了一份"隐形变量黑名单",你可以对照检查自己的 prompt:

隐形变量常见位置破坏方式修复方法
时间戳prompt 开头每秒都变挪到变量区或删掉
随机 ID / 会话 ID系统提示里每次不同移到变量区
用户昵称系统提示里每个用户不同移到变量区
动态拼接的日期格式说明里每天变用占位符替代
字典序不固定的 JSON知识库内容键顺序随机固定序列化顺序
浮点数精度不一致参考数据0.1 vs 0.10统一格式化

我印象最深的一次排查:一个朋友的 RAG 应用,知识库内容明明固定,命中率却只有 20%。查了半天发现,他每次从数据库取文档片段时用的是SELECT *,而数据库返回的字段顺序偶尔会变,导致序列化出来的 JSON 字符串前缀不一致。改成固定字段顺序后,命中率直接到 85%。这种坑,不看 usage 数据根本发现不了。

3.3 第三步:用 usage 数据做命中率监控

优化不能靠感觉,得靠数据。Claude API 每次返回的usage字段里,藏着判断命中率的全部信息:

usage = response.usage print(f"缓存写入: {usage.cache_creation_input_tokens}") print(f"缓存读取: {usage.cache_read_input_tokens}") print(f"标准输入: {usage.input_tokens}") print(f"输出: {usage.output_tokens}") # 计算本次请求的缓存命中率 total_input = (usage.cache_creation_input_tokens + usage.cache_read_input_tokens + usage.input_tokens) if total_input > 0: hit_rate = usage.cache_read_input_tokens / total_input print(f"本次命中率: {hit_rate:.2%}")

我建议你把这个监控做成常态化的,至少记录三个指标:

  • 单次命中率:cache_read / (cache_read + cache_creation + input)
  • 缓存写入频率:如果cache_creation一直很高,说明缓存老在失效重建
  • 平均命中率:按天/按小时聚合,看趋势

这里有个经验值可以参考:健康的缓存命中率应该在 70% 以上。低于 50% 说明你的前缀稳定性有问题;低于 30% 基本等于没优化。如果cache_creation_input_tokens每次都很大,那几乎可以确定是前缀里有变量在捣乱,回到第二步去查。

注意:第一次请求必然是缓存写入(cache_creation),这是正常的。判断命中率要看第二次及以后的请求。所以监控时最好排除掉每个缓存周期的首次请求。

3.4 第四步:控制缓存刷新节奏,别让缓存白白过期

缓存 5 分钟过期这个特性,决定了你的请求节奏也会影响命中率。这里分两种情况:

高频场景(请求间隔 < 5 分钟):缓存天然能续上,你只需要保证前缀稳定就行。批量任务尽量连续跑,别跑一批停半小时再跑,那样每次都要重新写缓存。

低频场景(请求间隔 > 5 分钟):缓存基本用不上,这时候有两个选择。一是合并请求,把多次小请求攒成一次大请求,减少缓存重建次数;二是接受缓存失效,把优化重点转向压缩 prompt 长度。我一般建议低频场景优先考虑合并请求,因为缓存写入本身是 1.25 倍价格,频繁重建反而更贵。

还有一个技巧:如果你的应用是多用户共享同一套系统提示,那不同用户的请求其实可以共享缓存。只要系统提示部分完全一致,用户 A 建立的缓存,用户 B 的请求也能命中。这就要求你把用户个性化的内容严格隔离到变量区,别混进系统提示里。

4. 完整实操:从改造到验证的全流程

4.1 改造前的基线测量

动手之前,先测基线。不然你改完不知道到底有没有效果。我一般会跑一个 20 次的小批量测试,记录改造前的数据:

import time def measure_baseline(client, prompts, n=20): stats = {"cache_read": 0, "cache_creation": 0, "input": 0} for i in range(n): resp = client.messages.create( model="claude-sonnet-4-5", max_tokens=512, system=OLD_SYSTEM_PROMPT, # 改造前的写法 messages=[{"role": "user", "content": prompts[i % len(prompts)]}] ) u = resp.usage stats["cache_read"] += u.cache_read_input_tokens or 0 stats["cache_creation"] += u.cache_creation_input_tokens or 0 stats["input"] += u.input_tokens time.sleep(0.5) # 控制节奏,模拟真实调用 total = sum(stats.values()) print(f"基线命中率: {stats['cache_read']/total:.2%}") return stats

跑完你会得到一个基线数字。我经手的项目里,没优化过的基线命中率普遍在 10% 到 35% 之间,很少有超过 40% 的。

4.2 逐步改造与对比

改造按前面四步走,每改一步测一次,这样能清楚知道哪一步贡献最大。根据我的实测,四步的贡献大致是这样的:

优化步骤命中率提升说明
重构 prompt 结构+30~40%贡献最大,必做
消除隐形变量+15~25%排查成本高但收益明显
加监控0(间接)让问题可见,是其他步骤的前提
控制刷新节奏+5~15%视场景而定

改造后的验证代码和基线测量一样,只是换成新的 prompt 结构。我一般会要求改造后命中率至少到 70%,达不到就继续排查。

4.3 一个真实的改造案例

拿我最近做的一个客服问答机器人举例。改造前:系统提示 1500 token,每次请求都完整重发,命中率 0%。改造过程:

第一步,把系统提示拆成"角色定义 + 产品知识库 + 回答规范"三块,全部打上cache_control,命中率到 45%。第二步,发现产品知识库里有个"当前促销活动"字段每天变,把它挪到变量区,命中率到 78%。第三步,加了 usage 监控,发现偶尔有请求命中率骤降,查出来是知识库 JSON 的键顺序不稳定,固定后命中率稳定在 88%。

最终效果:同样的业务量,输入侧成本从每月约 120 美元降到约 35 美元。这个降幅里,缓存优化贡献了绝大部分。

4.4 成本核算:优化到底省了多少

把账算清楚,你才知道这事值不值得投入时间。假设你的应用每天 5000 次请求,每次固定前缀 4000 token:

  • 优化前(命中率 20%):每天约 4000 × 5000 × (0.2×0.1 + 0.8×1) = 约 1640 万等效 token
  • 优化后(命中率 85%):每天约 4000 × 5000 × (0.85×0.1 + 0.15×1) = 约 470 万等效 token

按标准输入价折算,每天省下的成本相当可观,一个月下来就是一笔实打实的开支削减。而且这个优化是一次性的,改完结构之后长期受益,边际成本几乎为零。

5. 常见问题与排查技巧实录

5.1 命中率上不去的排查清单

遇到命中率低,按这个顺序查,基本能定位到问题:

  1. 前缀够 1024 token 吗:不够的话缓存根本不生效,先合并内容
  2. cache_control打对位置了吗:必须打在稳定内容的最后一块
  3. 前缀里有时间戳/ID 吗:有就挪走
  4. JSON 序列化顺序稳定吗:用sort_keys=True固定
  5. 请求间隔超过 5 分钟吗:超了就考虑合并请求
  6. 多用户场景系统提示一致吗:不一致就隔离个性化内容

5.2 几个我踩过的坑

坑一:以为缓存是自动的。Claude API 的缓存需要显式标记cache_control,不标记就不会缓存。我一开始以为系统会自动识别重复前缀,白等了一个月。

坑二:把变量放在中间。有次我把"用户问题"放在了系统提示和知识库之间,结果知识库部分永远命中不了。记住,变量必须放在所有稳定内容之后。

坑三:忽略输出 token 成本。缓存优化只管输入侧,输出 token 永远是最贵的。如果你的输出很长,优化输出(比如限制 max_tokens、要求简洁回答)的收益可能比缓存还大。

坑四:缓存断点打太多。虽然最多能打 4 个,但不是越多越好。断点太多会让缓存管理变复杂,命中粒度反而下降。我一般只用 1 到 2 个断点。

5.3 常见问题速查表

现象可能原因解决方向
命中率恒为 0没打 cache_control检查标记位置
命中率忽高忽低前缀有随机变量排查时间戳/ID/顺序
cache_creation 一直很高缓存频繁失效检查请求间隔和前缀稳定性
前缀够长但没缓存低于最小长度门槛合并内容凑够 1024 token
多用户命中率低系统提示不一致隔离个性化内容

6. 我个人的几点实操体会

做 Claude API 成本优化这两年,最大的体会是:缓存命中率不是一个技术指标,是一个成本意识问题。技术手段其实就那么几招,难的是养成"每次写 prompt 都想想哪些内容能缓存"的习惯。我现在的做法是,任何新 prompt 上线前,先跑 20 次测命中率,低于 70% 就不上线,逼着自己把结构调好。

另外分享一个小技巧:如果你用的是多模型切换的架构(比如同时接 Claude、其他模型做对比),缓存策略要单独设计,别指望一套 prompt 结构通吃。不同厂商的缓存机制细节不一样,Claude 的前缀缓存对顺序极其敏感,这点在跨模型场景里要特别注意。

最后说个容易被忽略的点:缓存优化和 prompt 压缩是互补的,不是二选一。缓存解决的是"重复内容别重复付费",压缩解决的是"内容本身能不能更短"。两个一起做,成本才能压到最低。我现在的项目里,固定前缀压到刚好够 1024 token 的门槛,既保证缓存生效,又不浪费长度,这个平衡点值得你花时间去找。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询