通过 OpenAI 兼容的 Chat Completions API 或原生 Gemini API 创建和复用 Gemini 上下文缓存(Context Cache),用 cache_control 缓存稳定前缀,降低重复长内容的 token 成本。
本文介绍如何通过 OpenAI 兼容的 Chat Completions API 或原生 Gemini API 创建和复用 Gemini 上下文缓存(Context Cache)。
使用前准备:
export API_KEY="你的API密钥"
适用场景
当多个请求会重复携带相同的大段内容时,可以缓存稳定前缀,例如:
- 超长 system prompt
- 固定的知识库或产品文档
- 多轮对话中的稳定历史消息
- 重复使用的工具定义和说明
Context Cache 适合“前面内容保持不变,最后的问题持续变化”的请求。
核心用法
在稳定前缀最后一条消息的 content block 上添加 cache_control:
{
"type": "text",
"text": "这是稳定前缀的最后一段内容",
"cache_control": {
"type": "ephemeral",
"ttl": "5m"
}
}
支持的 TTL:
| TTL | 含义 |
|---|---|
5m |
缓存 5 分钟 |
1h |
缓存 1 小时 |
消息结构
推荐使用以下结构:
system
→ 稳定的长文本或历史消息
→ 带 cache_control 的稳定前缀边界
→ 当前用户问题(不缓存)
cache_control 所在消息及它之前的所有消息组成缓存前缀。它后面必须至少有一条实时消息。
OpenAI 兼容请求示例
curl "https://api.openveer.com/v1/chat/completions" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gemini-3.6-flash",
"stream": false,
"messages": [
{
"role": "system",
"content": "请严格根据提供的参考资料回答问题。"
},
{
"role": "user",
"content": "这里放需要重复使用的长篇参考资料……"
},
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "我已阅读并理解以上参考资料。",
"cache_control": {
"type": "ephemeral",
"ttl": "5m"
}
}
]
},
{
"role": "user",
"content": "请总结参考资料中的三个核心观点。"
}
]
}'
第一次发送时,系统会尝试创建缓存,并使用新缓存完成当前请求。
原生 Gemini 请求示例
原生 Gemini generateContent 接口同样支持在 contents[].parts[] 中添加 cache_control:
curl "https://api.openveer.com/v1beta/models/gemini-3.6-flash:generateContent" \
-H "x-goog-api-key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"systemInstruction": {
"parts": [
{
"text": "请严格根据提供的参考资料回答问题。"
}
]
},
"contents": [
{
"role": "user",
"parts": [
{
"text": "这里放需要重复使用的长篇参考资料……"
}
]
},
{
"role": "model",
"parts": [
{
"text": "我已阅读并理解以上参考资料。",
"cache_control": {
"type": "ephemeral",
"ttl": "5m"
}
}
]
},
{
"role": "user",
"parts": [
{
"text": "请总结参考资料中的三个核心观点。"
}
]
}
]
}'
cache_control 是平台在 Gemini 请求格式上的扩展字段。平台识别边界后会在转发前移除该字段,并自动创建或复用缓存内容(cachedContent)。
流式接口使用相同请求体,只需将地址改为:
curl -N "https://api.openveer.com/v1beta/models/gemini-3.6-flash:streamGenerateContent?alt=sse" \
-H "x-goog-api-key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"contents": [
{
"role": "user",
"parts": [
{
"text": "这里放需要重复使用的长篇参考资料……",
"cache_control": {
"type": "ephemeral",
"ttl": "5m"
}
}
]
},
{
"role": "user",
"parts": [
{
"text": "请总结参考资料中的三个核心观点。"
}
]
}
]
}'
复用时保持 systemInstruction、边界之前的 contents、TTL 和 tools 不变,只修改边界之后的实时内容。
创建和复用流程
第一次发送带 cache_control 的请求时:
识别稳定前缀
→ 创建 Context Cache
→ 在当前请求中引用新缓存
→ 返回模型结果
再次发送相同稳定前缀时:
识别相同稳定前缀
→ 复用未过期的 Context Cache
→ 只发送当前实时内容
→ 返回模型结果
因此,第一次请求也可能直接返回较大的缓存命中 token。这是正常行为,并不要求先发送一次“预热请求”。
复用缓存
再次请求时,保持以下内容不变:
- 模型
cache_control之前的所有消息cache_control.ttl- 工具定义(如果使用 tools)
- 原生 Gemini 请求中的
systemInstruction
只修改边界之后的实时问题:
{
"role": "user",
"content": "参考资料中提到了哪些风险?"
}
只要稳定前缀一致且缓存未过期,系统就会复用已有缓存。
以下改动会生成不同的缓存:
- 修改稳定前缀中的文字或消息顺序
- 更换模型
- 将
5m改为1h - 修改 tools 或工具参数定义
- 使用不同的 API 用户或渠道
Python 示例
from openai import OpenAI
client = OpenAI(
api_key="YOUR_API_KEY",
base_url="https://api.openveer.com/v1",
)
stable_messages = [
{
"role": "system",
"content": "请严格根据提供的参考资料回答问题。",
},
{
"role": "user",
"content": "这里放需要重复使用的长篇参考资料……",
},
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "我已阅读并理解以上参考资料。",
"cache_control": {
"type": "ephemeral",
"ttl": "5m",
},
}
],
},
]
response = client.chat.completions.create(
model="gemini-3.6-flash",
messages=[
*stable_messages,
{
"role": "user",
"content": "请总结参考资料中的三个核心观点。",
},
],
)
print(response.choices[0].message.content)
print(response.usage)
后续请求复用同一个 stable_messages,只替换最后一条 user 消息即可。
判断是否命中
OpenAI 兼容响应
查看响应中的:
{
"usage": {
"prompt_tokens": 14430,
"prompt_tokens_details": {
"cached_tokens": 14420,
"cache_write_tokens": 0
}
}
}
字段说明:
| 字段 | 含义 |
|---|---|
prompt_tokens |
本次请求的全部输入 token |
cached_tokens |
本次从缓存读取的输入 token |
cache_write_tokens |
缓存写入 token;返回 0 是正常行为 |
首次请求也可能出现较大的 cached_tokens,因为系统可以先创建缓存,再在同一次模型调用中引用它。
原生 Gemini 响应
查看响应中的 usageMetadata.cachedContentTokenCount:
{
"usageMetadata": {
"promptTokenCount": 13926,
"cachedContentTokenCount": 13916,
"totalTokenCount": 13954
}
}
字段说明:
| 字段 | 含义 |
|---|---|
promptTokenCount |
本次请求的全部输入 token |
cachedContentTokenCount |
本次从缓存读取的输入 token |
totalTokenCount |
本次请求的输入和输出 token 总数 |
streamGenerateContent 会在 SSE 响应帧中返回相同的 usageMetadata。客户端应读取包含该字段的响应帧,不要只检查第一段文本。