Prompt缓存计费与断点策略:LLM应用成本优化实战
2026/9/24 22:52:05 网站建设 项目流程

1. 从一次账单异常说起:Prompt 缓存到底在解决什么问题

如果你正在调用大模型 API 做产品,大概率遇到过这种情况:同一个系统提示词、同一段背景资料,在一天之内被重复发送了几百上千次,月底账单出来的时候,输入 token 的费用占了总成本的大头。更让人头疼的是,明明内容完全一样,每次请求都要重新计算一遍,延迟也降不下来。这不是你的代码写得有问题,而是没有用上Prompt 缓存这个机制。

Prompt 缓存的核心思路非常朴素:把那些反复出现、内容固定的前缀部分(比如系统提示词、工具定义、知识库片段、few-shot 示例)在服务端缓存下来,后续请求如果命中相同前缀,就直接复用已经计算好的中间状态,不再重复计费、不再重复计算。它解决的是"重复输入成本高、首 token 延迟长"这两个最实际的痛点。适合所有在做 LLM 应用开发的人——不管你是刚接第一个 API 的新手,还是已经在优化线上成本的老手,这套机制都值得花时间吃透。

但这里有个容易被忽略的事实:缓存不是自动生效的,它需要你在请求里显式声明断点。很多人以为只要内容一样平台就会自动缓存,结果发现账单没降、延迟没变,然后开始怀疑是不是平台"虚标"。实际上,缓存机制的设计里有一个关键概念叫断点(breakpoint),你得告诉系统"从这里开始,前面的内容请缓存起来"。断点打在哪里、打几个、怎么和计费规则配合,直接决定了你能省多少钱。这篇文章就把计费逻辑和断点策略这两件事拆开讲清楚,顺带把实际踩过的坑一并交代。

2. 计费规则拆解:缓存命中与未命中到底差在哪

2.1 输入 token 的三档定价逻辑

要理解缓存的价值,先得搞清楚计费是怎么分档的。目前主流平台的计费模型里,输入 token 通常分成三档,价格依次递减:

计费类型说明相对价格(以某平台为例)
普通输入 token未命中缓存的完整输入基准价 1x
缓存写入 token首次建立缓存时写入的部分约 1.25x
缓存命中 token后续请求复用缓存的部分约 0.1x

这张表里最关键的信息是:缓存命中的价格只有普通输入的十分之一左右。也就是说,如果你的系统提示词有 2000 token,一天被调用 1000 次,不做缓存的话这 200 万 token 全按基准价算;做了缓存之后,第一次写入按 1.25 倍算一次,后面 999 次全按 0.1 倍算,成本差距是数量级的。

但这里有个反直觉的点:缓存写入比普通输入还贵。很多人第一次看到这个规则会疑惑——既然要省钱,为什么写入还要加价?原因在于,建立缓存本身需要服务端额外做一次状态持久化,这个开销是真实存在的。所以缓存策略的本质是一道数学题:写入成本 + N 次命中成本 < N 次普通输入成本,只有当复用次数足够多时,缓存才划算。

2.2 算一笔账:多少次复用才回本

假设一段前缀有 1000 token,普通输入单价为 P,缓存写入为 1.25P,缓存命中为 0.1P。设复用次数为 N(包含首次写入那一次),则:

  • 不做缓存:N × 1000 × P
  • 做缓存:1000 × 1.25P + (N-1) × 1000 × 0.1P

令两者相等:N = 1.25 + 0.1(N-1),解得N ≈ 1.28

也就是说,只要同一段前缀被复用超过 2 次,缓存就开始省钱。这个门槛低得超出很多人的预期。实际场景里,系统提示词在一天内被调用几十上百次是常态,所以缓存几乎是稳赚不赔的。真正需要权衡的不是"要不要缓存",而是"缓存多长的前缀、断点打在哪里"。

