最近接连有好几个朋友问我:Claude Prompt 缓存到底能不能帮企业把 API 成本压下来?我的答案是能,但前提是你把这件事当成一项工程来做,而不是在某个请求上加一个缓存参数就当完事,更不是把官方的机制说明读一遍就觉得自己会了。这篇文章不讲概念,直接按我自己落地项目的实际经验来写,从前缀复用的设计一路讲到接入层治理,把哪些 token 在降价、哪些请求根本命中不了、网关该怎么约束各个业务线,一次说清楚。适合那些已经跑起来一定量的请求、开始认真算账单的后端团队、API 平台负责人,还有负责成本优化的技术管理者看。
我自己见过太多类似的场景:某个业务线听说了缓存能省钱,立刻往消息体里塞了 cache_control,上线一看账单,怎么没便宜多少?再一看监控,命中率不到百分之十。问题基本不在缓存本身,而在请求的“前缀稳定性”和“接入方式”上。缓存只认一个东西:从头开始、连续、完全一致的 token 序列。这也就决定了,省钱的功课一大半在 Prompt 工程,另一半在接入治理。下面按我实际推进的顺序来说。
1. 省钱的本质:缓存命中之后,哪个环节在变便宜
想把这笔账算明白,得先弄清楚一次 Claude 请求的账单由哪些部分构成。很多人的第一反应是“输出 token 贵,所以少输出点”,但真正业务场景里,输入 token 的量通常远大于输出。一条客服回答可能只有两三百 token,但前置的系统提示词、企业知识库、历史会话、少样本示例,加起来动辄几千甚至几万 token。Prompt 缓存对成本的改善,恰恰就是发生在输入侧。
1.1 一次请求的账单里其实有四种 token
在缓存机制下,一个请求的 usage 反馈通常会包含几个不同性质的数字。我用最常见的字段名来示意:
| 字段 | 含义 | 费用性质 |
|---|---|---|
| input_tokens | 本次请求实际进入计算的普通输入 | 按普通输入单价计费 |
| cache_creation_input_tokens | 本次写入缓存的 token | 通常比普通输入单价更高一点 |
| cache_read_input_tokens | 本次命中的缓存读取 token | 通常比普通输入单价低一个数量级 |
| output_tokens | 生成结果的输出 | 无缓存概念,按输出单价计费 |
我第一次看到这个结构时,脑子里冒出来的念头是:这不相当于把“读操作”和“写操作”分开计费吗?没错。缓存命中的本质,就是服务端提前把重复出现的输入前缀以“近似只读”的方式保存了一段时间,后续请求不需要重新计算整段内容,只需要读取这部分缓存的运算结果。于是读取单价被大幅拉低。
这里有个关键点容易被忽略:第一个请求并没有省钱,反而可能比不缓存更贵一点,因为它要承担缓存写入那部分上浮费用。省钱的收益来自后续一段时间内的一次次命中。所以如果某个请求只调用一次,那开不开缓存基本没有意义;只有当相同前缀被大量重复使用时,缓存才有明显的成本优势。
1.2 TTL 和命中窗口:缓存不是永久会员
缓存不是写进去就永远存在。服务端会给缓存设置一个有效期,常见的时间窗口按分钟到小时来定。TTL 到期之后,缓存内容被清掉,下一次相同请求又得重新走“写入缓存”的路径。
这个机制听起来简单,但很多企业恰恰栽在 TTL 上。我见过一个内部工具,每次调用之间隔了两个小时,而团队设置的缓存 TTL 是五分钟。结果就是:每次都是缓存写入,命中率无限趋近于零,费用反而比不用缓存更贵。后面我把这部分请求单独挑出来,调整了 TTL,短期内立刻看到命中数字往上走。
TTL 的选择没有一个万能值。更合理的思路是按业务频率倒推:如果某个场景平均每五分钟就会被调用一次,那 TTL 设置成十五分钟甚至一小时都很安全;如果调用频率本身就是小时级,那就别把省钱希望寄托在短 TTL 上。
1.3 为什么第一个请求更贵,但整体仍然划算
很多人一看到“缓存写入比普通输入贵”就慌了,觉得这是不是一种变相涨价。其实账要这样算:缓存写入只发生在前缀首次出现或过期后的第一次请求上,之后只要前缀稳定,每笔请求读取的都是低价缓存结果,而不再需要按昂贵单价重算。
用生活里的类比就是:第一次把家具搬进仓库,要付一笔搬运费;之后只要家具还放在老位置,每次取用只付一个很低的保管费。如果你反复存取的是同一批家具,长期下来当然划算。Prompt 缓存的企业价值,就是把“每次请求都重新算一遍几万 token”变成“第一次算一遍,后面只做命中读取”。
所以我给团队的第一条原则是:不要盯着单次请求看省钱,要看一段时间内的综合账单。缓存是给稳态流量用的,不是给一次性请求用的。
2. 前缀工程化:稳定内容置前,变化内容往后放
理解了缓存只认“从头开始的连续前缀”,第二阶段就是把它落实到 Prompt 设计上。这一部分我称之为前缀工程化。它不是写一段漂亮 Prompt,而是把一份请求里的所有内容模块化、定序化,让真正昂贵且重复的部分稳定地出现在前面。
2.1 Prompt 顺序为什么就是缓存区的边界
缓存命中检测的是“从消息数组开头开始的一段连续内容”。如果你的消息前面几段都一样,后面才开始变化,那么变化不会破坏前面的命中。反过来,如果变化发生在前面,哪怕只是插入一个时间戳,从插入点开始到结尾的部分全部失效,因为缓存区要求前缀连续。
这也是团队最容易犯的错:习惯性地在系统提示词里拼接当前日期、用户名称、会话状态之类的动态信息。结果每个请求开头都不一样,缓存命中等于零。解决办法不是禁止所有动态信息,而是把动态内容往后挪,或者干脆不放进缓存前缀里。
比如一个客服机器人,请求结构如果是这样:
第 1 段:企业内部客服规范(固定) 第 2 段:商品知识库(固定) 第 3 段:历史对话片段(动态) 第 4 段:用户当前问题(动态)那前面的固定内容占据了绝大多数 token,动态内容只占尾部。每次请求都可以命中同一个长前缀,省钱效果立刻体现。如果反过来,把“当前时间”拼进第一段,整条缓存就失去了意义。
2.2 落地模板:知识库、系统词、少样本应该怎么排
我一般建议团队把请求内容拆成几个明确模块,每个模块都有自己的稳定性和变更频率。
| 模块 | 内容示例 | 是否适合进缓存前缀 |
|---|---|---|
| 系统角色设定 | 你是客服助手,必须遵循以下规范 | 适合 |
| 企业知识库 | 产品手册、售后政策、FAQ | 适合 |
| 少样本示例 | 标准问答对、格式示例 | 适合 |
| 会话摘要 | 用户历史意图摘要 | 视情况 |
| 当前用户输入 | 用户刚发的一句话 | 不适合或放最后 |
少样本示例是很多人会忽略的缓存资产。有些业务要依赖两三百行示例来约束输出格式,这部分内容极长、极少变化,天然适合放在缓存前缀里。值得注意的是,示例一旦被项目成员随手改掉,哪怕只是多了一个换行,也会造成后续所有请求重新写入缓存。所以示例要像代码一样做版本控制,不能手改了事。
知识库的处理更有讲究。完整把几百页文档塞进每次请求不现实,通常会先经检索系统筛出相关片段。这时要注意:检索结果的拼接顺序必须稳定,不要按分数排序后每次变化,最好使用固定的知识库章节顺序来拼接。否则同一段知识今天排第三、明天排第五,前缀很容易被打散。另一个实践是给每个知识库片段加固定的标题和分隔符,让它作为一段结构化的稳定文本出现在消息里。
2.3 版本管理:模板变更会全量失效
前缀工程化里最容易被低估的是模板版本管理。团队只要修改了系统提示词,哪怕只是一句话,缓存前缀就会整体变化,下一次请求需要重新写入。这是正常的,不算设计缺陷。问题在于有些团队根本没有“Prompt 版本”的概念,改一个字就上线,改完之后也没人观察命中率。
我自己的做法是为每套 Prompt 模板维护一个版本号,版本号要么不进入请求内容,要么放在缓存前缀之外的位置。如果一定要让模型知道当前版本,我会把它放在动态段里,避免版本号变化导致整段缓存失效。同时,每次模板变更后都要主动对比变更前后的命中率曲线,如果命中率出现悬崖式下跌,那基本可以确认是模板变动带来的缓存冷启动。
有一点我想特别提醒:不要为了命中率而死活不改 Prompt。业务效果永远是第一位的,如果确实需要优化提示词,那就接受重新写入的成本,但要有意识地批量切换,而不是让不同请求零散地使用不同版本。等等,尽量一次性全量切到新模板,让新缓存尽快“热”起来。
3. 接入治理第一步:让所有业务线都走同一条带规范的路
前缀工程化解决的是“请求内容长什么样”的问题。但企业里如果同时有多个业务线、多个后端服务,每个人直接调 SDK,那内容规范很快就会失控。接入治理要做的事情,是从组织机构上保证“缓存友好的结构”成为默认路径,而不是依赖某个工程师的自律。
3.1 各自调 SDK 的后果
我见过一个真实场景:同一家公司里,客服团队、分析团队、内容团队各自搭了一套调用服务。客服团队规范地维护长前缀,分析团队则把动态查询参数拼在最前面,内容团队甚至直接在代码里把时间戳写进系统提示词。最后成本数据混在一起,谁都说不清为什么命中率整体上不去。
问题的根源不在这几个团队的代码水平,而在缺少一个统一的接入层。只要每个团队都能绕过公共规范直接调用,前缀设计就变成了一件可做可不做的事情。一旦组织变大,这件事必然走向失控。所以接入治理的第一步,是把“业务方直连 API”改成“业务方连接内部网关或统一代理”。
3.2 网关核心校验:不该放前面的参数,一律拦下来
我做接入治理时并不追求网关能自动生成最优 Prompt,那不是网关该干的事。网关的核心职责有两块:第一,强制请求通过统一模板组装;第二,对动态字段做白名单校验。
先说强制模板。业务方提交给网关的请求不应该是一堆散落的字符串,而应该是一个或者几个“业务场景标识”。网关根据场景标识,从内部的 Prompt 仓库拉取对应模板,自动把固定知识库、系统规范、少样本示例组装到前缀区,再把业务方传入的动态内容放在尾部。这样业务方不需要关心缓存边界,他们只需要说“我这里是客服场景,用户问题是 XXX”。
动态字段白名单的意思是说,网关要能识别请求里的哪些参数会被插入到固定模板之前。如果业务方试图传入一个“timestamp”或者“request_id”并且要求放到开头,网关应该直接拒绝,或者自动把这些字段剥夺出前缀区。这一步看着简单,但能拦住大多数“看似无心”的缓存污染。
3.3 一个可落地的网关配置样例
我给出一个简化版的配置结构,不是某个具体产品的配置文件,而是我想表达的最小控制面长什么样:
{ "scenarios": { "customer_service_v2": { "template": "prompt_templates/cs_v2.json", "cache_ttl": "1h", "prefix_required": true, "allowed_dynamic_fields": ["user_query", "session_summary"] } }, "rules": { "forbid_order_first_fields": ["timestamp", "request_id", "date"], "max_input_tokens": 20000 } }业务方发起请求时,只需要传入scenario: customer_service_v2和user_query。网关自行拼出最终的消息结构,并确保timestamp、request_id这一类字段不会混入前缀区。如果业务方非要传date,网关会把它放到底部,或者忽略掉。
这个网关可以是独立的服务,也可以是一个封装好的 SDK 客户端,甚至可以是服务网格里的一段拦截逻辑。具体形态不限制,但一定要做到“无法绕过”。只要有人能拿着原始 API Key 绕过网关直连,它就只是建议书而不是治理。实践中我会把 API Key 管理收回到网关层,业务侧只持有网关生成的内部凭证,这样从根上杜绝了绕过。
4. 接入治理第二步:用量、命中率、费用预算一起盯住
治理的目标不是上一套网关就结束,而是让成本变化可观测、可预警、可归因。没有指标的接入治理,最后还是会退化成“凭感觉优化”。下面这几个指标是我每次落地项目都会优先看的东西。
4.1 从 usage 字段里抽三个数字
每个请求返回的 usage 都会带几个关键数字。建议在网关层统一记录,而不是让各个业务方各自打日志。统一记录的好处是方便跨业务对比,也方便发现异常波动。
我一般会为每个请求记录:
- 普通输入 token:
input_tokens - 缓存写入 token:
cache_creation_input_tokens - 缓存读取 token:
cache_read_input_tokens - 输出 token:
output_tokens
字段名可能随不同版本的 SDK 有差别,但核心含义不会变。某次排查时我发现团队有人在日志里只记了input_tokens和output_tokens,结果所有请求看起来都是满额计算,缓存命中率像是不存在一样。后来把三个输入相关数字分开记录,才看清真实情况。
4.2 命中率与有效成本公式
计算命中率时,要处理一个容易被误解的地方:不要把缓存读取单独除以总输入 token。因为普通输入、缓存写入、缓存读取三者的单价不同,简单占比会误导人。
我用两个指标来观察:
- 缓存读取占比 = cache_read_input_tokens / (input_tokens + cache_creation_input_tokens + cache_read_input_tokens)
- 预估有效输入成本 = input_tokens × 普通单价 + cache_creation_input_tokens × 写入单价 + cache_read_input_tokens × 读取单价
实际成本公式要看计费规则,但即使只做“有效输入成本”的估算,也比只看 token 数量更接近真实账单。缓存读取占比高不代表成本一定最优,还要看写入频率;如果写入频繁,写入单价的上浮可能吃掉读取省下的钱。
我通常会设置一个“缓存放大倍数”,定义为cache_read_tokens / cache_creation_tokens。这个倍数超过某个阈值,说明缓存确实在反复复用;如果长期接近 1,那说明每次写入之后只被读取了一次,收益不大。
4.3 阈值、告警、预算护栏
在网关里,我习惯为每个业务线设置预算上限。比如某个客服场景每月输入成本预算为 X,当累计消耗超过 70% 时提醒,超过 90% 时限制流量。预算护栏不是为了让业务难做,而是避免一个异常流量直接把整月成本打爆。
告警不是为了事后追责,而是为了快速定位。如果某条业务线的缓存命中率在半小时内从 80% 降到 20%,大概率是有人改了模板或动态字段。那就立刻去看发布记录和网关日志,而不是等着月底账单出来再做分析。这类告警配合版本控制,能把“成本事故”的响应时间从几天压缩到几分钟。
5. 高命中率之路上的五个经典障碍与排查过程
理论说得再多,不如看看实际踩坑。我自己带过的不止一个项目,命中率常年低于预期。下面这些障碍,几乎每个团队都会遇到至少一个。
5.1 现场还原:一次命中率从 80% 掉到 0 的排查
有一回,某客服项目原本的缓存命中率稳定在百分之七八十,某天突然归零,而且没有代码发布记录。我们的第一反应是看后端日志,确认是不是网关层面把请求路径换了。结果发现网关日志里所有请求的 usage 都显示大量cache_creation_input_tokens,完全没有cache_read_input_tokens。
这说明两件事:第一,缓存根本没有被命中;第二,每次请求都在重新写入,很像前缀发生了变化。于是我们拉出两个前后相隔五分钟的请求,把消息内容逐字段做 diff。果然,system提示词的开头多了一段动态文本,内容是“当前天气状况:晴,23℃”。这个字段来自一个新增的天气服务,被前端塞进了请求参数里,后端再把请求参数拼接到了系统提示词最前面。
这种问题如果只靠人工 review 代码,很难快速发现。真正有用的排查链路是:先把请求日志的 usage 字段按时间聚合,观察那个“从命中变成写入”的精确时间点;再把那前后两条请求做前缀 diff。只要看到 diff 发生在头部,就几乎可以锁定是一个动态字段入侵了固定前缀区。
修复也不复杂:将天气字段从系统提示词里挪到消息末尾,作为一种辅助上下文存在,而不是要求模型必须把它当作第一优先级的输入。改完之后命中率在下一个 TTL 周期内恢复到了原来的水平。
5.2 其他四个高频原因
除了动态字段入侵,下面几个障碍也很常见。
第一,短前缀问题。缓存往往有最小的有效前缀长度和区间对齐要求。有些请求里的静态内容只有几百 token,达不到稳定命中条件。这类请求就算开了缓存,也很难获得命中收益。我的建议是评估一下静态内容是否足够长,如果不够长,别硬上缓存,合并批量请求也许更划算。
第二,无序拼接。检索结果明明是可以固定的知识库,但因为代码里用了哈希表或者按分数排序,每次输出的顺序都在变。顺序一变,前缀就被打断。这个问题的排查也靠 diff 法:将同一知识库在不同请求里的文本做对比,如果发现文本相同但顺序不同,就去修代码里的数据结构,改成有序列表。
第三,TTL 与调用频率不匹配。前面已经说过,短 TTL 遇到低频率调用会变成“次次冷启动”。这是命中率最低的一种情况,但不是缓存机制的问题。排查时看一下两次相同前缀请求的时间间隔,跟 TTL 对比,基本一眼就能判断。
第四,SDK 或端到端工具的“隐藏改写”。有些团队不是直接调 API,而是通过第三方封装库或者某种消息框架。框架可能在发送前自动向消息头插入请求 ID、元数据、时间信息,这些都不会显示在你的显式代码里。结果你看着自己的请求没有问题,但实际到达服务端时前缀已经变了。这种问题通常可以通过“网关记录请求原文、后置检查消息开头”来发现。
我也做过一个笨办法:在开发和测试环境里专门加一个断言,检查每一个即将发出的请求的 message 数组,确保开头若干段的 hash 和预期模板完全一致。这个断言在 CI 里跑,比任何事后的监控告警都要早发现问题。虽然简单,但确实能阻拦大量不规范改动。
6. 落地几个月后我才想明白的几件事
实践久了,很多开始时笃定的判断会被推翻。下面这几条不是标准文档里的建议,更多是来自真实项目的个人体会。
缓存不是越高越好。有些优化方案会为了提高命中率,强制把大量动态内容也塞进静态模板里,结果命中率上去了,但模型输出变得越来越“钝”,因为它接收不到实时的上下文。成本是降低了,业务指标却下滑了。这个取舍没有标准答案,我现在的做法是优先保业务效果,再谈缓存优化。
先治理再优惠。很多团队一上来就追求缓存命中率,恨不得所有请求都命中。但如果没有接入层治理,类似的“藏动态前缀”问题会反复上演。与其给每个工程师都上一遍培训课,不如从接入层把规则固化成代码。治理到位之后,缓存优化只是水到渠成的事。
测试环境不要盲目复用生产模板。测试人员为了模拟线上,通常会随意修改 Prompt 中的参数。这样一来,测试环境会产生大量缓存写入,虽然不是真实的计费损失,但会污染监控数据。我们后来把测试环境独立缓存、独立指标,线上数据才变得干净可靠。
最后再说一个小技巧:如果你不想立刻搭完整的网关,可以先在现有代码里抽一个统一函数负责组装给模型的消息列表,所有业务方都必须通过这个函数来发起请求。函数里把固定模板放在前面,动态参数强制放后面,并加上结构检查。这个函数本质上就是一个极简接入治理,够小、够轻、也足够先跑起来。等规模大到函数撑不住了,再升级成独立网关,迁移成本也不会太高。
Claude Prompt 缓存不是能不能省钱的问题,而是企业愿不愿意为它补上前缀工程和接入治理这两门基础课。补上了,账单一出来你会看见一条明显的下行曲线;补不上,那个缓存参数可能只是一行自我安慰的代码。