Responses WebSocket 协议详解:为什么它会让 Agent 工作流更快

Responses WebSocket 协议详解:为什么它会让 Agent 工作流更快

很多人第一次看到 OpenAI 的 WebSocket mode,会直觉地把它理解成"把 HTTP 流式输出换成 WebSocket"。这个理解不算错,但也不够准确。

真正关键的点在于:Responses WebSocket 优化的不是单次文本生成本身,而是多轮、工具密集、长链路 Agent 工作流里的 continuation 成本

本文主要基于两份材料展开:

但要先强调一件事:

Daniel Vaughan 那篇文章讲的是 Codex app-server 的 WebSocket 远程控制协议 ;本文真正聚焦的是 OpenAI Responses WebSocket 协议。两者都使用 WebSocket,但不是同一个应用层协议。


先说结论

先给出这篇文章最重要的结论:

  1. Responses WebSocket 的价值,不是"流式输出更酷",而是"多轮工具回路更轻"
  2. OpenAI 官方明确提到:对于 20+ 次 tool call 的 rollout ,WebSocket mode 端到端最多可带来约 40% 的速度提升
  3. 首 Token(TTFT)通常也会更快 ,但官方没有公开给出单独的百分比;官方只明确说明了低延迟 continuation 路径和 generate: false warmup 机制。
  4. Routin.ai 已经支持 OpenAI-compatible 的 Responses WebSocket 路径。从实际可用的 Codex CLI provider 配置可以直接看出这一点。

RoutinAI 是一个企业级统一的 LLM API 网关,提供一个单一且类型安全的接口,访问来自 GPT、Claude 和 Gemini 系列的超过 100 个领先大语言模型(例如 gpt-5.4、claude-opus-4-6、gemini-3.1-pro-preview)等更多模型。它通过提供零延迟的边缘路由、无需改动代码即可无缝切换模型、统一计费,以及以支出上限和访问策略进行集中治理,消除了管理多个 AI 供应商的复杂性。

如果只用一句话概括:

Responses WebSocket 不是为了替代 HTTP Streaming 的"表现形式",而是为了降低 Agent 连续多轮执行时的控制面延迟。


Daniel Vaughan 那篇文章,和本文到底是什么关系?

Daniel Vaughan 的文章讨论的是 Codex CLI app-server

  • 底层是一个 JSON-RPC 2.0 风格服务
  • 既支持 stdio,也支持 WebSocket
  • 目标是把 Codex 从本地 CLI 变成可远程接入、可重连、可 headless 部署的 agent runtime

这说明了一件很重要的事:

当 Agent 从单机命令行工具演进为远程、长任务、可协作的系统时,长连接会越来越重要。

但 Daniel 文中的 WebSocket,承载的是:

  • 客户端与 app-server 之间的 JSON-RPC 控制协议
  • 远程 TUI、审批、线程恢复、状态同步

而 OpenAI 官方文档中的 WebSocket,承载的是:

  • 客户端与 /v1/responses 之间的模型交互协议
  • response.create 请求
  • response.output_text.delta 这类流式事件
  • 基于 previous_response_id 的低延迟 continuation

所以,最准确的理解方式是:

  • Codex app-server WebSocket:上层 agent runtime 的远程控制协议
  • Responses WebSocket:底层模型 API 的长连接 continuation 协议

两者方向一致,但分工完全不同。


第一层:WebSocket 协议本身在做什么?

在讲 OpenAI 的应用层协议之前,先把 WebSocket 自身讲清楚。

1. 它不是一个独立于 HTTP 的全新握手协议

WebSocket 的建立过程大致分为四步:

  1. 建立 TCP 连接
  2. 如果是 wss://,再建立 TLS 会话
  3. 发起一次 HTTP Upgrade 请求
  4. 服务端返回 101 Switching Protocols,连接升级为 WebSocket

典型握手请求大致如下:

http 复制代码
GET /v1/responses HTTP/1.1
Host: api.openai.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: <random_base64>
Authorization: Bearer <OPENAI_API_KEY>

服务端接受升级后会返回:

http 复制代码
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: <server_hash>

其中 Sec-WebSocket-Accept 是服务端根据客户端的 Sec-WebSocket-Key 加上固定 magic GUID 计算出来的摘要值,用来证明这不是普通 HTTP 响应,而是真正的 WebSocket 升级响应。

2. 建立成功后,通信不再是 HTTP 请求/响应,而是帧(frame)

