1. Prompt Caching 核心原理#
1.1 技术机制:跳过 Prefill 计算#
Transformer 推理的核心开销在于注意力层的前向传播。处理每个 token 时,模型会将其投影为 Query (Q)、Key (K)、Value (V) 向量。KV Cache 存储了已处理前缀在所有层和注意力头上的这些张量。
当新请求共享相同前缀时,模型直接复用缓存的 KV 张量,仅对新增(后缀)token 执行注意力计算。这意味着:
- 缓存命中时:跳过前缀部分的全部计算,TTFT(首 token 延迟)降低最高约 80%
- 精确前缀匹配:必须从第一个 token 开始完全一致,任何早期变动都会使缓存失效
- 128 token 增量:缓存命中的最小粒度为 128 token
┌─────────────────────────────────────────────────────────┐
│ 请求 Token 序列 │
├─────────────────────────┬───────────────────────────────┤
│ 缓存前缀 (Cache Hit) │ 新增后缀 (需计算) │
│ 系统指令 + 工具定义 + │ 用户输入 + 新消息 + │
│ 环境上下文 + 历史消息 │ 动态内容 │
└─────────────────────────┴───────────────────────────────┘
↓ ↓
复用 KV 张量 执行注意力计算
(零计算开销) (正常推理)1.2 缓存激活条件#
| 条件 | 说明 |
|---|---|
| 最小 token 数 | ≥ 1024 token |
| 命中粒度 | 128 token 增量 |
| 缓存方式 | 精确前缀匹配(exact prefix matching) |
| 路由依赖 | 相同前缀的请求需落在同一台机器上 |
| 默认保留 | 内存缓存自动生效;扩展缓存保留 KV 张量 24 小时 |
1.3 可缓存内容#
整个请求前缀都可被缓存,包括:
- 系统消息(system instructions)
- 工具定义(tool definitions)
- 结构化输出 schema
- 图片、音频等多模态输入
- 历史对话消息
2. Codex Agent Loop 架构#
2.1 核心循环模式#
Codex 本质上是一个在循环中运行的工具调用 LLM。其核心流程:
┌──────────────────────────────────────────────────────────┐
│ Codex Agent Loop │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 思考/规划 │───▶│ 工具调用 │───▶│ 观察结果 │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ ▲ │ │
│ └────────────────────────────────────┘ │
│ 迭代直到完成 │
└──────────────────────────────────────────────────────────┘典型工作流程:
- 检查目录:列出/读取文件,理解项目结构
- 识别项目类型:检测语言、框架、构建系统
- 读取指导文件:AGENTS.md、README.md 等
- 迭代执行:编写补丁、运行构建、测试验证
- 提交变更:执行 git 命令完成提交
2.2 工具集#
Codex 的核心工具包括:
| 工具 | 功能 |
|---|---|
run_command | 执行任意终端命令 |
read_file | 读取文件内容到上下文 |
write_file / patch_file | 写入或修补文件 |
web_search | 网络搜索 |
其中 run_command 最为强大——它使 Codex 能够运行任何命令,无需预编程工具知识。
2.3 Responses API 与推理项持久化#
Codex 使用 OpenAI 的 Responses API 而非 Chat Completions API。关键区别在于:
- Responses API 通过
previous_response_id或加密推理项(encrypted reasoning items)在轮次间持久化原始思维链 token - Chat Completions 没有这种持久化机制
- 这意味着 Responses API 的缓存利用率比 Chat Completings 高 40-80%
3. Codex 如何利用 Prompt Caching#
3.1 稳定前缀策略#
Codex 团队的核心设计原则:将持久内容放在前面,动态内容追加到后面。
┌─────────────────────────────────────────────────────────────┐
│ Codex 请求结构 │
├─────────────────────────────────────────────────────────────┤
│ ① 系统指令 (System Instructions) ← 稳定,可缓存 │
│ ② 工具定义 (Tool Definitions) ← 稳定,可缓存 │
│ ③ 沙箱配置 (Sandbox Configuration) ← 稳定,可缓存 │
│ ④ 环境上下文 (Environment Context) ← 稳定,可缓存 │
│ ⑤ 历史消息 (Previous Messages) ← 追加,可缓存 │
│ ⑥ 新用户输入 (New User Input) ← 动态,不缓存 │
└─────────────────────────────────────────────────────────────┘关键实践:
- 系统指令、工具定义、沙箱配置在请求间保持一致且顺序固定
- 新消息追加而非修改早期内容
- 避免在前缀中放置时间戳等动态内容(使用
metadata字段替代)
3.2 工具与 Schema 一致性#
工具定义和 schema 是缓存前缀的一部分(在开发者指令之前注入)。任何变动都会使缓存失效:
- Schema 键名变更
- 工具顺序调整
- 指令内容修改
优化技巧:使用 allowed_tools 调整工具而不破坏缓存
# 完整工具集保持在缓存前缀中(静态):
tools = [get_weather_def, get_location_def, calendar_def, ...]
# 每次调用通过 allowed_tools 限制可用工具(在请求元数据中,不在前缀中):
allowed_tools = {"mode": "auto", "tools": ["get_weather", "get_location"]}3.3 prompt_cache_key 路由粘性#
请求基于前约 256 个 token 的哈希进行路由。提供 prompt_cache_key 可增加路由粘性:
- 某编码客户使用此参数将缓存命中率从 60% 提升到 87%
- 每个推理引擎处理约 15 请求/分钟(每前缀 +
prompt_cache_key组合) - 超出流量会溢出到新机器(产生一次性缓存未命中)
编码场景的粒度建议:
| 策略 | 适用场景 | 效果 |
|---|---|---|
| 每用户 key | 同一代码库的跨会话复用 | 提高个人工作流缓存命中 |
| 每会话 key | 大量无关并行线程 | 更好的扩展性 |
| 用户分组 bucket | 多用户共享缓存 | 平衡命中率与扩展性 |
3.4 Responses API 优势#
内部基准测试显示,Responses API 的缓存利用率比 Chat Completions 高 40-80%:
| API | 缓存机制 | 推理模型支持 |
|---|---|---|
| Responses API | previous_response_id 持久化思维链 | ✅ 完整支持 |
| Chat Completions | 无思维链持久化 | ❌ 隐藏 CoT 被丢弃 |
4. Compaction:上下文压缩与缓存的博弈#
4.1 Codex 的 Compaction 机制#
Codex 不使用传统的基于摘要的压缩,而是采用专有的上下文管理方式:
- 传递一个压缩的、加密的对象,保留原始对话的潜在空间表示
- 使用专用端点
/responses/compact执行压缩 - 在对话项列表中包含特殊的
type=compaction项,带有不透明的encrypted_content
# Codex 的 Compaction 调用
response = client.responses.create(
model="gpt-5.2-codex",
input=conversation,
store=False,
context_management=[{
"type": "compaction",
"compact_threshold": 100000
}],
)4.2 Compaction 与缓存的冲突#
上下文工程(决定每次请求输入什么)与 prompt caching 本质上是矛盾的——一个追求动态性,另一个追求稳定性。
┌─────────────────────────────────────────────────────────┐
│ Compaction 对缓存的影响 │
│ │
│ 未压缩时: │
│ [系统指令][工具][历史消息1][历史消息2][历史消息3][新输入] │
│ ↑ 缓存命中 ↑ │
│ │
│ 压缩后: │
│ [系统指令][工具][压缩摘要][历史消息3][新输入] │
│ ↑ 缓存失效 ↑ (前缀结构改变) │
└─────────────────────────────────────────────────────────┘当执行以下操作时,缓存会失效:
- 删除早期对话轮次
- 摘要/压缩历史消息
- 修剪超出上下文窗口的内容
4.3 Compaction Amnesia 问题#
社区观察到一个关键问题:LLM 推理质量在超过 100-150k token 后显著下降,而 Compaction 正是在推理质量最低时执行摘要,导致"压缩失忆"。
平衡策略:
- 使用 evals 选择压缩方法和频率
- 平衡 token 减少的成本节省与缓存收益
- 考虑保留更多上下文以维持缓存命中率
5. 优化策略与最佳实践#
5.1 前缀稳定化(最高优先级)#
这是最低成本、最高收益的优化。
DO ✅
# 稳定的请求结构
messages = [
{"role": "system", "content": SYSTEM_INSTRUCTIONS}, # 固定
{"role": "system", "content": TOOL_DEFINITIONS}, # 固定
{"role": "system", "content": ENVIRONMENT_CONTEXT}, # 固定
# ... 历史消息追加 ...
{"role": "user", "content": user_input} # 动态
]DON'T ❌
# 在前缀中插入动态内容
messages = [
{"role": "system", "content": f"当前时间: {datetime.now()}"}, # 破坏缓存!
{"role": "system", "content": SYSTEM_INSTRUCTIONS},
# ...
]5.2 确保前缀超过 1024 Token#
低于 1024 token 的前缀永远不会被缓存。反直觉的是,稍长但稳定的前缀可能更便宜:
| 场景 | 前缀长度 | 缓存率 | 实际成本 |
|---|---|---|---|
| 短前缀 | 900 token | 0% | 100% |
| 延长前缀 | 1,100 token | 50% | 67% |
| 延长前缀 | 1,100 token | 70% | 45% |
5.3 使用 Flex Processing 代替 Batch API#
Flex Processing(service_tier="flex")提供与 Batch 相同的 50% token 折扣,但具有更多控制:
- 请求速率调优
- 扩展 prompt caching 支持
prompt_cache_key支持
在 10,000 个相同请求的对比测试中:
- Flex 的缓存命中率比 Batch 高 8.5%
- 输入 token 成本降低 23%
- GPT-5 之前的推理模型(o3、o4-mini)在 Batch 上不支持缓存
5.4 利用扩展缓存(Extended Caching)#
对于 gpt-5.5、gpt-5.5-pro 及以后的模型,默认启用 24 小时 KV 张量保留:
{
"model": "gpt-5.1",
"input": "Write me a haiku...",
"prompt_cache_retention": "24h"
}重要: 缓存的只是 KV 张量(隐藏状态的键/值投影)——中间数值表示。无论保留策略如何,原始文本或多模态输入永远不会被存储。
5.5 Realtime API 的 retention_ratio 优化#
Realtime API 的上下文窗口较短(32k),默认截断模式(auto)会增量删除旧消息,导致每轮都发生缓存未命中。
使用 retention_ratio 控制保留比例:
{
"event": "session.update",
"session": {
"truncation": {
"type": "retention_ratio",
"retention_ratio": 0.7
}
}
}这会以更大的块进行截断,创建更稳定的前缀,代价是一次性丢失更多对话历史。
6. 成本与延迟影响#
6.1 各模型缓存折扣#
| 模型 | 输入 ($/1M) | 缓存输入 ($/1M) | 折扣 |
|---|---|---|---|
| gpt-4o | $2.50 | $1.25 | 50% |
| gpt-4.1 | $2.00 | $0.50 | 75% |
| gpt-5-nano | $0.05 | $0.005 | 90% |
| gpt-5.2 | $1.75 | $0.175 | 90% |
| gpt-realtime (音频) | $32.00 | $0.40 | 98.75% |
6.2 延迟影响#
在 2,300 次提示运行的测试中:
| 前缀长度 | TTFT 改善 |
|---|---|
| 1,024 token | 7% |
| 150k+ token | 67% |
结论:输入越长,缓存对首 token 延迟的收益越大。
6.3 实际案例:97% 缓存命中率#
某开发者报告实现了 97% 缓存命中率,成本降低约 5.9 倍。
7. 实战代码示例#
7.1 监控缓存命中#
from openai import OpenAI
client = OpenAI()
response = client.responses.create(
model="gpt-5.2",
input="Explain quantum computing...",
)
# 检查缓存命中
cached_tokens = response.usage.prompt_tokens_details.cached_tokens
total_tokens = response.usage.prompt_tokens
cache_hit_rate = cached_tokens / total_tokens * 100
print(f"缓存命中: {cached_tokens}/{total_tokens} tokens ({cache_hit_rate:.1f}%)")响应中的缓存信息:
{
"usage": {
"prompt_tokens": 2006,
"completion_tokens": 300,
"total_tokens": 2306,
"prompt_tokens_details": {
"cached_tokens": 1920
}
}
}7.2 Codex 风格的 Agent Loop 实现#
from openai import OpenAI
client = OpenAI()
# 稳定的系统指令(缓存友好)
SYSTEM_INSTRUCTIONS = """You are a coding assistant. You can:
- Read and write files
- Run terminal commands
- Search the web
Always follow these steps:
1. Understand the task
2. Explore the codebase
3. Plan your changes
4. Implement and test
5. Commit your work"""
# 稳定的工具定义(缓存友好)
TOOLS = [
{"type": "function", "function": {"name": "run_command", ...}},
{"type": "function", "function": {"name": "read_file", ...}},
{"type": "function", "function": {"name": "write_file", ...}},
]
def agent_loop(user_request: str, max_iterations: int = 10):
"""Codex 风格的 Agent Loop"""
conversation = [
{"role": "system", "content": SYSTEM_INSTRUCTIONS},
{"role": "user", "content": user_request}
]
for i in range(max_iterations):
response = client.responses.create(
model="gpt-5.2-codex",
input=conversation,
tools=TOOLS,
# 使用 prompt_cache_key 提高路由粘性
prompt_cache_key="my-coding-agent",
)
# 检查是否有工具调用
if response.output[0].type == "function_call":
# 执行工具
result = execute_tool(response.output[0])
# 追加结果(不修改历史,保持缓存)
conversation.append({"role": "assistant", "output": response.output})
conversation.append({"role": "tool", "output": result})
else:
# 完成
return response.output[0].content
return "达到最大迭代次数"7.3 使用 allowed_tools 保持缓存#
# 完整工具集(静态,保持在缓存前缀中)
ALL_TOOLS = [
{"type": "function", "function": {"name": "read_file", ...}},
{"type": "function", "function": {"name": "write_file", ...}},
{"type": "function", "function": {"name": "run_command", ...}},
{"type": "function", "function": {"name": "search_web", ...}},
{"type": "function", "function": {"name": "git_commit", ...}},
]
def create_response(conversation, allowed_tool_names: list[str]):
"""使用 allowed_tools 限制可用工具,同时保持缓存"""
return client.responses.create(
model="gpt-5.2",
input=conversation,
tools=ALL_TOOLS,
tool_choice={
"mode": "auto",
"tools": allowed_tool_names # 动态限制,不影响缓存前缀
}
)7.4 Compaction 与缓存平衡#
def smart_conversation_management(conversation: list, threshold: int = 100000):
"""智能对话管理:平衡压缩与缓存"""
# 估算当前 token 数
current_tokens = estimate_tokens(conversation)
if current_tokens < threshold:
# 未达阈值,保持原样(维护缓存)
return conversation
# 达到阈值,执行 compaction
response = client.responses.create(
model="gpt-5.2-codex",
input=conversation,
store=False,
context_management=[{
"type": "compaction",
"compact_threshold": threshold
}],
)
# 返回压缩后的对话(包含 encrypted_content)
return response.output8. 常见问题排查#
8.1 缓存命中率低的原因#
| 原因 | 检查方法 |
|---|---|
| 工具/schema 变更 | 对比连续请求的工具定义 |
| 朴素截断 | 检查是否因上下文窗口限制而截断前缀 |
| 指令/系统提示变更 | 对比系统消息内容 |
| reasoning effort 变更 | 检查 reasoning_effort 参数 |
| 缓存过期 | 检查请求间隔是否超过 24 小时 |
| 前缀中插入动态内容 | 检查时间戳、随机值等 |
| 使用 Chat Completions + 推理模型 | 切换到 Responses API |
8.2 调试工具#
# 1. 检查单个请求的缓存状态
print(f"Cached tokens: {response.usage.prompt_tokens_details.cached_tokens}")
# 2. 使用 Usage Dashboard 查看整体缓存率
# 在 OpenAI 控制台中筛选 cached/uncached tokens
# 3. 对比请求前缀
def compare_prefixes(req1_messages, req2_messages):
"""找出前缀分歧点"""
for i, (m1, m2) in enumerate(zip(req1_messages, req2_messages)):
if m1 != m2:
print(f"分歧点: 消息 {i}")
print(f" 请求1: {m1}")
print(f" 请求2: {m2}")
return
print("前缀一致")8.3 prompt_cache_key 最佳实践#
# ❌ 过于细粒度(每请求一个 key,无复用)
prompt_cache_key=f"request-{uuid4()}"
# ❌ 过于粗粒度(所有用户共享,容易溢出)
prompt_cache_key="global"
# ✅ 按用户分组
prompt_cache_key=f"user-{user_id}"
# ✅ 按会话分组
prompt_cache_key=f"conversation-{conversation_id}"
# ✅ 按用户组分组(平衡命中率与扩展性)
group_id = hash(user_id) % 10 # 10 个 bucket
prompt_cache_key=f"group-{group_id}"9. 总结#
Codex Prompt Caching 优化的核心原则#
- 稳定前缀:将持久内容(指令、工具、配置)放在前面,动态内容追加到后面
- 监控指标:通过
cached_tokens和 Usage Dashboard 持续跟踪缓存命中率 - 理解限制:每前缀 + key 组合约 15 RPM,超出会溢出到新机器
- 善用
prompt_cache_key:在路由粘性和扩展性之间找到平衡 - 选择正确的 API:Responses API 比 Chat Completions 缓存利用率高 40-80%
- 平衡压缩与缓存:Compaction 节省 token 但破坏缓存,需要根据场景权衡
优化检查清单
- 前缀是否超过 1024 token?
- 系统指令和工具定义是否在请求间保持一致?
- 是否避免了前缀中的动态内容(时间戳、随机值)?
- 是否使用了
prompt_cache_key提高路由粘性? - 是否使用 Responses API 而非 Chat Completions?
- 是否监控了
cached_tokens指标? - Compaction 策略是否平衡了成本与缓存收益?
参考资料
- OpenAI Prompt Caching 201 - 缓存机制详解
- Unrolling the Codex Agent Loop - Codex 架构分析
- OpenAI API Reference - Responses - API 文档
- OpenAI API Reference - Conversation State - 对话状态管理


