Prompt Caching via Exact Prefix Preservation
问题
包含大量工具调用的长时Agent对话会遭遇二次方级性能衰减问题:
- JSON载荷持续膨胀:每次迭代都会将完整的对话历史发送至API
- 高昂的重复计算开销:若未启用缓存,模型会反复处理相同的静态内容
- ZDR限制:零数据保留(Zero Data Retention,ZDR)策略禁止留存服务端状态,因此无法采用
previous_response_id优化方案 - 配置变更影响:对话中途的配置修改(如沙箱、工具、工作目录变更)会破坏缓存效率
如果缺乏合适的缓存策略,随着对话时长增加,推理成本与延迟会呈二次方级增长。
方案
通过精确前缀保留提升提示词缓存(prompt cache)效率——始终追加新消息而非修改已有消息,并合理排序消息以最大化缓存命中。
核心要点:提示词缓存仅基于精确前缀匹配生效。若某请求的前N个token与过往请求匹配,则可复用已缓存的计算结果。
消息排序策略:
-
静态内容前置(位于prompt开头,所有请求共享缓存):
- 系统消息(若由服务器控制)
- 工具定义(需保持固定顺序)
- 开发者指令
- 用户/项目专属指令
-
可变内容后置(位于prompt末尾,随请求动态变化):
- 用户消息
- 助手消息
- 工具调用结果(迭代追加)
通过插入实现配置变更: 当对话中途需调整配置时,插入新消息而非修改已有消息:
[静态前缀...]
<sandbox_config_v1> // 初始配置消息
[对话内容...]
<sandbox changed>
<sandbox_config_v2> // 新增的插入消息
[对话继续...]
这种方式可保留所有历史消息的精确前缀,维持缓存命中效果。
导致缓存命中失效的操作:
- 修改可用工具列表(位置敏感)
- 调整消息顺序
- 修改已有消息内容
- 切换模型(会影响服务器端系统消息)
面向ZDR(零数据留存)的无状态设计:
避免使用previous_response_id以支持零数据留存(ZDR)。转而依靠提示词缓存实现线性性能:
不使用previous_response_id:
- 网络流量呈二次方增长(每次发送完整JSON)
- 采样成本呈线性增长(得益于提示词缓存)
使用previous_response_id:
- 网络流量呈线性增长
- 但违反ZDR要求(服务器必须存储对话状态)
如何使用
Prompt构建检查清单
- 按稳定性排序消息:静态内容 → 可变内容
- 绝不修改现有消息:始终追加新消息
- 保持工具顺序一致:以确定性顺序枚举工具
- 插入而非更新:配置变更时添加新消息
配置变更处理
| 变更类型 | 禁止操作 | 正确操作 |
|------------------|------------------------|--------------------------------------|
| 沙箱/审批模式 | 修改权限消息 | 插入新的role=developer消息 |
| 工作目录 | 修改环境消息 | 插入新的role=user消息 |
| 工具列表 | 对话中途变更 | 尽可能避免;否则会破坏缓存 |
MCP服务器注意事项
MCP服务器会推送notifications/tools/list_changed事件,用于标识工具列表发生变更。请勿在对话中途响应此事件,否则会破坏缓存命中效果。建议采用以下替代方案:
- 将工具刷新操作延迟至对话边界节点执行
- 或接受缓存未命中作为必要的权衡代价
实现示例
function buildPrompt(state: ConversationState): Prompt {
const items: PromptItem[] = [];
// 静态前缀(可缓存)
items.push({ role: 'system', content: state.systemMessage });
items.push({ type: 'tools', tools: state.tools }); // 务必保持顺序一致!
items.push({ role: 'developer', content: state.instructions });
// 可变内容(追加式添加)
items.push(...state.history);
return { items };
}
function handleConfigChange(
state: ConversationState,
newConfig: SandboxConfig
): ConversationState {
// 禁止:修改现有权限消息
// 正确:插入新消息
return {
...state,
history: [
...state.history,
{
role: 'developer',
content: formatSandboxConfig(newConfig),
},
],
};
}
权衡
优点:
- 线性采样成本:Prompt caching 让重复推理的复杂度从二次方降至线性
- 兼容ZDR:无状态设计支持零数据保留(Zero Data Retention,ZDR)政策
- 无服务器状态:规避
previous_response_id带来的复杂度 - 简洁的概念模型:精确前缀匹配的逻辑易于理解和推理
缺点:
- 二次方网络流量:JSON负载大小仍呈二次方增长(仅采样过程被缓存)
- 缓存脆弱性:对话中途的变更(如工具、模型切换)会破坏前缀匹配,导致缓存失效
- 需严格控制内容顺序:所有静态内容必须置于可变内容之前
- 工具枚举复杂度高:必须维持工具顺序的一致性
- MCP服务器限制:工具的动态变更会引发缓存未命中
参考文献
包含OpenAI Codex相关技术资源,涵盖Codex智能体循环解析博客、提示缓存官方文档、Codex命令行工具GitHub仓库,以及关联的上下文窗口自动压缩技术模式。
- 相关内容:上下文窗口自动压缩
来源摘要
来源: https://openai.com/index/unrolling-the-codex-agent-loop/