注意:不同平台的缓存有效期不一样,有的是 5 分钟,有的是 1 小时。如果你的调用频率很低,比如同一段前缀隔几小时才用一次,可能缓存早就过期了,这时候写入成本就白花了。所以缓存策略要结合你的实际调用节奏来定。

2.3 缓存命中的判定条件:前缀必须完全一致

缓存命中不是"内容相似"就行,而是要求从开头到断点位置的 token 序列完全一致,一个字符都不能差。这一点极其重要,因为很多人在系统提示词里塞了动态内容——比如当前时间戳、用户 ID、随机数——结果每次请求前缀都不一样,缓存永远命中不了,钱照花,延迟照旧。

我见过一个典型的错误案例:有人在系统提示词开头写了当前时间是 2024-01-15 10:30:00,想着让模型知道时间。结果每次请求这个时间都在变,整个前缀的缓存全部失效。正确的做法是把动态内容放到断点之后,或者干脆放到用户消息里,让固定的部分保持稳定。

3. 断点机制:缓存控制的核心开关

3.1 断点是什么,为什么必须显式声明

断点(breakpoint)是你在请求里主动标记的一个位置,含义是"从这个位置往前的内容,请缓存起来"。它不是自动的,因为服务端无法猜测你希望缓存哪一段——可能你想缓存系统提示词,也可能想缓存整个知识库,还可能只想缓存工具定义。显式声明让控制权回到开发者手里。

在请求结构里,断点通常通过一个缓存控制字段来标记,挂在消息对象上。以常见的消息数组结构为例,大致长这样:

{ "messages": [ { "role": "system", "content": [ { "type": "text", "text": "你是一个专业的客服助手,以下是产品知识库……", "cache_control": { "type": "ephemeral" } } ] }, { "role": "user", "content": "帮我查一下退货政策" } ] }

这里的cache_control就是断点标记。它挂在哪个内容块上,就表示"到这个块结束为止的前缀可以被缓存"。ephemeral表示这是临时缓存,平台会按自己的策略管理生命周期。

3.2 断点位置的选择:越靠前越省,但别乱放

断点打在哪里,直接决定了缓存覆盖的范围。原则很简单:断点之前的内容越多、越稳定,缓存收益越大。所以理想情况下,断点应该打在整个请求里最靠后的那个"稳定边界"上。

但这里有个常见的误区:有人为了最大化缓存范围,把断点打在最末尾,结果把用户消息也包进去了。用户消息每次都不一样,导致缓存永远命中不了。正确的做法是找到"固定内容"和"动态内容"的分界线,断点打在这条线上。

举个实际例子。假设你的请求结构是:

  1. 系统提示词(固定,2000 token)
  2. 工具定义(固定,1500 token)
  3. 知识库片段(半固定,可能按用户查询动态检索,3000 token)
  4. 用户消息(动态,100 token)

这种情况下,断点应该打在工具定义之后、知识库之前。这样系统提示词和工具定义这 3500 token 稳定命中缓存,知识库和用户消息走普通计费。如果你把知识库也纳入缓存,由于它是动态检索的,命中率会很低,反而浪费写入成本。

3.3 多个断点的分层缓存策略

部分平台支持在同一个请求里打多个断点,形成分层缓存。这个能力在复杂场景下非常有用。比如:

  • 第一个断点:系统提示词(最稳定,命中率最高)
  • 第二个断点:工具定义 + 少量示例(较稳定)
  • 第三个断点:知识库片段(按业务模块分组,中等稳定)

分层的好处是,即使后面的内容变了,前面的缓存依然有效。比如用户切换了业务模块,知识库片段变了,但系统提示词和工具定义的缓存不受影响,依然命中。这比只打一个断点的鲁棒性高得多。

不过要注意,断点数量通常有上限(常见是 4 个),而且每个断点都会带来一次写入开销。所以不是越多越好,要根据内容的稳定层级来设计。我的经验是:把内容按"变化频率"分成 2 到 3 层就够了,层数太多管理成本反而上升。

4. 实战中的断点设计:几个真实场景的取舍

