HelloAI
L4 第 15 篇 🐣 难度 🕒 11 分钟

Prompt Caching 工程实战:省 90% LLM 成本的技巧

Anthropic / OpenAI / Gemini 都支持的"prefix caching"——让重复的 system prompt 不再重复花钱。

阿莱
2026/9/20

LLM 调用的费用结构里,输入 token 经常是最大头—— 特别是有大段 system prompt、RAG 检索内容、文档分析的场景。

Prompt Caching 是过去两年最重要的成本优化技术:

同一前缀只算一次,后续命中降到 1/10 的价格**。

谁会被坑

典型场景:

你的 system prompt = 5000 tokens
每次用户请求 = 100 tokens 用户输入 + 800 tokens 输出

每次实际计费 = 5000 + 100 input + 800 output

但 system prompt 永远不变——
为什么每次都要重新付钱算它?

如果一天 1 万次请求:

  • 没缓存:5000 × 10000 = 5000 万 input tokens
  • 有缓存:5000 一次 + 100 × 10000 = 600 万 input tokens省 88%

三家厂商的 Prompt Caching 对比

Anthropic(最显式 + 最实用)

response = client.messages.create(
    model="claude-3-5-sonnet-20241022",
    system=[
        {"type": "text", "text": "短指令"},
        {
            "type": "text",
            "text": LARGE_KNOWLEDGE_BASE,  # 大段内容
            "cache_control": {"type": "ephemeral"}  # ← 关键
        }
    ],
    messages=[{"role": "user", "content": user_question}]
)

效果:

  • 写入缓存:1.25× 正常 input 价格(首次)
  • 读取缓存:0.1× 正常价格 = 省 90%
  • 缓存生效期:5 分钟(ephemeral),新版可买 1 小时

可对多个段分别打 cache_control,最多 4 个 cache breakpoint。

OpenAI(自动)

OpenAI 没有显式 cache_control—— 它会自动检测你重复发送的前缀(>= 1024 tokens),自动缓存:

# 不需要任何特殊配置
response = openai.chat.completions.create(
    model="gpt-4o",
    messages=[
        {"role": "system", "content": LARGE_SYSTEM_PROMPT},
        {"role": "user", "content": user_q}
    ]
)
# 第二次同样请求 → 自动命中 cache → 50% 折扣

效果:

  • 自动检测,无需改代码
  • 缓存命中:50% 折扣(不如 Anthropic 的 90% 狠)
  • 缓存生效期:~5-10 分钟

Gemini(context caching)

需要显式创建

from google.generativeai import caching

cache = caching.CachedContent.create(
    model="gemini-2.5-pro",
    system_instruction=LARGE_SYSTEM_PROMPT,
    contents=[],
    ttl=datetime.timedelta(hours=1)
)

response = model.generate_content(
    "user question",
    cached_content=cache.name
)

效果:

  • 缓存读取:~75% 折扣
  • TTL 可控(最长几小时)
  • 适合长文档 RAG

缓存命中的”对齐”原则

所有家厂商都有同一个底层逻辑:

缓存是按 token 前缀匹配的——前缀必须一字不差

意思是:

请求 A:
  system: "You are an assistant. Knowledge base: [10k tokens]"
  user:   "What is X?"

请求 B:
  system: "You are an assistant. Knowledge base: [10k tokens]"
  user:   "What is Y?"

                10k tokens 完全相同 → 命中缓存 → 省钱

请求 C:
  system: "You are a helpful assistant. Knowledge base: [10k tokens]"

                差一个字 → 不命中 → 全价

这是为什么放可变内容时要特别小心——日期、随机 ID、用户名等放到前面会破坏缓存。

一个实战模板

def build_request(user_query: str, user_context: dict):
    return {
        "system": [
            # ① 缓存层 1:不变的指令
            {"type": "text", "text": SYSTEM_INSTRUCTIONS,
             "cache_control": {"type": "ephemeral"}},

            # ② 缓存层 2:大段知识库(不变)
            {"type": "text", "text": KNOWLEDGE_BASE_10K,
             "cache_control": {"type": "ephemeral"}},

            # ③ 不缓存:每次都变的用户上下文
            {"type": "text", "text": f"User profile: {json.dumps(user_context)}"},
        ],
        "messages": [{"role": "user", "content": user_query}]
    }

顺序很关键:变的内容必须放前缀之后。把变的东西塞前面会让所有缓存失效。

常见 RAG 场景

