为什么 Codex Cache 看起来降低了,以及为什么我们现在更推荐 WS 配置
Posted April 4, 2026 by XAI 技术团队 ‐ 13 min read

最近一段时间,如果你在用 Codex through api.xairouter.com,可能会注意到一个现象:
某些链路上的 cache 命中看起来没有以前那么“夸张”了。
这不是错觉,而且这次变化是有意为之。
我们这篇文章想说明四件事:
- 为什么你看到的 cache 会降低。
- 为什么旧的“高 cache”方案本质上改了请求语义。
- 为什么现在的方案虽然更克制,却更适合生产。
- 为什么我们更推荐使用原生 Responses WebSocket 配置来获得超高 cache。
OpenAI 官方 Prompt Caching 101
在继续讨论 XAI Router / Codex-Cloud 之前,先把 OpenAI 官方 Prompt Caching 文档中的几个关键点说清楚。
根据官方文档,Prompt Caching 的核心不是“记住上一次回答”,而是:
- 对完全相同的 prompt 前缀做复用
- 静态内容越靠前,越容易命中
- 动态内容越靠后,越不容易破坏命中
prompt_cache_key会和前缀 hash 一起帮助请求更稳定地路由到更可能已有缓存的机器
官方还明确给出了几条非常重要的工程结论:
- 对于较长请求,Prompt Caching 可能显著降低延迟与输入 token 成本。
- 最佳实践是把稳定内容放前面,把用户动态内容放后面。
prompt_cache_key要稳定使用,但粒度不能过粗;如果某个固定前缀与同一个 key 在每分钟内过热,缓存效果反而可能下降。- Prompt cache 不会改变输出语义;缓存的是 prompt 预填充,不是把上一次回答直接抄回来。
- Prompt cache 不会跨组织共享;即使 prompt 一样,跨组织也不会共用缓存。
下面这张图来自 OpenAI 官方 Prompt Caching 文档,可以直接帮助理解“前缀一致时 hit,不一致时 miss”的核心机制:
图源:OpenAI Prompt Caching Guide https://developers.openai.com/api/docs/guides/prompt-caching
把这套官方语义理解清楚之后,后面你就会更容易明白:
- 为什么“高 cache”不能靠隐式续链去换
- 为什么我们更强调稳定前缀与稳定路由
- 为什么原生 Responses WebSocket 更容易拿到超高 cache
先说结论
我们最近做的不是“关掉 cache”,而是把一类危险的隐式续链去掉了。
以前某些高 cache 路径的核心思路是:
- 让
prompt_cache_key尽量稳定 - 再把它隐式升级成
session_id/conversation_id一类的会话身份
这样做确实可能带来更高的 continuation / cache 命中,但代价也很明确:
- 这次提问有可能延续了上一次语义
- HTTP 请求会在用户不知情的情况下变成“隐式续链请求”
- 最终表现就是用户感知上的:
- “问这次,回上次”
- “明明是新问题,却像还在老会话里”
所以最近的收敛并不是“把 cache 做差了”,而是把:
cache key -> 会话身份
这条危险耦合链切断了。
为什么以前的 cache 看起来更高
因为以前那套方案吃到的,不只是 Prompt Caching。
它实际上混合了两种收益:
- 真正的 Prompt Cache 命中
- 隐式会话连续性带来的上下文复用
对于用户来说,表面上看到的是:
- 重复前缀好像更容易命中
- 同一个任务好像更容易继续
但对系统来说,这其实是在做一件更危险的事:
把原本独立的请求偷偷改成了持续中的同一会话。
这就是为什么我们后来确认:
最早那种“高 cache”方案,本质上不可避免地更改了请求语义。
它不是纯 cache 优化,而是通过隐式续链把“cache 收益”和“会话污染风险”绑在了一起。
最近到底改了什么
1. WS 收敛为显式会话
在 WebSocket 路径上,我们已经收敛成:
- 只认显式
session_id - 只认显式
conversation_id - 不再从
prompt_cache_key派生 WS 会话身份 - 不再传播内部 sticky 头作为上游会话身份
这意味着:
- WS 仍然可以拿到很高的 cache
- 但不会再因为隐藏会话升级而更容易串语义
2. HTTP 中 prompt_cache_key 只做 cache hint
在 HTTP 路径上,现在的规则是:
prompt_cache_key仍然保留在 payload 中- transformed 路径仍然可以自动补更稳定的 compat
prompt_cache_key - 但它不再被隐式升级成上游
session_id/conversation_id
也就是说:
- 你还能继续吃 Prompt Caching
- 但不会再把“缓存键”误当成“会话键”
3. XAI Router 的 sticky 只做本地账号亲和
现在的 sticky 也已经收敛成:
- 有
sessionHash才 sticky - 同一个
sessionHash会稳定落到同一个 upstream key/account - 没有
sessionHash的请求继续走普通 round-robin - sticky 不再外传到上游
所以现在本地 sticky 的职责很清楚:
帮助相似请求尽量落到同一账号,提升 cache locality。
而不是:
偷偷把不同请求变成同一个上游会话。
为什么最近你会感觉 cache 下降
因为我们主动放弃了一部分“靠隐式续链换来的命中”。
这部分命中以前看起来很漂亮,但问题在于:
- 它不够纯粹
- 它不够安全
- 它不够可解释
现在你看到的下降,主要下降的是:
- continuation 带来的表面命中
而不是:
- 真正意义上的 prompt prefix cache 能力
如果你的客户端继续:
- 保持稳定
prompt_cache_key - 保持稳定前缀
- 尽量走原生 Responses / WS 路径
那你依然可以拿到非常高的 cache。
为什么我们更推荐原生 WS 配置
如果目标是:
- 超高 cache
- 低语义污染
- 高吞吐
- 长任务更稳
那么我们当前最推荐的路线,不是“重新打开隐式续链”,而是:
直接使用原生 Responses WebSocket 模式。
原因很简单。
1. WS 天然更适合保持会话连续性
WebSocket 本来就是长连接:
- 同一个任务在同一连接内持续进行
- 上下文连续性是显式、自然、可解释的
- 不需要拿
prompt_cache_key去冒充会话身份
2. WS 内部前缀更稳定
在同一条原生 Responses WS 连接里:
- model 不会频繁变化
- instructions 更稳定
- tool schema 更稳定
- 会话上下文天然集中
这正是 Prompt Caching 最喜欢的环境。
3. 可以拿到很高 cache,但不容易串会话
我们最近的方向不是“拒绝高 cache”,而是:
用正确的协议路径拿到高 cache。
而 WS 就是当前最适合的路径。
推荐基线配置
如果你希望在 Codex 中优先使用原生 Responses WebSocket,并尽量获得超高 cache,我们推荐下面这份基线配置:
将下面内容保存到:
~/.codex/config.toml如果你已经有自己的 Codex 配置,也可以只把相关字段合并进去,而不是整份覆盖。
model_provider = "xai"
model = "gpt-5.4"
model_reasoning_effort = "xhigh"
plan_mode_reasoning_effort = "xhigh"
model_reasoning_summary = "none"
model_verbosity = "medium"
model_context_window = 1050000
model_auto_compact_token_limit = 945000
tool_output_token_limit = 6000
approval_policy = "never"
sandbox_mode = "danger-full-access"
suppress_unstable_features_warning = true
cache_ttl = "30m"
[model_providers.xai]
name = "OpenAI"
base_url = "https://api.xairouter.com"
wire_api = "responses"
requires_openai_auth = false
env_key = "XAI_API_KEY"
supports_websockets = true
[features]
responses_websockets_v2 = true
multi_agent = true
[agents]
max_threads = 4
max_depth = 1
job_max_runtime_seconds = 1800为什么这份 WS 配置更容易拿到超高 cache
1. wire_api = "responses"
这意味着请求走的是原生 Responses 语义,而不是再经由额外桥接层包装成其他格式。
对 cache 来说,路径越原生,前缀越稳定。
2. supports_websockets = true
允许客户端直接走原生 Responses WebSocket,而不是每一轮都重建 HTTP 上下文。
3. responses_websockets_v2 = true
这是当前更推荐的 Responses WebSocket 工作方式。
它的核心价值不是“花哨”,而是:
- 更接近官方语义
- 更适合多轮任务链
- 更容易维持前缀稳定
4. model = "gpt-5.4"
这是当前我们最推荐的高质量基线模型之一,同时也已经纳入自动 compat cache key 优化范围。
5. 大上下文 + 较高 compaction 阈值
model_context_window = 1050000
model_auto_compact_token_limit = 945000这意味着:
- 在任务还没必要 compact 之前,尽量保留完整稳定前缀
- 让长任务在高 cache 条件下继续工作
如果阈值太低,前缀会更早被压缩,cache 稳定性反而更容易下降。
6. cache_ttl = "30m"
这是客户端侧很实用的工程参数,适合作为高 cache 基线的一部分。
它不是“替代上游 Prompt Caching”,而是和上游 cache 配合,让工作流更稳定。
最适合这份配置的使用方式
这份配置最适合:
- 长时间连续编码任务
- 同一仓库内的多轮迭代
- 工具定义和 instructions 相对稳定的任务
- 希望 cache 很高,但又不希望系统偷偷改会话语义的用户
简单说:
它适合“同一任务长时间深入”,不适合“故意让多个不同任务共享隐式上下文”。
如果你确实更在意 cache,而不是严格语义
我们不建议重新打开旧的隐式续链方案。
原因很明确:
- 它确实可能让 cache 更高
- 但它也不可避免地更改了请求语义
- 这会让“问这次,回上次”重新成为真实风险
所以当前更合理的建议是:
- 想要超高 cache:走原生 Responses WS
- 想要显式续链:明确传
session_id/conversation_id/previous_response_id - 想要安全稳定:不要再依赖隐式升级
最后的总结
最近 cache 看起来降低,不是因为我们把系统做差了,而是因为我们主动去掉了“通过隐式续链换来的那部分命中”。
现在的策略更清晰:
prompt_cache_key负责 cachesession_id/conversation_id/previous_response_id负责显式续链xai的本地 sticky 只负责账号亲和- WebSocket 是当前最容易拿到超高 cache 且不改语义的路径
如果你希望:
- cache 足够高
- 性能足够好
- 语义又足够干净
那么当前最推荐的答案不是回退到最早的隐式高 cache 方案,而是:
直接使用原生 Responses WebSocket 配置。