18 min readAdmin技术

Codex 如何使用和优化 Prompt Caching:深度技术解析

基于 OpenAI Prompt Caching 201 技术文档与 Codex Agent Loop 架构分析


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
text
┌─────────────────────────────────────────────────────────┐
│                    请求 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。其核心流程:

text
┌──────────────────────────────────────────────────────────┐
│                    Codex Agent Loop                       │
│                                                          │
│  ┌─────────┐    ┌─────────┐    ┌─────────┐              │
│  │ 思考/规划 │───▶│ 工具调用 │───▶│ 观察结果 │              │
│  └─────────┘    └─────────┘    └─────────┘              │
│       ▲                                    │             │
│       └────────────────────────────────────┘             │
│                    迭代直到完成                            │
└──────────────────────────────────────────────────────────┘

典型工作流程:

  1. 检查目录:列出/读取文件,理解项目结构
  2. 识别项目类型:检测语言、框架、构建系统
  3. 读取指导文件:AGENTS.md、README.md 等
  4. 迭代执行:编写补丁、运行构建、测试验证
  5. 提交变更:执行 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 团队的核心设计原则:将持久内容放在前面,动态内容追加到后面

text
┌─────────────────────────────────────────────────────────────┐
│                    Codex 请求结构                              │
├─────────────────────────────────────────────────────────────┤
│  ① 系统指令 (System Instructions)           ← 稳定,可缓存   │
│  ② 工具定义 (Tool Definitions)              ← 稳定,可缓存   │
│  ③ 沙箱配置 (Sandbox Configuration)         ← 稳定,可缓存   │
│  ④ 环境上下文 (Environment Context)          ← 稳定,可缓存   │
│  ⑤ 历史消息 (Previous Messages)             ← 追加,可缓存   │
│  ⑥ 新用户输入 (New User Input)              ← 动态,不缓存   │
└─────────────────────────────────────────────────────────────┘

关键实践:

  • 系统指令、工具定义、沙箱配置在请求间保持一致且顺序固定
  • 新消息追加而非修改早期内容
  • 避免在前缀中放置时间戳等动态内容(使用 metadata 字段替代)

3.2 工具与 Schema 一致性#

工具定义和 schema 是缓存前缀的一部分(在开发者指令之前注入)。任何变动都会使缓存失效:

  • Schema 键名变更
  • 工具顺序调整
  • 指令内容修改

优化技巧:使用 allowed_tools 调整工具而不破坏缓存

Python
# 完整工具集保持在缓存前缀中(静态):
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 APIprevious_response_id 持久化思维链✅ 完整支持
Chat Completions无思维链持久化❌ 隐藏 CoT 被丢弃

4. Compaction:上下文压缩与缓存的博弈#

4.1 Codex 的 Compaction 机制#

Codex 不使用传统的基于摘要的压缩,而是采用专有的上下文管理方式:

  • 传递一个压缩的、加密的对象,保留原始对话的潜在空间表示
  • 使用专用端点 /responses/compact 执行压缩
  • 在对话项列表中包含特殊的 type=compaction 项,带有不透明的 encrypted_content
Python
# 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 本质上是矛盾的——一个追求动态性,另一个追求稳定性。

text
┌─────────────────────────────────────────────────────────┐
│              Compaction 对缓存的影响                       │
│                                                         │
│  未压缩时:                                              │
│  [系统指令][工具][历史消息1][历史消息2][历史消息3][新输入]  │
│  ↑ 缓存命中 ↑                                            │
│                                                         │
│  压缩后:                                                │
│  [系统指令][工具][压缩摘要][历史消息3][新输入]              │
│  ↑ 缓存失效 ↑  (前缀结构改变)                             │
└─────────────────────────────────────────────────────────┘

当执行以下操作时,缓存会失效:

  • 删除早期对话轮次
  • 摘要/压缩历史消息
  • 修剪超出上下文窗口的内容

4.3 Compaction Amnesia 问题#

社区观察到一个关键问题:LLM 推理质量在超过 100-150k token 后显著下降,而 Compaction 正是在推理质量最低时执行摘要,导致"压缩失忆"。

平衡策略:

  • 使用 evals 选择压缩方法和频率
  • 平衡 token 减少的成本节省与缓存收益
  • 考虑保留更多上下文以维持缓存命中率

5. 优化策略与最佳实践#