连接升级后,双方发送的就不是 HTTP body 了,而是 WebSocket 帧。

常见 opcode 如下:

Opcode 含义
0x1 文本帧(text)
0x2 二进制帧(binary)
0x8 关闭连接(close)
0x9 Ping
0xA Pong

OpenAI 的 Responses WebSocket 在实际使用中,一条业务消息通常就是一个 JSON 文本消息,也就是承载在 text frame 里。

3. WebSocket 的几个关键特征

全双工

客户端和服务端都可以主动发送消息,而不需要等待对方先发起请求。

长连接

建立一次连接后,可以连续发送很多轮业务事件,不必每一轮都重新发起 HTTP 请求。

心跳与保活

协议内建 ping/pong 机制,适合长任务、长连接场景下做连接探活。

显式关闭

通信结束时,可以通过 close frame 做比较干净的关闭流程,而不是粗暴断开 TCP。

4. 一个容易被忽略的细节:客户端帧要 mask

按照 WebSocket 标准:

  • 客户端发给服务端的帧必须带 masking
  • 服务端发给客户端的帧通常不 mask

这件事通常由 WebSocket 库自动处理,但从协议层理解它很重要:

WebSocket 不是"纯裸 TCP JSON 流",它本身是有严格帧格式和控制语义的标准协议。


第二层:OpenAI Responses WebSocket 的应用层协议

理解了 WebSocket 本体之后,再来看 OpenAI 在这条连接上定义的应用层语义。

1. 连接地址与鉴权

官方文档给出的接入点是:

text 复制代码
wss://api.openai.com/v1/responses

鉴权方式仍然是标准 Bearer Token:

text 复制代码
Authorization: Bearer <OPENAI_API_KEY>

2. 客户端不是发 HTTP POST,而是在 socket 上发送事件

WebSocket mode 下,客户端通过发送 response.create 事件来发起一次 Responses 请求。

一个最小示例大致如下:

json 复制代码
{
  "type": "response.create",
  "model": "gpt-5.4",
  "store": false,
  "input": [
    {
      "type": "message",
      "role": "user",
      "content": [
        {
          "type": "input_text",
          "text": "Find fizz_buzz()"
        }
      ]
    }
  ],
  "tools": []
}

它和普通 POST /responses 的请求体非常接近,但有一个关键差异:

  • WebSocket mode 不使用 stream
  • WebSocket mode 不使用 background

因为流式能力本身已经由 WebSocket 连接承载了。

3. 服务端返回的是一串 Responses streaming events

OpenAI 明确说明:WebSocket mode 的服务端事件,与现有 Responses streaming event model 保持一致。

也就是说,WebSocket 只替换了传输方式,没有重新发明一套新的流式事件语义。

最常见的一组事件包括:

  • response.created
  • response.in_progress
  • response.output_item.added
  • response.content_part.added
  • response.output_text.delta
  • response.output_text.done
  • response.output_item.done
  • response.completed
  • error

如果是工具调用场景,还会看到类似事件:

  • response.function_call_arguments.delta
  • response.function_call_arguments.done
  • response.mcp_call_arguments.delta
  • response.mcp_call_arguments.done
  • response.file_search_call.in_progress
  • response.web_search_call.searching

4. 为什么这些事件很重要?

因为 Responses API 从一开始就不是"只产出一段文本"的接口,它的输出是一个 output item 流,里面可能包含:

  • assistant message
  • function call
  • mcp call
  • shell call
  • reasoning summary
  • refusal
  • file search / web search 等 hosted tool 输出

所以更准确的说法不是"模型在输出 token",而是:

模型在一个统一的事件流里,逐步产出文本、工具调用、工具结果状态与最终完成信号。

5. sequence_number 的意义

每个服务端事件都带有 sequence_number

这意味着:

  • 客户端可以按顺序消费事件
  • 即使前端内部有异步 UI 渲染,也能根据序号恢复正确顺序
  • 对调试和事件回放很有帮助

这也是 Responses WebSocket 比"手搓文本流协议"更工程化的地方。


一个完整的 Responses WebSocket 工作流长什么样?

1. 普通单轮生成

