提示缓存与会话
提示缓存如何在 EZAT Router 上帮你省钱、如何在两种 API 上使用,以及为什么一个对话会固定在同一个部署上。
何时阅读本页
你会多次发送同一段很长的前缀(系统提示词、文档、工具定义,或不断增长的对话),希望为它少付些费用。本页介绍缓存有效期、Anthropic 格式 API 上的 cache_control、OpenAI 兼容 API 上的自动缓存、会话,以及如何确认缓存生效。
缓存能省什么
提示缓存(prompt caching)保存的是提示词开头部分处理后的结果。当下一个请求以完全相同的内容开头时,模型会从缓存中读取这部分,而不是重新处理。
| token 类型 | 价格 |
|---|---|
| 输入 | 模型的输入价格 |
| 缓存写入 | 高于输入价格。1 小时缓存的写入价格高于 5 分钟缓存。 |
| 缓存读取 | 远低于输入价格 |
各模型的价格见价格。缓存的前缀只要被读回一两次就划算了。写入后再也没被读取的前缀,比不缓存直接发送更贵。
- 缓存匹配的是完全相同的前缀,顺序为
tools、system、messages,一直到标有cache_control的块为止。在这之前任何地方的改动(系统提示词中的时间戳、调整了顺序的工具)都会导致缓存未命中。 - 短提示词不会被缓存。每个模型都有最小可缓存长度(几百到几千个 token)。低于这个长度时,请求照常执行,按普通输入计费,不会报错。
- 每次读取都会刷新有效期,且不额外收费。
缓存有效期
| 有效期 | cache_control | 适用场景 |
|---|---|---|
| 5 分钟(默认) | {"type": "ephemeral"} | 请求之间相隔几分钟以内:聊天、agent 循环、连续执行的任务 |
| 1 小时 | {"type": "ephemeral", "ttl": "1h"} | 请求之间的间隔超过 5 分钟 |
有效期从写入或最近一次读取该缓存条目的请求开始时算起。模型页面的「能力」一列显示哪些模型提供 1 小时缓存。
在 Anthropic 格式的 API 上
把 cache_control 放在你想缓存的前缀的最后一个块上:tools 中的最后一个工具、system 中的一个块,或 messages 中的一个内容块。每个请求最多 4 个断点。
import os
import anthropic
client = anthropic.Anthropic(base_url="https://api.router.ezat.io", api_key=os.environ["EZAT_API_KEY"])
# A long, unchanging system prompt: the part worth caching.
system = [
{
"type": "text",
"text": "You answer questions about the Example Ltd product manual. " + "Manual text. " * 400,
"cache_control": {"type": "ephemeral"},
}
]
for question in ["How do I reset my password?", "How do I change my email address?"]:
message = client.messages.create(
model="anthropic/claude-sonnet-5-5",
max_tokens=64,
system=system,
messages=[{"role": "user", "content": question}],
# Different questions are different sessions; one session id keeps them on one deployment.
extra_headers={"x-ezat-session": "manual-assistant-v1"},
)
usage = message.usage
print("write", usage.cache_creation_input_tokens, "read", usage.cache_read_input_tokens)只要前缀超过模型的最小长度,第一个请求会写入缓存,第二个请求会读取缓存。
Anthropic 的顶层 cache_control(请求上的一个字段,由它替你放置断点)同样会透传,但 Claude Opus 4.6 会拒绝它。在块上设置的断点对所有支持缓存的模型都有效。
在 OpenAI 兼容 API 上
OpenAI 的格式无法标记断点,所以 EZAT 会自动缓存。对于自己没有设置断点、且发往支持缓存的模型的请求,EZAT 会放置两个 5 分钟的断点:
- 在稳定前缀之后:系统提示词,没有系统提示词时为最后一个工具;
- 在最新的消息上,供对话的下一轮读取。
这样,多轮对话中已经发送过的内容都会从缓存读取。
- 关闭缓存:对单个请求使用请求头
x-ezat-cache: off。超过最小长度、且不会再次发送的一次性提示词应当这样做:否则它们要为一次永远不会被读取的缓存写入付费。 - 自己放置断点:像 OpenRouter 一样,在内容部分(
text、image_url、file,或系统消息的文本部分)或工具上加上cache_control。这时 EZAT 不再添加断点。ttl字段在这里同样有效。 prompt_cache_key为会话命名,作用与x-ezat-session相同(见下文)。
{
"model": "anthropic/claude-sonnet-5-5",
"messages": [
{
"role": "system",
"content": [
{ "type": "text", "text": "Long instructions…", "cache_control": { "type": "ephemeral", "ttl": "1h" } }
]
},
{ "role": "user", "content": "First question" }
]
}会话:一个对话,一个部署
EZAT 用多个部署提供每个模型,而提示缓存只存在于其中一个部署中。发往另一个部署的请求会错过缓存,并重新写入。所以 EZAT 会把同一个对话的请求保持在服务过它的部署上。
- 会话由你的组织、系统提示词和第一条消息共同确定,忽略
cache_control(客户端每一轮都会移动断点)。正常对话的每一轮都属于同一个会话,你无需做任何事。 - EZAT 会记住这个部署 1 小时,从会话最后一次成功请求算起。
- 会话会在以下情况迁移:它的部署不健康、容量不足,或停止服务。之后的下一个请求会在新部署上重新写入缓存。
自动确定的会话不合适时,请自己为会话命名:
| 情况 | 发送什么 |
|---|---|
| 对话过程中系统提示词或第一条消息会变化(系统提示词中带日期、历史记录被裁剪或摘要) | 每个对话一个 id |
| 许多彼此独立的请求共享一段很长的前缀(同一个系统提示词、相同的文档) | 每个共享前缀一个 id,如上面的示例 |
| 其他情况 | 什么都不用发 |
| API | 方式 |
|---|---|
| 两者 | 请求头 x-ezat-session: <id>。任意字符串;只有前 256 个字符有效。 |
| OpenAI 兼容 | 请求体字段 prompt_cache_key,最多 256 个字符(更长会以 400 拒绝)。两者都发送时以请求头为准。 |
会话 id 只在你自己组织的请求范围内分组。不要给所有流量都发送同一个 id:只要那个部署还有容量,所有流量都会发往它。
确认缓存生效
| 位置 | 缓存写入 | 缓存读取 |
|---|---|---|
Anthropic 格式的 usage | cache_creation_input_tokens(提供方报告时,在 cache_creation 中按有效期细分) | cache_read_input_tokens |
OpenAI 格式的 usage | prompt_tokens_details.cache_write_tokens | prompt_tokens_details.cached_tokens |
| 控制台「用量」页面 | 缓存(写入 / 读取) | 缓存(写入 / 读取) |
在 Anthropic 格式的 API 上,input_tokens 只统计未缓存的输入;输入总量为 input_tokens + cache_creation_input_tokens + cache_read_input_tokens。在 OpenAI 兼容 API 上,prompt_tokens 已经是总量。
如果缓存读取一直为零:
- 前缀在请求之间发生了变化,或低于模型的最小长度;
- 请求之间相隔超过 5 分钟(请使用 1 小时有效期);
- 请求属于不同的会话(请发送
x-ezat-session); - 模型不支持缓存(见下一节)。
有特殊情况的模型
| 模型 | 有何不同 |
|---|---|
capabilities.promptCaching 为 false 的模型(目前:Claude Fable 5) | 它们的缓存永远读不回来,所以在两种 API 上,EZAT 都会从发往它们的请求中移除 cache_control,并跳过自动缓存。你按普通输入付费,而不必为永远不会被读取的缓存写入付费。usage 中不显示缓存 token。 |
| Claude Opus 4.6 | 拒绝 Anthropic 的顶层 cache_control。请在块上设置断点。 |
每个模型的 capabilities 见 GET https://api.router.ezat.io/api/models(无需 key)。模型页面的「能力」一列会显示「提示缓存」和「1 小时缓存」。
Claude Code
Claude Code 会自己放置断点,而且它的系统提示词和第一条消息在整个对话中保持不变,所以缓存和会话无需任何设置即可生效。见 Claude Code。
给 AI agent 的说明
- Anthropic 格式 API:在稳定前缀的最后一个工具、系统块或消息块上放置
{"type": "ephemeral"}(或加上"ttl": "1h")。最多 4 个断点。 - OpenAI 兼容 API:缓存是自动的;除非需要 1 小时有效期,否则不要添加断点。一次性提示词发送
x-ezat-cache: off。 - 对于一个对话,如果它的系统提示词或第一条消息会变化,每一轮都发送相同的
x-ezat-session(或prompt_cache_key)。不要所有流量都用同一个值。 - 在第二个请求上检查
cache_read_input_tokens(Anthropic 格式)或prompt_tokens_details.cached_tokens(OpenAI 格式),确认缓存读取生效。