LLM 提示缓存 #3:可运行的 Python 教程

目录
  1. 0. 准备工作
  2. 1. 感知缓存的调用(每个厂商都一样)
  3. 2. Anthropic Claude —— 显式 cache_control 标记
  4. 3. OpenAI GPT-5.x —— 自动缓存
  5. 4. Google Gemini —— 隐式缓存
  6. 5. DeepSeek-v4-flash —— 磁盘支撑的自动缓存
  7. 6. 阿里 Qwen —— 报告命中,折扣不稳定
  8. 7. 跨厂商基准测试(2026-05-25 实测)
  9. 8. 上线前检查清单
  10. 9. 感知 TTL 的模式
  11. 8.1 会话绑定的工作负载(聊天、IDE 助手)
  12. 8.2 批处理 / Cron 的心跳
  13. 8.3 冷存储文档
  14. 10. 网关到底带来了什么
  15. 常见问题

太长不看 — 一套 OpenAI SDK、一个 base_url,覆盖所有主流 LLM。本文中的数据于 2026-05-25 针对线上 Synthorai 网关、使用约 7,300 token 的稳定系统提示实测得出。网关在这里的作用是朴素而诚实的:一个端点、一个鉴权头,以及一个 usage.cost 字段,让你免于维护按厂商划分的定价矩阵。缓存背后的 Transformer 原理见 第 1 篇:缓存原理;各厂商的设计取舍见 第 2 篇:厂商对比

系列:共 4 篇中的第 3 篇 · 前文:第 1 篇 — 缓存原理 · 第 2 篇 — 厂商对比与评测 · 下一篇:第 4 篇 — 按使用场景选择最佳 LLM


0. 准备工作

pip install openai
# common.py — reused across every example
import os, time
from openai import OpenAI

oai = OpenAI(
    api_key=os.environ["SYNTHORAI_KEY"],
    base_url="https://synthorai.io/v1",
)

网关对其代理的每个模型(GPT、Claude、Gemini、DeepSeek、Qwen)都使用 OpenAI 的传输格式。你只需更改 model 字段,而不必更换 SDK。鉴权使用 Authorization: Bearer <key>

公开网关上可用的支持缓存的模型 ID(2026-05 快照):claude-haiku-4-5claude-sonnet-4-5 / 4-6claude-opus-4-5 / 4-6 / 4-7gpt-5.4-minigpt-5.4-nanogpt-5.2gpt-5.5-progemini-2.5-flashgemini-2.5-progemini-3.1-pro-previewdeepseek-v4-flashqwen3-maxqwen3.5-flash。完整的实时列表见 GET /v1/models


1. 感知缓存的调用(每个厂商都一样)

你无需主动开启。对于任何上游支持提示缓存的模型,网关只是把响应元数据透传过来。有两个字段告诉你发生了什么:

resp = oai.chat.completions.create(
    model="gpt-5.4-mini",
    max_tokens=128,
    messages=[
        {"role": "system", "content": LONG_STABLE_PROMPT},   # ~7K tokens
        {"role": "user",   "content": "First question"},
    ],
)
print(resp.usage.prompt_tokens_details.cached_tokens)   # cache hit count
print(resp.usage.cost)                                  # USD, gateway-computed

cached_tokens 是命中上游前缀缓存的输入 token 数量。usage.cost 是网关为这一次调用计算出的价格(USD)—— 无需在本地维护按厂商划分的费率表。

由架构推导而来、适用于所有厂商的两条规则:

  1. 稳定内容在前,易变内容在后。 前缀从第 0 个 token 开始匹配;开头处哪怕一个字节的改动都会使整个前缀失效。
  2. 不要把动态数据放进系统提示。 当前时间戳、会话 ID、请求 UUID 都会破坏缓存。

下面的内容只是同一模式在各厂商上的示例而已。


2. Anthropic Claude —— 显式 cache_control 标记

Claude 属于显式标记家族 —— Anthropic 的 API 不会自动缓存。要获得缓存命中,需要在 systemmessages 数组中标注最多四个 cache_control 断点。缓存读取约为输入费率的 10%;缓存写入为 125%(溢价 25%)。