sequenceDiagram participant Client participant OpenAI Client->>OpenAI: WebSocket connect (wss://api.openai.com/v1/responses) Client->>OpenAI: response.create OpenAI-->>Client: response.created OpenAI-->>Client: response.in_progress OpenAI-->>Client: response.output_item.added OpenAI-->>Client: response.content_part.added OpenAI-->>Client: response.output_text.delta OpenAI-->>Client: response.output_text.done OpenAI-->>Client: response.output_item.done OpenAI-->>Client: response.completed

2. 带工具调用的 Agent 回路

真正能体现 WebSocket 价值的,是下面这种多轮工具回路:
sequenceDiagram participant Client participant OpenAI Client->>OpenAI: response.create(input, tools) OpenAI-->>Client: response.created / in_progress OpenAI-->>Client: function_call_arguments.delta OpenAI-->>Client: function_call_arguments.done Client->>Client: 本地执行工具 Client->>OpenAI: response.create(previous_response_id, function_call_output) OpenAI-->>Client: response.in_progress OpenAI-->>Client: response.output_text.delta OpenAI-->>Client: response.completed

这里最关键的一点是:

第二轮 continuation 并不是把整段历史重发一遍。

你只需要发送:

  • previous_response_id
  • 新增的 input items

例如:

json 复制代码
{
  "type": "response.create",
  "model": "gpt-5.4",
  "store": false,
  "previous_response_id": "resp_123",
  "input": [
    {
      "type": "function_call_output",
      "call_id": "call_123",
      "output": "tool result"
    },
    {
      "type": "message",
      "role": "user",
      "content": [
        {
          "type": "input_text",
          "text": "Now optimize it."
        }
      ]
    }
  ],
  "tools": []
}

为什么它比 HTTP + SSE 更适合 Agent?

如果只是单轮问答,HTTP + SSE 已经足够好用。但 Agent 工作流和单轮问答完全不是一个问题。

HTTP + SSE 的优势

  • 接入简单
  • 浏览器和后端生态成熟
  • 单次流式输出体验好

但 Agent 工作流天然更偏向双向、连续、低开销

典型 Agent 回路通常是:

  1. 模型输出中间解释
  2. 模型发起工具调用
  3. 客户端执行工具
  4. 客户端把工具输出回传给模型
  5. 模型继续下一轮
  6. 如有需要继续重复多次

这时问题就来了:

  • SSE 擅长的是服务端单向下行流
  • Agent 需要的是客户端与服务端双向持续交换事件
  • continuation 次数一多,HTTP 请求边界本身就会变成额外延迟来源

所以,WebSocket 更适合 Agent,不是因为它"更先进",而是因为它更符合 Agent 的通信形态。


性能提升到底来自哪里?

OpenAI 官方文档把逻辑写得很清楚,核心可以归结为三层。

1. 长连接减少每轮 continuation 的固定开销

如果使用 HTTP,每一次工具回传都需要重新进入一次请求生命周期:

  • 发起 HTTP 请求
  • 附带请求头
  • 重新过一遍请求分发
  • 重新解析 continuation 请求

WebSocket 模式下,连接已经建立完成,直接在活跃连接上继续发送 response.create 即可。

这类开销单看一次不大,但在多轮工具回路中会反复出现。

2. 只发送增量输入,而不是重复发送整段历史

在 WebSocket mode 下,continuation 的方式是:

  • previous_response_id = 上一轮 response id
  • input = 仅新增 items

这意味着你不需要在每一轮都重发整段上下文。

直接收益包括:

  • 更少的请求序列化成本
  • 更小的网络传输体积
  • 更少的服务端输入解析成本

3. 活跃连接上存在 connection-local 的最近响应缓存

这是官方文档里最关键的一点之一。

OpenAI 明确说明:

  • 在活跃 WebSocket 连接上
  • 服务端会把 最近一次 previous response state 保存在 connection-local in-memory cache
  • 如果你继续的是这一次最近 response,服务端可以直接复用这份内存态上下文

这带来的收益非常直接:

continuation 不必每一轮都从更慢的持久化状态恢复路径出发,而是可以命中连接内的低延迟路径。


官方给出的量化收益:20+ Tool Calls 最多约 40% 提速

这是目前官方最明确的一条公开性能数字:

对于 20 次以上工具调用的 rollout,WebSocket mode 端到端最多可带来约 40% 的速度提升。

这条数字为什么合理?

因为在工具密集场景里,真正占时间的不只是模型推理,还包括:

  • continuation 请求建立
  • 历史上下文重传
  • response state 恢复
  • 每轮串行控制流的固定管理成本

当这些固定成本在 20+ 轮中反复出现时,WebSocket 的收益会被明显放大。

哪些场景收益最大?

  • coding agent
  • orchestration loop
  • 多次 function calling
  • 多次 shell / MCP / hosted tools 往返

哪些场景收益不会特别夸张?

  • 一次性问答
  • 单轮短回答
  • 没有工具调用
  • 没有 continuation

所以,不要把这 40% 简化成"所有请求都更快 40%"。

更准确的说法是:

越像 Agent,收益越大。


首 Token 为什么通常也会更快?

这里需要讲得严谨一点。

可以明确说的部分

首 Token(TTFT,Time To First Token)通常更快,主要不是因为模型本身突然"每秒解码更快",而是因为控制面延迟降低了

具体来源包括:

  1. 少了一次新的 HTTP continuation 开销
  2. 少了大历史 payload 的上传与解析
  3. 命中连接内最近 response 的内存缓存
  4. 可以使用 generate: false 进行 warmup

generate: false 是什么?

OpenAI 文档明确提到,客户端可以先发送一条:

json 复制代码
{
  "type": "response.create",
  "model": "gpt-5.4",
  "generate": false,
  "tools": [...],
  "instructions": "...",
  "input": [...]
}

它不会直接返回模型输出,但会:

  • 预热 request state
  • 返回一个 response ID
  • 让后续真正要生成的那一轮可以更快开始

这个机制对即将进入复杂工具回路的请求尤其有价值。

但不能过度宣传的部分

官方没有公开给出 TTFT 的单独提升百分比

因此,最稳妥的技术表述应该是:

Responses WebSocket 往往能够改善首 Token 延迟,尤其是在 continuation turn、工具密集工作流和 warmup 之后的下一轮生成阶段;但 OpenAI 官方目前公开量化的数据,主要是 20+ tool calls 场景下端到端最多约 40% 的提速,而不是单独的 TTFT 百分比。


重要但容易被误解的一点:更快,不等于更省 Token 费用

previous_response_id 能让 continuation 更轻、更快,但这不意味着计费时只算新增输入。

OpenAI 在 Conversation State 文档里明确说明:

即便使用 previous_response_id,链路中之前的输入 token 在计费上仍然会作为 input tokens 参与统计。

所以要把两个概念分开:

  • 传输与调度成本:WebSocket 会优化
  • 上下文 token 计费:不会因为你只发送增量 items 就自动变成"只付增量 token"

这是架构设计时必须知道的边界。


工程限制与边界条件

WebSocket mode 很强,但它不是"无限制银弹"。

1. 单连接只有一个 in-flight response

官方明确说明:

  • 一个 WebSocket 连接上可以收到多个 response.create
  • 但它们是 顺序执行
  • 当前 不支持 multiplexing

这意味着如果你需要并行跑多个独立任务,就应该:

  • 建多条 WebSocket 连接
  • 而不是把并发期待建立在一条连接上

2. 连接时长限制为 60 分钟

到达上限时,会返回类似错误:

  • websocket_connection_limit_reached

因此生产环境必须做好:

  • reconnect
  • continuation recovery
  • 长任务分段续跑

3. store=false 与 ZDR 是兼容的

这点很重要,因为它直接决定了合规与性能是否能兼得。

OpenAI 明确写道:

  • WebSocket mode 与 store=false 兼容
  • 也与 Zero Data Retention 兼容

原因在于:最近响应状态只保留在连接本地内存里,不写入磁盘。

4. 但 store=false 更依赖活跃连接

如果你使用:

  • store=false
  • 并且连接断了
  • 或者引用的 previous_response_id 已经不在连接本地缓存里

那么就没有持久化回退路径了,可能会收到:

  • previous_response_not_found

所以最准确的工程总结是:

store=false + WebSocket 可以很快,也更利于隐私与 ZDR,但它更依赖"连接还活着、最近状态仍在缓存里"。

5. 失败会驱逐缓存

官方还提到:

  • 如果某一轮 continuation 失败(4xx 或 5xx)
  • 服务端会把这次引用的 previous_response_id 从连接本地缓存中驱逐

这样做是为了避免后续继续复用一份已经可能不一致的缓存状态。


Compaction 在 WebSocket 模式下怎么理解?

长链路 Agent 不可避免会遇到上下文膨胀,因此 compaction 是配套机制,而不是可选附属功能。

OpenAI 文档把它分成两种情况。

1. 服务端 compaction:context_management

如果你开启服务端 compaction,例如配置 compact_threshold

  • 仍然按正常方式继续发送 response.create
  • 仍然使用最新的 previous_response_id
  • 仍然只发送新增 input items

也就是说,对调用方来说,continuation 模式不变。

2. 独立 POST /responses/compact

这个接口返回的是:

  • 一个 新的 compacted input window
  • 而不是一个新的 response id

所以 compact 完成后,你需要:

  • 在 WebSocket 上开启一条新的 response chain
  • 把 compacted output 当作新的 input
  • 此时可以省略 previous_response_id 或显式设为 null

这说明 WebSocket mode 并不是"上下文无限长,永远不用管",而是和 compaction 机制一起工作。


Codex app-server WebSocket 为什么值得被拿来做对照?

回到 Daniel Vaughan 那篇文章,它的真正价值在于:

它展示了 Agent 产品形态正在如何变化。

从文章可以看到,Codex app-server 正在走向:

  • 远程接入
  • 轻客户端 / 重服务端
  • 可重连
  • 可审批
  • 可 headless 部署
  • 用 WebSocket 做长连接控制通道

这与 Responses WebSocket 的关系并不是"同一个协议",而是:

  • 前者解决 agent runtime 的远程控制问题
  • 后者解决模型 API 的低延迟 continuation 问题

也正因为这两层同时成立,现代 coding agent 才能真的做到:

  • 前端很轻
  • 模型与工具回路很长
  • 任务中断后可恢复
  • 远程服务端持续运行

Routin.ai 已经支持 Responses WebSocket 路径

这一点不需要猜,直接看你提供的 Codex CLI 配置就很清楚:

toml 复制代码
model = "gpt-5.4"
model_provider = "meteor-ai"
disable_response_storage = true
approval_policy = "never"
sandbox_mode = "danger-full-access"

model_supports_reasoning_summaries = true
rmcp_client = true
model_reasoning_effort = "xhigh"
personality = "pragmatic"
service_tier = "fast"

[model_providers.meteor-ai]
name = "meteor-ai"
base_url = "https://api.routin.ai/v1"
env_key = "OPENAI_API_KEY"
wire_api = "responses"
supports_websockets = true
requires_openai_auth = true

[features]
unified_exec = true
shell_snapshot = true
steer = true
skills = true
powershell_utf8 = true
collaboration_modes = true
fast_mode = true
multi_agent = true
responses_websockets_v2 = true

这段配置至少说明了四件事:

1. wire_api = "responses"

说明它走的是 Responses 协议栈,而不是旧的 Chat Completions 兼容层。

2. supports_websockets = true

说明这个 provider 支持 Responses over WebSocket

3. base_url = "https://api.routin.ai/v1"

说明 Routin.ai 暴露的是 OpenAI-compatible API 基址。

4. responses_websockets_v2 = true

这是 Codex CLI 侧的功能开关,说明当前这条链路已经启用了它所期待的新版 Responses WebSocket 集成能力。

需要注意的是:

responses_websockets_v2 = true 是客户端侧能力开关,不是 OpenAI 官方 API 路径里真的有一个 /v2/responses/websocket 这样的版本号。

因此,最稳妥、也最技术化的表述是:

从实际可运行的 Codex CLI provider 配置来看,Routin.ai 已经把 OpenAI-compatible 的 Responses WebSocket 工作流完整打通,并且能够直接承载 Codex CLI 所需的 Responses WebSocket 路径。


最后总结

如果把本文压缩成几句话,结论如下:

  1. WebSocket 是传输协议,Responses events 是应用层协议。
  2. OpenAI Responses WebSocket 复用了现有 Responses 的事件模型,只是把请求/续跑方式改成了长连接事件流。
  3. 它最大的价值不在于"流式文本更丝滑",而在于 Agent 多轮工具回路的 continuation 更轻、更快
  4. 官方公开的量化收益是:20+ tool calls 的 rollout 最多约 40% 端到端提速
  5. 首 Token 通常也会更快 ,但官方没有公开 TTFT 百分比;可确认的原因包括连接复用、增量输入、最近响应缓存与 generate: false warmup。
  6. Daniel Vaughan 文章中的 Codex app-server WebSocket 很值得参考,但它讨论的是 remote agent runtime 控制协议,不是 OpenAI 的 Responses WebSocket 协议本身。
  7. 从实际配置可见,Routin.ai 已经支持 OpenAI-compatible 的 Responses WebSocket 工作流

如果要用一句最准确的话为全文收尾,那就是:

Responses WebSocket 的真正价值,不是"把流式输出搬到 WebSocket 上",而是"把 Agent continuation 的固定成本压低到足够小"。