
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 分层结构是这样的从上到下依次是系统角色定义几乎永不变固定知识库 / 参考文档长期不变Few-shot 示例基本不变输出格式约束基本不变本次任务的具体指令每次可能变本次任务的输入数据每次都变前四层是缓存的主力后两层是变量。关键操作是给前四层打上缓存标记。在 Claude API 里通过cache_control参数来标记import anthropic client anthropic.Anthropic(api_keyyour-key) response client.messages.create( modelclaude-sonnet-4-5, max_tokens1024, 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, n20): stats {cache_read: 0, cache_creation: 0, input: 0} for i in range(n): resp client.messages.create( modelclaude-sonnet-4-5, max_tokens512, systemOLD_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 命中率上不去的排查清单遇到命中率低按这个顺序查基本能定位到问题前缀够 1024 token 吗不够的话缓存根本不生效先合并内容cache_control打对位置了吗必须打在稳定内容的最后一块前缀里有时间戳/ID 吗有就挪走JSON 序列化顺序稳定吗用sort_keysTrue固定请求间隔超过 5 分钟吗超了就考虑合并请求多用户场景系统提示一致吗不一致就隔离个性化内容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 的门槛既保证缓存生效又不浪费长度这个平衡点值得你花时间去找。