通过网关使用 cache_control 最干净的方式,是把官方 anthropic SDK 指向网关的 Anthropic 原生端点(OpenAI 兼容的 /chat/completions 路径目前不会传递 cache_control 标记 —— Claude 缓存请使用 /v1/messages)。

import os
from anthropic import Anthropic

anth = Anthropic(
    api_key=os.environ["SYNTHORAI_KEY"],
    base_url="https://synthorai.io/",   # SDK appends /v1/messages
)

msg = anth.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=512,
    system=[
        {"type": "text", "text": SYSTEM_INSTRUCTIONS,
         "cache_control": {"type": "ephemeral"}},       # BP 1: never changes
        {"type": "text", "text": TOOL_DESCRIPTIONS,
         "cache_control": {"type": "ephemeral"}},       # BP 2: rarely changes
        {"type": "text", "text": RETRIEVED_DOCUMENTS},  # changes per call — not cached
    ],
    messages=[{"role": "user", "content": question}],
)

print(msg.usage)
# Usage(input_tokens=18, output_tokens=64,
#       cache_creation_input_tokens=0, cache_read_input_tokens=8123,
#       cost=...)

TTL 选项。 {"type": "ephemeral"} 默认采用 5 分钟的滑动 TTL(每次命中都会把过期时间向后推)。对于空闲间隔超过 5 分钟的工作负载,可以在同一标记上请求 1 小时的 TTL:

"cache_control": {"type": "ephemeral", "ttl": "1h"}

分层断点。 最多四个标记让你可以独立缓存「从不改变」+「很少改变」+「每次任务改变」的内容 —— 对于提示各部分以不同频率变化的智能体和 RAG 工作负载而言,这是同类中最佳的能力。即使尾部那一层(例如检索到的文档)在不同调用间发生变化,前面的层仍然会命中。

选择模型。 截至 2026-05,网关上可用的 Claude ID:claude-haiku-4-5claude-sonnet-4-5 / 4-6claude-opus-4-5 / 4-6 / 4-7。Haiku 适合低成本对话;Sonnet 是通用选择,拥有最强的智能体缓存模式;Opus 适合最难的推理任务。

实测缓存命中 / 写入 / 无缓存参考(2026-05-25,约 7,976 token 系统提示,max_tokens=64):

模型缓存写入缓存读取无缓存参考读取折扣命中 TTFT(流式)
claude-haiku-4-5$0.00916$0.00086$0.00725−88%1.31 s
claude-sonnet-4-5$0.02713$0.00247$0.02175−89%1.76 s
claude-sonnet-4-6$0.02736$0.00253$0.02198−88%1.81 s
claude-opus-4-5$0.04522$0.00409$0.03624−89%2.08 s
claude-opus-4-6$0.04522$0.00411$0.03625−89%2.55 s
claude-opus-4-7$0.06545$0.00609$0.05259−88%2.30 s

折扣在整个家族中保持一致。写入溢价约比无缓存高 25%(Anthropic 文档所述的费率);只需一次缓存命中即可回本。


3. OpenAI GPT-5.x —— 自动缓存

OpenAI 会对任何前缀足够长的请求自动缓存。无需改代码,无需标记。

def ask_gpt(question: str):
    t0 = time.perf_counter()
    resp = oai.chat.completions.create(
        model="gpt-5.4-mini",
        max_tokens=64,
        messages=[
            {"role": "system", "content": LONG_STABLE_PROMPT},
            {"role": "user",   "content": question},
        ],
    )
    return resp, time.perf_counter() - t0

r1, t1 = ask_gpt("Which export formats are supported?")
r2, t2 = ask_gpt("How long is the refund window for annual plans?")

print(t1, r1.usage.prompt_tokens_details.cached_tokens, r1.usage.cost)
# 3.63   0       0.00267
print(t2, r2.usage.prompt_tokens_details.cached_tokens, r2.usage.cost)
# 1.23   6400    0.00257

同一个 6,887 token 的提示调用两次。第二次调用:系统提示的 93% 命中缓存,总延迟从 3.6 s 降到 1.2 s。这里成本几乎没变,因为缓存折扣被一次更长的首调用补全所抵消 —— 更干净的跨厂商数据见 §7。

