提示词缓存
提示词缓存(Prompt Caching,也称上下文缓存)由模型提供商或上游模型渠道实现,用于复用模型对相同输入前缀的处理结果。Bita API 负责鉴权、路由、协议适配、计费和日志展示,不在平台网关中另行创建一份模型提示词缓存。
提示词缓存适合重复发送长系统提示词、工具定义、文档或多轮对话历史的场景,可能降低输入费用和首段响应延迟。
提示词缓存复用的是已经处理过的相同输入前缀,不是模型已经生成的回答。即使缓存命中,模型仍会根据本次完整请求重新生成输出,因此回答内容可能不同,输出 Token 也会正常计费。
这里的“支持”表示 Bita API 能把请求交给具备缓存能力的模型渠道,并在当前协议允许时传递缓存参数、返回用量以及按缓存价格计费。缓存是否创建、能否命中、何时失效,最终由模型提供商和实际使用的上游渠道决定。
Bita API 与模型提供商分别负责什么
一次带缓存能力的调用大致经过下面的链路:
应用
↓ 请求和缓存参数
Bita API:鉴权 → 选择模型渠道 → 协议适配 → 转发请求
↓
模型提供商或上游渠道:匹配前缀 → 读取或写入缓存 → 执行模型推理
↓ 响应和缓存用量
Bita API:返回响应 → 记录用量 → 按模型、缓存价格和分组倍率计费
| 参与方 | 主要职责 |
|---|---|
| 你的应用 | 组织稳定的提示词前缀;按协议添加 cache_control 等可选参数;读取 usage |
| Bita API | 校验 API 密钥;选择可用渠道;在接口和渠道支持时透传或适配参数;记录上游返回的缓存用量并完成计费 |
| 模型提供商或上游渠道 | 实现缓存;决定最小缓存长度、匹配规则、隔离范围和有效期;报告缓存读取或写入 Token |
Bita API 不决定某段前缀是否命中,也不能把一个渠道中的缓存迁移到另一个渠道。即使模型名称相同,请求切换到不同提供商、区域、项目或上游渠道后,也不能假设缓存仍然共享。
模型广场显示缓存价格,表示平台具备对应的缓存计费信息,不等于每一条请求都会产生缓存。最终应以原始响应和使用日志中的缓存读取、缓存写入数据为准。
缓存怎样工作
可以把一次请求拆成稳定前缀和动态后缀:
稳定前缀:工具定义 + 系统提示词 + 固定文档 + 固定示例
动态后缀:本次用户问题 + 时间、用户资料等变化内容
首次请求通常需要由模型提供商处理稳定前缀,并可能把它写入缓存。后续请求被路由到可复用该缓存的渠道,并在有效期内使用相同前缀时,模型可以读取已有缓存,只处理新增或变化的后缀:
第一次:稳定前缀 + 问题 A → 处理前缀 → 可能写入缓存 → 生成回答
第二次:稳定前缀 + 问题 B → 读取缓存 → 处理问题 B → 生成回答
缓存不会扩大模型的上下文窗口。缓存 Token 仍属于本次上下文的一部分,也仍会受到模型最大上下文长度和限流规则影响。
哪些场景适合使用
| 场景 | 是否适合 | 原因 |
|---|---|---|
| 多次基于同一份长文档提问 | 适合 | 文档可以作为稳定前缀重复使用 |
| 固定系统提示词和大量示例 | 适合 | 每次请求的开头高度一致 |
| 工具定义较多的 Agent | 适合 | 工具名称、描述和参数结构通常稳定 |
| 较长的多轮对话 | 可能适合 | 历史消息保持不变,只在末尾追加新消息 |
| 每次请求都很短 | 通常不适合 | 可能达不到模型的最小缓存长度 |
| 提示词开头包含时间戳或随机数 | 不适合 | 前缀每次变化,很难命中 |
| 只发送一次的长任务 | 通常收益有限 | 只有写入,没有后续读取 |
命中的关键是相同前缀
大多数提示词缓存按前缀匹配。要提高命中率,应把稳定内容放在前面,把每次变化的内容放在最后:
推荐:
固定系统提示词 → 固定工具 → 固定文档 → 用户本次问题
不推荐:
当前时间 → 用户本次问题 → 固定系统提示词 → 固定文档
以下变化都可能导致缓存部分或全部失效:
- 修改系统提示词、工具定义、文档或示例。
- 改变消息、内容块或工具的顺序。
- 文本内容相同,但空格、换行或 JSON 序列化结果发生变化。
- 切换模型、接口协议、分组或实际使用的上游渠道。
- 缓存已经过期或被上游回收。
如果应用动态生成请求体,应保持稳定部分的序列化结果一致。不要在稳定前缀中加入时间戳、请求 ID、随机数或每位用户都不同的数据。
不同协议的常见方式
| 协议 | 常见缓存方式 | 常见用量位置 |
|---|---|---|
| OpenAI Chat Completions | 支持的模型通常自动缓存相同前缀 | usage.prompt_tokens_details.cached_tokens |
| OpenAI Responses | 支持的模型通常自动缓存相同前缀 | usage.input_tokens_details.cached_tokens |
| Anthropic Messages | 通过 cache_control 标记可缓存前缀,部分模型或渠道也可能提供自动缓存 | usage.cache_creation_input_tokens、usage.cache_read_input_tokens |
| Gemini 原生接口 | 由模型和接口提供隐式缓存或显式上下文缓存 | usageMetadata 中的缓存相关字段 |
字段不存在或数值为 0,不一定表示请求失败,只表示上游没有为本次调用报告缓存命中或写入。Bita API 的使用日志会尽量统一展示模型提供商通过不同协议返回的缓存数据,但不会自行生成缓存用量。
OpenAI 兼容接口
对于支持自动提示词缓存的模型,通常不需要添加专用参数。连续请求只需保持前缀完全一致:
import requests
url = "https://www.bita-api.com/v1/chat/completions"
headers = {"Authorization": "Bearer YOUR_BITA_API_KEY"}
common_messages = [
{
"role": "system",
"content": "你是一名合同审阅助手。请根据以下固定规则和合同正文回答问题……",
}
]
for question in ["这份合同的付款条件是什么?", "违约责任有哪些?"]:
response = requests.post(
url,
headers=headers,
json={
"model": "YOUR_MODEL_ID",
"messages": common_messages
+ [{"role": "user", "content": question}],
},
timeout=120,
)
response.raise_for_status()
result = response.json()
usage = result.get("usage", {})
details = usage.get("prompt_tokens_details", {})
print("cached_tokens:", details.get("cached_tokens", 0))
第一次请求通常不会有缓存读取;应等待第一次请求开始返回或完成后,再发送后续请求观察命中情况。具体可缓存的最小长度和有效期由模型决定。
Anthropic Messages
支持显式缓存的 Claude 模型可以在稳定内容末尾添加 cache_control。下面把固定系统提示词标记为临时缓存:
import requests
response = requests.post(
"https://www.bita-api.com/v1/messages",
headers={
"x-api-key": "YOUR_BITA_API_KEY",
"anthropic-version": "2023-06-01",
},
json={
"model": "YOUR_MODEL_ID",
"max_tokens": 1024,
"system": [
{
"type": "text",
"text": "你是一名合同审阅助手。请根据以下固定规则和合同正文回答问题……",
"cache_control": {"type": "ephemeral"},
}
],
"messages": [
{
"role": "user",
"content": "这份合同的付款条件是什么?",
}
],
},
timeout=120,
)
response.raise_for_status()
result = response.json()
usage = result.get("usage", {})
print("cache_creation_input_tokens:", usage.get("cache_creation_input_tokens", 0))
print("cache_read_input_tokens:", usage.get("cache_read_input_tokens", 0))
首次请求通常显示缓存写入,后续相同前缀的请求才可能显示缓存读取。cache_control 的位置、可用类型和有效期必须由目标模型及渠道支持;如果出现参数错误,应先移除该字段验证基础请求是否正常。
Gemini 原生接口
部分 Gemini 模型会自动对重复前缀使用隐式缓存,不需要修改请求。Gemini 的 generateContent 接口还可能支持创建带有效期的显式缓存对象,再通过缓存名称引用固定内容。
显式缓存涉及由 Gemini 服务创建、查询、更新和删除缓存对象,并非所有 Bita API 模型渠道都会开放这些管理端点。Bita API 不会代替 Gemini 创建自己的缓存对象。使用前应先确认目标模型、渠道和管理端点均受支持,不要仅根据模型名称推断功能可用。
怎样提高缓存命中率
- 先稳定,后变化:把系统提示词、工具和固定资料放在请求开头。
- 追加对话,不改历史:多轮对话应在末尾追加新消息,避免改写较早的消息。
- 固定工具定义:保持工具顺序、描述和 JSON Schema 一致。
- 保持序列化一致:使用同一套模板和 JSON 生成逻辑。
- 避免无意义变化:不要在前缀中放时间戳、追踪 ID 或随机内容。
- 先完成预热请求:大量并发请求同时到达时,后续请求可能来不及读取第一条请求创建的缓存。
- 按模型分别统计:不同模型、分组或渠道的缓存不能假设可以共享。
如果需要频繁更新大文档,可以把长期不变的规则放在最前面,把更新较少的文档放在中间,把每次变化的问题放在最后。这样即使文档更新,最前面的稳定部分仍可能继续命中。
缓存怎样影响费用
缓存相关输入通常分为三类:
- 普通输入:本次需要正常处理的 Token。
- 缓存写入:首次创建或更新缓存的 Token。
- 缓存读取:本次从已有缓存复用的 Token。
可以用下面的简化公式理解:
输入费用 ≈ 普通输入 Token × 输入价格
+ 缓存写入 Token × 缓存写入价格
+ 缓存读取 Token × 缓存读取价格
不同模型不一定同时提供三种独立价格。有些模型自动写入且不单独收费,有些模型的写入价格可能高于普通输入,但读取价格更低。因此:
- 第一次请求不一定更便宜,甚至可能因为缓存写入而更贵。
- 相同前缀被重复读取多次后,才更可能产生净节省。
- 缓存只影响对应的输入 Token;动态输入和模型输出仍按各自价格计算。
- 最终费用还会受到 API 密钥分组倍率和其他计费项目影响。
模型价格参见模型定价与计费,单次请求的缓存读写数量参见用量与调用日志。
缓存与数据保存不是一回事
提示词缓存、对话状态和应用自己的响应缓存是不同概念:
| 机制 | 保存或复用的内容 | 是否重新生成回答 |
|---|---|---|
| 提示词缓存 | 已经处理过的相同输入前缀 | 是 |
| 对话状态 | 服务端保存的消息或响应对象 | 是 |
| 应用响应缓存 | 应用直接保存并返回过去的最终回答 | 否 |
缓存内容实际位于模型提供商或上游渠道,其隔离范围、有效期和数据策略也由对应服务决定。不要因为通过 Bita API 调用,就假设缓存位于 Bita API、输入内容已经脱敏,或者不会被上游暂时保留。敏感数据仍应遵循你的业务安全要求,并避免把 API 密钥、密码等秘密放入提示词。
为什么一直没有命中
按下面顺序排查:
- 确认模型提供商及实际使用的渠道支持缓存;模型广场中的缓存价格可以作为平台计费支持的参考。
- 确认两次请求使用相同的模型、接口、API 密钥分组和稳定前缀,并尽量保持在同一上游渠道。
- 对请求体做脱敏后比较,检查空格、换行、消息顺序和工具定义是否变化。
- 确认固定前缀足够长;短请求可能达不到模型的最小缓存长度。
- 在缓存有效期内重试,并先等待第一条请求开始返回或完成。
- 检查上游响应中的
usage,再对照 Bita API 使用日志;两处都没有缓存数据时,上游可能未报告缓存用量。 - 如果使用
cache_control或显式缓存对象,确认当前渠道接受对应字段和端点。
即使设计正确,缓存也不保证每次命中。生产环境应把缓存视为一种成本和延迟优化,不能把业务正确性依赖在缓存一定存在上。