5.1 前缀稳定化(最高优先级)#

这是最低成本、最高收益的优化

DO ✅

Python
# 稳定的请求结构
messages = [
    {"role": "system", "content": SYSTEM_INSTRUCTIONS},  # 固定
    {"role": "system", "content": TOOL_DEFINITIONS},      # 固定
    {"role": "system", "content": ENVIRONMENT_CONTEXT},   # 固定
    # ... 历史消息追加 ...
    {"role": "user", "content": user_input}               # 动态
]

DON'T ❌

Python
# 在前缀中插入动态内容
messages = [
    {"role": "system", "content": f"当前时间: {datetime.now()}"},  # 破坏缓存!
    {"role": "system", "content": SYSTEM_INSTRUCTIONS},
    # ...
]

5.2 确保前缀超过 1024 Token#

低于 1024 token 的前缀永远不会被缓存。反直觉的是,稍长但稳定的前缀可能更便宜:

场景前缀长度缓存率实际成本
短前缀900 token0%100%
延长前缀1,100 token50%67%
延长前缀1,100 token70%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 张量保留:

JSON
{
  "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 控制保留比例:

JSON
{
  "event": "session.update",
  "session": {
    "truncation": {
      "type": "retention_ratio",
      "retention_ratio": 0.7
    }
  }
}

这会以更大的块进行截断,创建更稳定的前缀,代价是一次性丢失更多对话历史。


6. 成本与延迟影响#

6.1 各模型缓存折扣#

模型输入 ($/1M)缓存输入 ($/1M)折扣
gpt-4o$2.50$1.2550%
gpt-4.1$2.00$0.5075%
gpt-5-nano$0.05$0.00590%
gpt-5.2$1.75$0.17590%
gpt-realtime (音频)$32.00$0.4098.75%

6.2 延迟影响#

在 2,300 次提示运行的测试中:

前缀长度TTFT 改善
1,024 token7%
150k+ token67%

结论:输入越长,缓存对首 token 延迟的收益越大。

6.3 实际案例:97% 缓存命中率#

某开发者报告实现了 97% 缓存命中率,成本降低约 5.9 倍


7. 实战代码示例#

7.1 监控缓存命中#

Python
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}%)")

响应中的缓存信息:

JSON
{
  "usage": {
    "prompt_tokens": 2006,
    "completion_tokens": 300,
    "total_tokens": 2306,
    "prompt_tokens_details": {
      "cached_tokens": 1920
    }
  }
}

7.2 Codex 风格的 Agent Loop 实现#

Python
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 保持缓存#

Python
# 完整工具集(静态,保持在缓存前缀中)
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 与缓存平衡#

Python
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.output

8. 常见问题排查#

8.1 缓存命中率低的原因#

原因检查方法
工具/schema 变更对比连续请求的工具定义
朴素截断检查是否因上下文窗口限制而截断前缀
指令/系统提示变更对比系统消息内容
reasoning effort 变更检查 reasoning_effort 参数
缓存过期检查请求间隔是否超过 24 小时
前缀中插入动态内容检查时间戳、随机值等
使用 Chat Completions + 推理模型切换到 Responses API

8.2 调试工具#

Python
# 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 最佳实践#

Python
# ❌ 过于细粒度(每请求一个 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 优化的核心原则#

  1. 稳定前缀:将持久内容(指令、工具、配置)放在前面,动态内容追加到后面
  2. 监控指标:通过 cached_tokens 和 Usage Dashboard 持续跟踪缓存命中率
  3. 理解限制:每前缀 + key 组合约 15 RPM,超出会溢出到新机器
  4. 善用 prompt_cache_key:在路由粘性和扩展性之间找到平衡
  5. 选择正确的 API:Responses API 比 Chat Completions 缓存利用率高 40-80%
  6. 平衡压缩与缓存:Compaction 节省 token 但破坏缓存,需要根据场景权衡

优化检查清单

  • 前缀是否超过 1024 token?
  • 系统指令和工具定义是否在请求间保持一致?
  • 是否避免了前缀中的动态内容(时间戳、随机值)?
  • 是否使用了 prompt_cache_key 提高路由粘性?
  • 是否使用 Responses API 而非 Chat Completions?
  • 是否监控了 cached_tokens 指标?
  • Compaction 策略是否平衡了成本与缓存收益?

参考资料


相关文章