gpt-5.4-nano 更清晰地展示了折扣(命中时成本降低 44%)。对于只关心首 token 时间的聊天 UI,流式数据才是关键:

def ttft(model, question):
    t0 = time.perf_counter()
    stream = oai.chat.completions.create(
        model=model, max_tokens=64,
        messages=[
            {"role": "system", "content": LONG_STABLE_PROMPT},
            {"role": "user",   "content": question},
        ],
        stream=True, stream_options={"include_usage": True},
    )
    for ev in stream:
        if ev.choices and ev.choices[0].delta and ev.choices[0].delta.content:
            return time.perf_counter() - t0     # first content token

缓存命中那次实测的 TTFT:gpt-5.4-mini0.73 sgpt-5.4-nano1.00 s


4. Google Gemini —— 隐式缓存

通过网关访问时,Gemini 的缓存同样是自动的。你不需要执行任何 cachedContent 创建步骤。

r = oai.chat.completions.create(
    model="gemini-2.5-flash",
    max_tokens=128,
    messages=[
        {"role": "system", "content": LONG_STABLE_PROMPT},
        {"role": "user",   "content": "Summarize section 6 in two bullets."},
    ],
)
print(r.usage.prompt_tokens_details.cached_tokens, r.usage.cost)

gemini-2.5-flash 在约 7,300 token 系统提示上的一次实测命中:7,140 个缓存 token(97%),成本从 $0.00198 降到 $0.00024 —— 那一次便宜了 88%

两个值得了解的坑

  • Gemini 的 *-pro 变体是推理模型。当 max_tokens 很小时,你经常会看到 completion_tokens=0,因为预算被隐藏的思考消耗掉了。对任何面向用户的场景,把 max_tokens 调到 ≥256。
  • 隐式缓存的 TTL 很短,且官方未明确说明。在我们的测试中,相隔 5 s 的两次调用之间命中成功;而约 10 s 后的第三次调用有时会未命中。不要设计假定一定会命中的逻辑;请检查 cached_tokens 并优雅降级。

5. DeepSeek-v4-flash —— 磁盘支撑的自动缓存

DeepSeek 的自动缓存比其他厂商常驻 GPU 显存的缓存存活更久。调用形式相同:

r1 = oai.chat.completions.create(
    model="deepseek-v4-flash", max_tokens=128,
    messages=[{"role": "system", "content": LONG_STABLE_PROMPT},
              {"role": "user",   "content": "Q1"}],
)
# r1.usage.cost = $0.00091, cached_tokens = 0

r2 = oai.chat.completions.create(
    model="deepseek-v4-flash", max_tokens=128,
    messages=[{"role": "system", "content": LONG_STABLE_PROMPT},
              {"role": "user",   "content": "Q2"}],
)
# r2.usage.cost = $0.00023, cached_tokens = 6784  →  74% saved

缓存命中那次的流式 TTFT:2.93 s。在这组对比中 DeepSeek 不是延迟最低的选项 —— 它的优势在于成本,以及缓存能在小时级间隔内保持温热这一事实。


6. 阿里 Qwen —— 报告命中,折扣不稳定

r = oai.chat.completions.create(
    model="qwen3-max", max_tokens=128,
    messages=[{"role": "system", "content": LONG_STABLE_PROMPT},
              {"role": "user",   "content": "Q1"}],
)
print(r.usage.prompt_tokens_details.cached_tokens, r.usage.cost)
# 7040    0.00549

我们测试中观察到的注意点:cached_tokens 报告了命中(7,234 中的 7,040 = 97%),但 usage.cost 在缓存命中那次并没有下降(仍约为 $0.0055)。这意味着上游缓存确实命中了(TTFT 更快,1.53 s vs 冷启动 3.03 s),但网关对该厂商的成本字段在这一日期尚未反映出缓存费率的折扣。如果你在 Qwen 上对成本敏感,请关注 cached_tokens,并在这一问题归一之前以上游定价页为准。


7. 跨厂商基准测试(2026-05-25 实测)

单次顺序运行。7,284 字符(约 6,900–7,300 token,取决于分词器)的稳定系统提示。max_tokens=64。一次未命中调用后紧接一次命中调用。

自动缓存的厂商(无需标记):

