为什么 Codex Cache 看起来降低了,以及为什么我们现在更推荐 WS 配置

Posted April 4, 2026 by XAI 技术团队 ‐ 13 min read

最近一段时间,如果你在用 Codex through api.xairouter.com,可能会注意到一个现象:

某些链路上的 cache 命中看起来没有以前那么“夸张”了。

这不是错觉,而且这次变化是有意为之

我们这篇文章想说明四件事:

  1. 为什么你看到的 cache 会降低。
  2. 为什么旧的“高 cache”方案本质上改了请求语义。
  3. 为什么现在的方案虽然更克制,却更适合生产。
  4. 为什么我们更推荐使用原生 Responses WebSocket 配置来获得超高 cache。

OpenAI 官方 Prompt Caching 101

在继续讨论 XAI Router / Codex-Cloud 之前,先把 OpenAI 官方 Prompt Caching 文档中的几个关键点说清楚。

根据官方文档,Prompt Caching 的核心不是“记住上一次回答”,而是:

  • 完全相同的 prompt 前缀做复用
  • 静态内容越靠前,越容易命中
  • 动态内容越靠后,越不容易破坏命中
  • prompt_cache_key 会和前缀 hash 一起帮助请求更稳定地路由到更可能已有缓存的机器

官方还明确给出了几条非常重要的工程结论:

  1. 对于较长请求,Prompt Caching 可能显著降低延迟与输入 token 成本。
  2. 最佳实践是把稳定内容放前面,把用户动态内容放后面。
  3. prompt_cache_key 要稳定使用,但粒度不能过粗;如果某个固定前缀与同一个 key 在每分钟内过热,缓存效果反而可能下降。
  4. Prompt cache 不会改变输出语义;缓存的是 prompt 预填充,不是把上一次回答直接抄回来。
  5. Prompt cache 不会跨组织共享;即使 prompt 一样,跨组织也不会共用缓存。

下面这张图来自 OpenAI 官方 Prompt Caching 文档,可以直接帮助理解“前缀一致时 hit,不一致时 miss”的核心机制:

OpenAI Prompt Caching 可视化:相同前缀命中,不同前缀 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。

它实际上混合了两种收益:

  1. 真正的 Prompt Cache 命中
  2. 隐式会话连续性带来的上下文复用

对于用户来说,表面上看到的是:

  • 重复前缀好像更容易命中
  • 同一个任务好像更容易继续

但对系统来说,这其实是在做一件更危险的事:

把原本独立的请求偷偷改成了持续中的同一会话。

这就是为什么我们后来确认:

最早那种“高 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 配合,让工作流更稳定。


最适合这份配置的使用方式

这份配置最适合:

  1. 长时间连续编码任务
  2. 同一仓库内的多轮迭代
  3. 工具定义和 instructions 相对稳定的任务
  4. 希望 cache 很高,但又不希望系统偷偷改会话语义的用户

简单说:

它适合“同一任务长时间深入”,不适合“故意让多个不同任务共享隐式上下文”。


如果你确实更在意 cache,而不是严格语义

我们不建议重新打开旧的隐式续链方案。

原因很明确:

  • 它确实可能让 cache 更高
  • 但它也不可避免地更改了请求语义
  • 这会让“问这次,回上次”重新成为真实风险

所以当前更合理的建议是:

  • 想要超高 cache:走原生 Responses WS
  • 想要显式续链:明确传 session_id / conversation_id / previous_response_id
  • 想要安全稳定:不要再依赖隐式升级

最后的总结

最近 cache 看起来降低,不是因为我们把系统做差了,而是因为我们主动去掉了“通过隐式续链换来的那部分命中”。

现在的策略更清晰:

  • prompt_cache_key 负责 cache
  • session_id / conversation_id / previous_response_id 负责显式续链
  • xai 的本地 sticky 只负责账号亲和
  • WebSocket 是当前最容易拿到超高 cache 且不改语义的路径

如果你希望:

  • cache 足够高
  • 性能足够好
  • 语义又足够干净

那么当前最推荐的答案不是回退到最早的隐式高 cache 方案,而是:

直接使用原生 Responses WebSocket 配置。