4.1 客服机器人:系统提示词 + 知识库的两段式

客服机器人是最典型的缓存受益场景。系统提示词通常很长(角色设定、回复规范、安全边界),而且完全固定;知识库片段则根据用户问题动态检索。我的做法是:

  • 系统提示词单独打一个断点,确保它 100% 命中
  • 知识库片段不打断点,走普通计费
  • 用户消息自然也在断点之后

这样设计的原因是,知识库片段的检索结果每次都可能不同,命中率低,打断点反而增加写入成本。而系统提示词是铁打的固定内容,命中率接近 100%,收益最稳定。

实测下来,一个 2500 token 的系统提示词,在日均 5000 次调用的情况下,光这一项每月就能省下相当可观的费用。而且首 token 延迟从原来的 800ms 左右降到了 300ms 以内,用户体验的提升是能直接感知到的。

4.2 代码助手:工具定义是缓存的大头

代码助手类应用的特点是工具定义特别长。一个功能完整的代码助手可能定义了十几个工具,每个工具的 schema 描述加起来轻松超过 3000 token。这部分内容完全固定,是缓存的绝佳目标。

我的策略是把工具定义和系统提示词合并成一个断点,放在消息数组的最前面。因为这两部分都是固定的,合并缓存可以减少断点数量,降低管理复杂度。实测命中率能稳定在 95% 以上。

这里有个细节值得注意:工具定义的顺序也会影响缓存命中。如果你在代码里用字典或哈希表存储工具定义,每次序列化出来的顺序可能不一样,导致前缀不一致,缓存失效。解决办法是固定工具定义的顺序,比如按名称排序后再序列化。这个坑我踩过,排查了半天才发现是顺序问题。

4.3 多轮对话:断点该不该跟着对话走

多轮对话场景比较特殊,因为历史消息会不断累积。有人会想:既然历史消息也是固定的(已经发生过的对话不会变),那是不是可以把断点打在历史消息的末尾,让整个对话历史都缓存起来?

理论上可行,但实际要谨慎。原因是多轮对话的历史每轮都在增长,上一轮的断点位置和这一轮不一样,缓存前缀对不上,命中率会打折扣。而且对话历史越长,写入成本越高。我的建议是:多轮对话只缓存系统提示词和工具定义这些真正固定的部分,对话历史走普通计费。除非你的对话轮次非常固定(比如固定 3 轮),否则跟着对话走不划算。

5. 那些文档里不会写的踩坑记录

5.1 动态内容混进前缀:最常见的缓存杀手

前面提过时间戳的问题,但实际项目里混进前缀的动态内容远不止这一种。我整理了一份"缓存杀手"清单,都是实际踩过的:

  • 时间戳和日期:系统提示词里写"今天是 X 月 X 日",每次请求都变
  • 用户 ID 和会话 ID:为了个性化,把用户标识拼进了系统提示词
  • 随机数或 UUID:某些框架会自动注入请求 ID
  • 动态排序的列表:比如工具列表、知识库片段列表,顺序不固定
  • 浮点数格式化差异:同一个数值,有时序列化成1.0,有时是1,导致前缀不一致

排查这类问题的方法很简单:把两次请求的完整 payload 打印出来,逐字符对比前缀部分。只要有一个字符不同,缓存就不会命中。我一般会在开发阶段加一个断言,检查前缀的哈希值是否稳定,不稳定就直接报警。

5.2 缓存过期与调用节奏的错配

缓存是有生命周期的,常见的是 5 分钟到 1 小时。如果你的调用节奏和缓存周期错配,就会出现"刚写入就过期"的尴尬情况。比如你的应用是定时任务,每小时跑一次,每次跑的时候缓存刚好过期,那写入成本就白花了。

解决办法有两个:一是调整调用节奏,让请求集中在缓存有效期内;二是评估是否值得缓存,如果复用次数太少,干脆不用缓存,走普通计费反而更省。我一般会统计一段时间的调用间隔分布,如果大部分间隔都超过缓存有效期,就放弃缓存。