模型未命中成本命中成本成本 Δ未命中总时延命中总时延命中 TTFT(流式)缓存命中率
gpt-5.4-nano$0.00131$0.00074−44%2.18 s1.48 s1.00 s5,888 / 6,887 (85%)
gpt-5.4-mini$0.00267$0.00257−4%*3.63 s1.23 s0.73 s6,400 / 6,887 (93%)
gemini-2.5-flash$0.00198$0.00024†−88%2.49 s1.37 sn/a‡7,140 / 7,322 (97%)
gemini-2.5-pro$0.00824$0.00205†−75%2.99 s1.76 sn/a‡6,120 / 7,328 (84%)
deepseek-v4-flash$0.00091$0.00023−74%4.02 s3.71 s2.93 s6,784 / 7,101 (96%)
qwen3-max$0.00553$0.00549−1%§4.80 s2.37 s1.53 s7,040 / 7,234 (97%)

* gpt-5.4-mini 未命中调用的补全为 44 token,而命中调用为 19 token —— 成本差额混合了缓存折扣与补全长度差异。这里更干净的信号是延迟下降(3.63 → 1.23 s)。 † 流式那一次的成本(其中报告了 cached_tokens);非流式那一次偶尔会对 Gemini 返回 cached_tokens=null 且成本未下降。网关针对 Gemini 的元数据目前不一致 —— 当 cached_tokens 存在时以它为准。 ‡ Gemini *-pro / *-flash 推理模型在较小的 max_tokens 下经常输出零个内容 token,因此在该预算下 TTFT 没有意义。若在生产中测量,请把 max_tokens 调大。 § 见 §6 —— 上游缓存确实命中(延迟下降),但网关的 usage.cost 字段在这一日期没有反映 qwen3-max 的折扣。

Anthropic Claude 由显式标记驱动;其数据放在单独的表中,因为折扣是通过 cache_control 主动开启的(模式见 §2)。相同提示,实测缓存写入 vs 缓存读取:

模型写入成本读取成本读取折扣命中 TTFT(流式)
claude-haiku-4-5$0.00916$0.00086−88%1.31 s
claude-sonnet-4-5$0.02713$0.00247−89%1.76 s
claude-sonnet-4-6$0.02736$0.00253−88%1.81 s
claude-opus-4-5$0.04522$0.00409−89%2.08 s
claude-opus-4-6$0.04522$0.00411−89%2.55 s
claude-opus-4-7$0.06545$0.00609−88%2.30 s

你的数据会因区域、时段,以及其他租户前缀的温热程度而不同。单次运行、单一日期 —— 不要把它们当作权威基准来引用。


8. 上线前检查清单

在发布一个感知缓存的提示之前:

  1. 稳定内容在前 —— 把系统提示、知识库、工具 schema 放在 messages 顶部。
  2. 易变内容在后 —— 把用户输入、检索文档、时间戳放在底部。
  3. system 中不放动态变量 —— 当前时间、用户 ID、随机种子都会摧毁你的前缀。
  4. 每次调用都记录 cached_tokens 如果生产中命中率低于 50%,说明你的前缀其实并不稳定。检查那些未命中的提示。
  5. 不要相信单次命中。 TTL 很短;按 hit_rate ∈ [0, 1) 来设计,而不是「总会命中」。

9. 感知 TTL 的模式

生产中最常见的失败模式不是「我忘了启用缓存」 —— 而是「我的命中率只有 12%,因为我的请求实际上没有落在 TTL 窗口内」。

8.1 会话绑定的工作负载(聊天、IDE 助手)

其自然节奏远低于 TTL。把提示结构组织好,缓存会自己保持温热 —— 不要额外搞别的工程。

8.2 批处理 / Cron 的心跳

如果你在 09:00 跑一个每日报表,在 3 分钟内突发调用模型 50 次,那么 09:00 的首次缓存写入是浪费的,因为缓存隔夜已经冷却。从 08:55 开始,每隔 TTL/2 用缓存前缀发送一个 1 token 的「ping」来保持温热:

def keepalive():
    oai.chat.completions.create(
        model="gpt-5.4-mini",
        max_tokens=1,
        messages=[
            {"role": "system", "content": LONG_STABLE_PROMPT},
            {"role": "user",   "content": "."},
        ],
    )

