LLM 提示缓存 #3:可运行的 Python 教程
目录
太长不看 — 一套 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-5、claude-sonnet-4-5 / 4-6、claude-opus-4-5 / 4-6 / 4-7、gpt-5.4-mini、gpt-5.4-nano、gpt-5.2、gpt-5.5-pro、gemini-2.5-flash、gemini-2.5-pro、gemini-3.1-pro-preview、deepseek-v4-flash、qwen3-max、qwen3.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)—— 无需在本地维护按厂商划分的费率表。
由架构推导而来、适用于所有厂商的两条规则:
- 稳定内容在前,易变内容在后。 前缀从第 0 个 token 开始匹配;开头处哪怕一个字节的改动都会使整个前缀失效。
- 不要把动态数据放进系统提示。 当前时间戳、会话 ID、请求 UUID 都会破坏缓存。
下面的内容只是同一模式在各厂商上的示例而已。
2. Anthropic Claude —— 显式 cache_control 标记
Claude 属于显式标记家族 —— Anthropic 的 API 不会自动缓存。要获得缓存命中,需要在 system 或 messages 数组中标注最多四个 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-5、claude-sonnet-4-5 / 4-6、claude-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-mini 为 0.73 s,gpt-5.4-nano 为 1.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 s | 1.48 s | 1.00 s | 5,888 / 6,887 (85%) |
gpt-5.4-mini | $0.00267 | $0.00257 | −4%* | 3.63 s | 1.23 s | 0.73 s | 6,400 / 6,887 (93%) |
gemini-2.5-flash | $0.00198 | $0.00024† | −88% | 2.49 s | 1.37 s | n/a‡ | 7,140 / 7,322 (97%) |
gemini-2.5-pro | $0.00824 | $0.00205† | −75% | 2.99 s | 1.76 s | n/a‡ | 6,120 / 7,328 (84%) |
deepseek-v4-flash | $0.00091 | $0.00023 | −74% | 4.02 s | 3.71 s | 2.93 s | 6,784 / 7,101 (96%) |
qwen3-max | $0.00553 | $0.00549 | −1%§ | 4.80 s | 2.37 s | 1.53 s | 7,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. 上线前检查清单
在发布一个感知缓存的提示之前:
- 稳定内容在前 —— 把系统提示、知识库、工具 schema 放在
messages顶部。 - 易变内容在后 —— 把用户输入、检索文档、时间戳放在底部。
system中不放动态变量 —— 当前时间、用户 ID、随机种子都会摧毁你的前缀。- 每次调用都记录
cached_tokens。 如果生产中命中率低于 50%,说明你的前缀其实并不稳定。检查那些未命中的提示。 - 不要相信单次命中。 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,它确实带来的,是这三点:
- 一个 base_url、一个鉴权头、所有模型。 切换
model字段,调用形式不变。相同的messages数组、相同的usage字段结构。你不必为五个厂商背上五套 SDK。 - 每次调用的
usage.cost(USD)。 网关用当前上游费率计算美元成本,并将其包含在每个响应中。你不必在代码里维护定价矩阵,也不必订阅各厂商的价格变更通知。 - 统一的
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-flash 和 deepseek-v4-flash。gpt-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.