Prompt Caching 实战:用缓存降低 LLM 成本

一、什么是 Prompt Caching(提示词缓存)

Prompt Caching(提示词缓存,也称前缀缓存 Prefix Caching)是模型供应商提供的一项能力:当你多次请求中前缀内容完全一致时,供应商会把这段前缀的注意力计算结果缓存下来,后续命中缓存的请求只需计算增量部分。对 LLM 应用来说,系统提示词、知识库上下文、工具定义这些在每轮对话里几乎不变的内容,正是缓存的最佳对象。用好 Prompt Caching,能同时换来「更低的 token 成本」和「更低的首 token 延迟」两笔收益。尤其在 RAG、智能客服、代码助手这类「长上下文 + 高频复用」场景里,收益最为明显。

二、它为什么能大幅降本

大模型计费通常分「输入 token」「输出 token」「缓存写入」「缓存命中」几类。未命中时,整段前缀按标准输入价计费;命中缓存后,前缀改按远低于标准价的「缓存命中价」计费(通常只有标准输入价的 1/10 左右),而写入缓存时会有一次性的小额写入费。当同一前缀被高频复用时,写入费被摊薄,整体成本直线下降。换句话说,缓存把「每次重复计算」变成了「一次写入、多次低价读取」,复用次数越多越划算。

计费项示例单价(每百万 token)说明
未命中输入$3.00每轮全量计费
缓存写入$3.75首次写入一次性计费
缓存命中$0.30后续命中低价计费

三、Anthropic Claude 落地

举个真实账:一个客服助手每天处理 1 万次对话,每次都要带上 3000 token 的系统提示词与知识库。若不缓存,每天输入 token 约 3000 万;若 95% 请求命中缓存、按命中价 1/10 计费,输入成本直接降到原来的 15% 左右,一个月往往能省下数百美元。更关键的是,前缀的注意力计算已被缓存,首 token 延迟明显更短,用户体验也同步提升——这是「降本」与「提速」一举两得。

Claude 通过消息体里的 cache_control 字段标记「从这里开始缓存」。最常见的做法是在系统提示词或长上下文的末尾加一个 {"type":"ephemeral"} 的断点。

{
  "model": "claude-sonnet-4-0",
  "system": [
    {
      "type": "text",
      "text": "你是一个严谨的 SQL 分析助手。以下是数据库 schema 与业务背景:\n...(长上下文)...",
      "cache_control": {"type": "ephemeral"}
    }
  ],
  "messages": [{"role":"user","content":"上季度华东区 GMV 是多少?"}]
}

首次请求会返回 cache_creation 相关的用量并收取写入费;只要前缀不变,后续请求命中缓存,返回体里的 cache_read_input_tokens 即命中计费的 token 数。

四、OpenAI 落地

OpenAI 的 Prompt Caching 更「自动」:当请求体里某个前缀达到最小 token 阈值(通常 1024 tokens)且内容稳定时,平台自动缓存,无需显式标记。你只需保证高频请求共享相同前缀即可。目前 GPT-4o、o 系列与部分嵌入模型均已支持,可在用量明细里直接看到 cached_tokens 字段,便于核算节省。

from openai import OpenAI
client = OpenAI()
# 把不变的 system + 知识库拼在前面,用户问题放最后
resp = client.chat.completions.create(
    model="gpt-4o",
    messages=[
        {"role":"system","content": SYSTEM_PROMPT + KNOWLEDGE_BASE},  # 稳定前缀
        {"role":"user","content": user_question},                     # 变化部分放末尾
    ],
)
print(resp.usage.prompt_tokens_details.cached_tokens)  # 命中缓存的 token 数

注意:OpenAI 只对「最后一个可缓存点之前」的内容做缓存,且前缀必须从第一个 token 起完全一致,因此把易变内容放在消息末尾是关键。

五、工程实践:哪些内容该缓存

内容是否适合缓存原因
系统提示词 / 角色设定非常适合几乎不变,复用率极高
知识库 / RAG 检索结果适合(需权衡)内容随查询变化;同一会话内可缓存
工具 / 函数定义非常适合函数清单长期稳定
Few-shot 示例适合固定样例可复用
每轮用户新问题不适合每轮都变,放末尾

六、避坑清单

缓存不是「开了就省」,下面这些坑会让你白忙:

  1. 前缀必须逐 token 一致。多一个空格、换一行、改个标点都会让缓存失效,动态注入的时间戳、随机 id 要避开前缀区。
  2. 最小 token 阈值。Anthropic 默认至少 1024 tokens 才触发缓存;太短的前缀不值得缓存。
  3. 缓存有 TTL。空闲一段时间缓存会被回收,需要重新写入;高并发场景复用率高、成本低。
  4. 多轮对话保持前缀。把历史消息整体作为稳定前缀的一部分时,要固定截断策略,避免每轮前缀漂移。
  5. 别为缓存牺牲质量。不要为了凑稳定前缀而把本应随用户变化的上下文硬塞进缓存区。

七、一个省钱的封装示例

把「稳定前缀 + 可变问题」封装成函数,统一在末尾追加用户问题,天然契合缓存要求:

def ask_with_cache(client, stable_prefix: str, question: str) -> str:
    # stable_prefix: 系统提示 + 知识库,长期不变
    # question: 每轮不同,放在最后
    resp = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {"role": "system", "content": stable_prefix},
            {"role": "user", "content": question},
        ],
    )
    used = resp.usage
    cached = used.prompt_tokens_details.cached_tokens
    print(f"缓存命中 token:{cached} / 输入 {used.prompt_tokens}")
    return resp.choices[0].message.content

配合监控把 cached_tokens 比例做成看板,命中率长期低于 50% 就要回头检查前缀是否真的稳定。

八、多轮对话里的缓存策略

聊天机器人常要保留历史消息,但把整段历史逐字拼进每次请求,历史一变前缀就失效。稳妥做法是:把「系统提示词 + 工具定义 + 当前会话的固定背景」作为缓存前缀,历史对话做摘要化后放在前缀末尾、用户最新问题之前;这样既复用了稳定前缀,又避免历史无限膨胀拖慢首 token。Claude 还支持在消息中间设置多个 cache_control 断点,把「长期稳定」与「中期稳定」分层缓存,例如系统提示词做长期缓存、本会话摘要做中期缓存,进一步抬高整体命中率。

小结

Prompt Caching 是大模型应用上线后最值得做的成本优化之一:零模型改动、纯接入层改造,就能把重复前缀的计费砍到十分之一。把它和推理成本优化上下文工程配合起来,再叠加RAG 混合检索Function Calling 工具调用,就能在效果不降的前提下把单位请求成本压到最低。

上一篇 fzf 模糊查找实战:重塑你的终端工作流
下一篇 技术选型决策实战:在相似方案间做出可靠选择