每次 ping 的成本是输入 token × 缓存费率,对于我们 7K token 前缀在 gpt-5.4-mini 上约为 $0.0026 —— 远低于让批处理任务在前 50 次真实调用上支付完整 prefill 的代价。

8.3 冷存储文档

对于偶发查询的文档(一整天里每小时一次),内存缓存大部分时间都是冷的。截至本文撰写时,网关尚未暴露托管的显式缓存创建端点 —— 对于长 TTL 需求,请使用 deepseek-v4-flash(磁盘支撑;实践中能存活小时级间隔),或在网关之外直接调用 Google 原生的 cachedContent API。


10. 网关到底带来了什么

声称网关「替你做了缓存」是不诚实的。缓存发生在模型层 —— 网关只是暴露已有的能力。相比直接使用各厂商的原生 SDK,它确实带来的,是这三点:

  1. 一个 base_url、一个鉴权头、所有模型。 切换 model 字段,调用形式不变。相同的 messages 数组、相同的 usage 字段结构。你不必为五个厂商背上五套 SDK。
  2. 每次调用的 usage.cost(USD)。 网关用当前上游费率计算美元成本,并将其包含在每个响应中。你不必在代码里维护定价矩阵,也不必订阅各厂商的价格变更通知。
  3. 统一的 cached_tokens 字段。 Anthropic 把缓存命中报告为 cache_read_input_tokens,OpenAI 报告为 prompt_tokens_details.cached_tokens,DeepSeek 报告为 prompt_cache_hit_tokens。网关将它们归一为 OpenAI 的形态,这样你的可观测性代码就不必按厂商分支。

这就是全部的卖点。其余的一切 —— 何时缓存、如何组织提示、选哪个模型 —— 是下一篇文章的工作。


下一篇第 4 篇 — 如何按使用场景选择最佳 LLM:聊天、API 与 AI 智能体 —— 一个把工作负载类型匹配到最优模型 + 缓存策略的决策矩阵,附成本计算。


常见问题

为什么对非 OpenAI 的模型也用 OpenAI SDK? 网关对其代理的每个厂商都使用 OpenAI 的传输格式。官方 openai SDK 为你提供带类型的响应、自动重试和流式辅助 —— 没有理由手写五个 HTTP 客户端。

缓存对流式响应有效吗? 有效。最后一个数据块中的 usage 对象会报告缓存命中数(当你传入 stream_options={"include_usage": True} 时)。延迟收益在流式上最明显,因为 TTFT 才是用户看到的。

对我的工作负载,哪个厂商的缓存折扣最大? 按 2026-05 的价格和 70%+ 的命中率,§7 表中最便宜的是 gemini-2.5-flashdeepseek-v4-flashgpt-5.4-mini 在 TTFT 上胜出。要拿到 Claude 文档所述的 90% 缓存折扣,需标注最多四个 cache_control 断点(见 §2)。用你自己的提示跑一遍同样的基准 —— 那是一天的工作量,而不是数周的迁移。

我什么时候需要 cache_control 标记? 只有在调用 Anthropic Claude 时才需要 —— 见 §2。对于 OpenAI/Gemini/DeepSeek/Qwen,上游会对任何足够长的前缀自动缓存,因此无需标记;该字段在这些厂商上会被静默忽略。

这些数据有多新? 于 2026-05-25 在公开网关上实测。请把它们当作单个数据点 —— 定价和延迟每个发布周期都会变化。

Anthropic Claude 的情况如何? Claude 通过网关以显式 cache_control 标记的方式受支持 —— 使用 anthropic SDK,配置 base_url="https://synthorai.io/"(SDK 会追加 /v1/messages)。OpenAI 兼容的 /chat/completions 路径目前不会传递这些标记;针对 Claude 缓存,请使用 §2 中展示的 Anthropic 原生路径。


来源与验证:所有数据于 2026-05-25 使用 openai SDK 2.38.0 针对 https://synthorai.io/v1 实测。厂商定价页:Anthropic Prompt Caching · OpenAI Prompt Caching · Google Gemini Context Caching · DeepSeek KV Cache Guide · Alibaba Bailian Context Cache.

← 返回博客