5.3 断点数量超限导致的静默失败

部分平台对断点数量有硬性限制,超过之后不会报错,而是静默忽略多余的断点。这个行为很坑,因为你的代码看起来没问题,但实际缓存没生效。我遇到过打了 6 个断点,结果只有前 4 个生效的情况,排查了很久才发现是数量限制。

建议在代码里对断点数量做校验,超过平台上限就直接报错,别让它静默失败。同时把断点配置集中管理,方便统一调整。

5.4 缓存命中率监控:没有度量就没有优化

上线缓存之后,一定要监控命中率。大部分平台在 API 响应里会返回缓存相关的统计字段,比如命中 token 数、写入 token 数。把这些数据采集起来,做成看板,你才能知道缓存策略到底有没有效果。

我一般关注三个指标:命中率(命中 token / 总输入 token)、写入频率(单位时间内的缓存写入次数)、成本节省比例(对比不做缓存的预估成本)。如果命中率低于 70%,说明前缀稳定性有问题,需要排查;如果写入频率过高,说明缓存频繁过期,需要调整策略。

6. 把缓存做稳的几个工程习惯

6.1 前缀内容版本化管理

系统提示词和工具定义不是一成不变的,产品迭代时经常要改。每次改动都会导致缓存失效,这是正常的。但如果改动频繁,缓存就一直建立不起来。我的做法是给前缀内容做版本管理:每次改动分配一个新版本号,旧版本的缓存自然过期,新版本重新建立。同时控制改动频率,非必要不改,改之前评估对缓存的影响。

具体操作上,我会把系统提示词和工具定义抽成独立的配置文件,用版本号命名,比如system_prompt_v3.txt。代码里引用版本号,而不是直接硬编码内容。这样改动有迹可循,也方便回滚。

6.2 请求构造的确定性保证

要保证缓存命中,请求构造必须是确定性的。这意味着同样的输入,每次序列化出来的 payload 必须完全一致。除了前面提到的工具顺序问题,还要注意:

  • JSON 序列化时 key 的顺序要固定
  • 浮点数的格式化要统一
  • 字符串的编码要一致(统一用 UTF-8)
  • 换行符要统一(\n还是\r\n

这些细节看起来琐碎,但任何一个不一致都会导致缓存失效。我一般会写一个单元测试,对同一份输入连续序列化两次,断言结果完全一致。这个测试能挡住大部分低级错误。

6.3 灰度上线与回滚预案

缓存策略的调整会影响线上成本和延迟,所以不要一次性全量切换。我的做法是先在小流量上验证,观察命中率和成本变化,确认没问题再逐步放量。同时准备好回滚预案,一旦发现异常(比如命中率骤降、延迟上升),能快速切回原策略。

灰度期间要重点观察的指标包括:缓存命中率、首 token 延迟、总成本。如果命中率符合预期但延迟没降,可能是平台侧的缓存读取有额外开销,需要进一步评估是否值得。

7. 关于缓存策略,我个人的几点体会

做了几个带缓存的 LLM 应用之后,我最大的体会是:缓存不是配置项,而是架构设计的一部分。它要求你在设计请求结构的时候,就有意识地把内容按稳定性分层。如果一开始没想清楚,后面再补缓存,往往要重构请求构造逻辑,成本很高。

另一个体会是,不要为了缓存而缓存。有些场景下复用次数本来就少,硬上缓存反而增加复杂度和写入成本。判断标准很简单:算一下复用次数,超过 2 次就值得,低于 2 次就别折腾。这个门槛低,但也不是所有场景都能达到。

最后分享一个小技巧:把缓存命中率做成一个实时告警指标。一旦命中率跌破阈值,立刻收到通知。这样能在问题扩散之前就发现,避免成本悄悄上涨。我现在的项目里,这个告警帮我抓到过好几次前缀被意外改动的问题,省了不少排查时间。

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

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

立即咨询