# ❌ 错误:检索结果在前,问题在后
prompt = f"""
{retrieved_chunks}        ← 每次问题不同,检索结果也不同
SYSTEM_INSTRUCTIONS
User: {user_query}
"""

# ✓ 正确:固定部分在前
prompt = f"""
SYSTEM_INSTRUCTIONS       ← 缓存这部分
RETRIEVAL_PROMPT_TEMPLATE
{retrieved_chunks}        ← 这部分变化,不缓存
User: {user_query}
"""

不能保证缓存——但最大化命中率

缓存的盲区

1. ❌ 流式响应起初不算缓存命中

模型刚开始流式输出时,缓存信息可能还没传回。监控成本要看完整响应的 token usage。

2. ❌ tool / function calling 改变前缀

当你的 tools 列表变了,前缀也变了——别频繁修改 tools。

3. ❌ Conversation memory 容易破坏缓存

# 多轮对话,每次都把整个历史发回
messages = [
    {"role": "system", "content": ...},   # 缓存
    {"role": "user", "content": "Q1"},
    {"role": "assistant", "content": "A1"},  # 历史
    {"role": "user", "content": "Q2"},
    {"role": "assistant", "content": "A2"},  # 历史
    {"role": "user", "content": "Q3"},   # 新问题
]

每次新对话轮次,前缀变了!需要在 system 段后立刻打 cache_control,再让历史增长。

监控

OpenAI / Anthropic 都在 response 里返回缓存使用情况:

# Anthropic
print(response.usage)
# cache_creation_input_tokens: 5000  ← 写入了 5000 tokens
# cache_read_input_tokens: 0          ← 命中了 0 个

# 第二次请求同 prompt
# cache_creation_input_tokens: 0
# cache_read_input_tokens: 5000       ← 命中!

埋点记录这两个数字,计算每月省了多少钱

def estimated_savings(cache_read_tokens, model="claude-sonnet"):
    # 90% off on cache reads
    full_price_per_M = 3.00  # input
    cached_price_per_M = 0.30
    saved_per_M = full_price_per_M - cached_price_per_M
    return cache_read_tokens / 1_000_000 * saved_per_M

反模式速查

反模式后果
把当前时间戳放到 system prompt 里每次缓存失效
把 user ID 放在 system 前缀每个用户独立缓存 = 没省
Tools 列表频繁改缓存频繁失效
< 1024 token 也想缓存OpenAI 不会缓存
一段拆得太细,多个 cache breakpointAnthropic 限制 4 个

配合 Semantic Cache

Prompt Caching 是”完全相同的前缀才省”。 Semantic Cache 是”意思相似的问题直接返回上次答案”:

用户 A: "What's the capital of France?"  →  调用 LLM, cache result
用户 B: "France capital?"                 →  embedding 接近 → 返回 A 的答案

两者互补——Prompt Caching 省 input tokens;Semantic Cache 完全跳过 LLM 调用。

收益预估

按 2026 价格,5000 token system prompt + 1 万次/天调用:

状态月度 input 成本
无缓存$4,500
OpenAI 自动 50% off$2,250
Anthropic 90% off$450

一个简单优化,每月省几千美金

💡 一个原则

Prompt Caching 是 2024-2025 最被低估的 LLM 工程优化

  • 配置成本:< 1 小时
  • 收益:省 50-90%
  • 适用场景:80% 的应用都符合

任何用 LLM 的产品上线后,第一周就该实现 prompt caching。 不做 = 每月白送几千上万美金给 OpenAI / Anthropic。

下一篇推荐:L4-12 LLM 成本优化L7-03 推理优化

🚧 3 个常见坑

⚠️ 实战避坑

坑 1:以为缓存自动命中 需要显式标记缓存点(Anthropic)/ 配置 prefix(OpenAI)——不配置等于没用缓存。

坑 2:缓存的 TTL 太短 默认 5-10 分钟,每次请求会刷新——高并发场景能保持热缓存,低并发反而频繁 miss。

坑 3:把动态内容放到 cached 段 缓存段必须前缀稳定——把 user query / 时间戳 / random ID 放到 cached prompt 里 = 100% miss。

🔗 被以下 1 篇文章引用
📬

读到这里说明你认真在学 🎯

订阅每周精选 —— 下一篇新文章 / 新可视化第一时间送到邮箱。

💬

讨论区

· 用 GitHub 账号登录评论
⚠️ Giscus 评论未配置 —— 在 src/components/Comments.astro 顶部填入 仓库 ID 和分类 ID(见组件注释里的配置步骤)。