一次请求一次回答,继续用 HTTP;只想边生成边展示,用 stream: true 的 SSE;工具调用很多、要连续多回合并复用连接的 Agent,才优先评估 Responses WebSocket;需要语音打断、低首音频延迟和自然轮次,则看 Realtime API。WebSocket 的价值来自持久连接、增量输入和多路会话,不等于“换一个协议就必然更快”。
官方资料(2026-10-30 核对):WebSocket Mode · Responses Connect Reference · Streaming API responses · Conversation state · Realtime API
先选对传输方式:HTTP、SSE、Responses WebSocket 还是 Realtime?
| 方案 | 适合场景 | 连接与数据流 | 关键边界 |
|---|---|---|---|
| 普通 HTTP | 短请求、后台任务入口、一次返回完整结果 | 一次请求,一次完整响应 | 首屏要等待完整生成 |
| HTTP + SSE | 聊天 UI、长文本、代码边生成边展示 | 一次 HTTP 请求内,服务端持续推送 typed events | 单向流;断开后不能把半截输出当完成 |
| Responses WebSocket | 长时间运行、工具调用多、多会话并行或分叉的 Agent | 一条持久双向连接,多次发送 response.create | 最长 60 分钟;要维护会话、lane、重连和背压 |
| Realtime API | 语音 Agent、打断、自然轮次、低首音频延迟 | 浏览器通常 WebRTC,受信服务器可用 WebSocket | 与 Responses WebSocket 是不同产品接口 |
如果当前应用只是把一条回答逐字显示,不必为了“WebSocket”重写。先看 OpenAI API Streaming 教程;只有网络握手和重复上下文准备在长工具链中形成可测瓶颈,再迁移持久连接。
Responses WebSocket 的工作方式
- 后端握手:受信任的服务端连接
wss://api.openai.com/v1/responses,在握手中用 Bearer API Key 鉴权。 - 创建第一回合:发送
type: "response.create",带模型、instructions、tools 与第一批 input。 - 消费事件:像 Responses SSE 一样按
event.type处理文本、工具调用、completed、failed、incomplete 和 error。 - 执行工具:本地校验参数、权限和幂等性,执行工具后,把新产生的
function_call_output与上一个previous_response_id发送到下一次response.create。 - 并行会话:需要共用一条连接时,为每条会话分配稳定的
stream_id,按它路由交错事件。
WebSocket 请求体与普通 Responses create 接近,但官方明确说明不使用 stream 和 background 这类传输字段。每个新回合仍要显式发送 response.create。
最小 JavaScript 连接示例
下面用通用 ws 客户端展示协议形状,模型名使用占位符,避免把某个账户当下可见的模型写成永久规则。标准 API Key 必须只存在于服务端环境变量。
import WebSocket from "ws";
const socket = new WebSocket(
"wss://api.openai.com/v1/responses",
{
headers: {
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`
}
}
);
let latestResponseId = null;
socket.on("open", () => {
socket.send(JSON.stringify({
type: "response.create",
model: "YOUR_MODEL",
input: "先列出排查计划。",
stream_id: "support.case_42"
}));
});
socket.on("message", (raw) => {
const event = JSON.parse(raw.toString());
// 多 lane 时,先按 event.stream_id 路由
if (event.type === "response.output_text.delta") {
process.stdout.write(event.delta);
}
if (event.type === "response.completed") {
latestResponseId = event.response.id;
console.log("\ncompleted:", latestResponseId);
}
if (
event.type === "response.failed" ||
event.type === "response.incomplete" ||
event.type === "error"
) {
console.error("not completed:", event);
}
});
socket.on("close", () => {
console.warn("socket closed; do not mark partial output complete");
});真实应用还要加握手失败、消息 Schema 校验、超时、连接关闭、重连、服务端限流与日志脱敏。不要只监听文本 delta;只有收到该 lane 的 response.completed 才能将输出记为完整。
继续下一回合:previous_response_id 只带新增输入
第一回合完成后,保存 event.response.id。下一回合在同一连接上再次发送 response.create,把前一响应 ID 作为 previous_response_id,input 只包含新用户消息或新的工具结果:
socket.send(JSON.stringify({
type: "response.create",
model: "YOUR_MODEL",
previous_response_id: latestResponseId,
input: [{
type: "function_call_output",
call_id: "call_from_previous_response",
output: JSON.stringify({ status: "ok" })
}],
stream_id: "support.case_42"
}));previous_response_id 的会话语义与 HTTP Responses 相同;区别在于持久连接还能使用连接内的近期状态。工具循环中的 call_id、参数校验、权限和副作用幂等请配合 Function Calling 教程。
stream_id:一条连接上并行多条 lane
stream_id 只用于 WebSocket response.create,不能塞进普通 HTTP POST /v1/responses。官方约束是 1–256 个字符,仅可包含字母、数字、下划线、连字符和句点;空字符串无效,省略则使用默认 lane。
| 行为 | 同一 stream_id | 不同 stream_id |
|---|---|---|
| 执行顺序 | 先进先出,不重叠 | 可以并发 |
| 事件顺序 | 该响应内部按 Responses 事件模型 | 不同 lane 的事件会交错 |
| 应用路由 | 维护该 lane 的 response ID 与状态 | 先按 stream_id 分流,再按 event.type 消费 |
| 出错影响 | 同 lane 延续失败可能影响引用缓存 | 分叉 lane 的错误不应被当成源 lane 成功或失败 |
官方部署清单写明一条连接最多支持 16 个 active responses。它不是“用户数上限”:需要为连接池、lane 生命周期、排队和背压制定自己的容量策略,超过主动并发时在应用层排队或分配新连接。
断线与 60 分钟上限:三种恢复路径
Responses WebSocket 连接最长 60 分钟,到点要重连;网络断开后,该连接的本地缓存也会消失。恢复不能只靠“重新连上”,还要知道每条 lane 的上下文来源。
- 已存储响应:若之前使用可存储状态且 response ID 仍有效,新连接可带
previous_response_id与新增输入继续。 - store=false、ZDR 或缓存未命中:省略
previous_response_id,发送该 lane 下一回合所需的完整输入上下文重新开始。 - 已做 standalone compaction:
/responses/compact返回的是压缩后的输入窗口,不是 response ID;用该窗口加最新用户/工具项作为新 input。
若返回 previous_response_not_found,不要无限重试同一个 ID。标记该 lane 需要重建上下文,并防止已经执行过的支付、写入或发信工具再次产生副作用。500/503、超时、Request ID 和受控重试见 API 5xx 与超时排查。
与 Realtime API 的区别
二者都可能使用 WebSocket,但不能因为传输层相似就共用产品逻辑。Responses WebSocket 侧重 Responses 的长工具链、增量输入和并行会话;Realtime API 面向语音交互、打断、自然轮次与实时音频。
- 文本/工具 Agent:从 Responses WebSocket 评测。
- 浏览器语音:优先按 Realtime WebRTC 或官方统一接口设计。
- 密钥:标准 API Key 保留在后端;Realtime 浏览器直连按官方流程获取临时 client secret。
- 状态:分别维护 Responses response/lane 状态与 Realtime session/audio 状态,不要混用事件对象。
性能收益应该怎样验证?
OpenAI 表述是:在 20 次以上工具调用的部分 rollout 中,观察到最高约 40% 的端到端提速。这是官方观察值,不是所有请求的承诺。至少对比:
- 首个事件、每个工具回合与最终 completed 的 P50/P95。
- 相同任务在 HTTP/SSE 与 WebSocket 下的总耗时和成功率。
- 连接建立、重连、previous_response_not_found 和工具错误占比。
- 不同 lane 并发时的排队、内存、背压与事件路由准确率。
- 失败重试是否造成重复工具副作用或重复计费请求。
如果主要耗时在外部数据库或慢工具,持久连接可能只省下一小部分。先测再迁移,不要把协议名称当成性能结论。
常见错误与排查顺序
| 现象 | 常见原因 | 处理 |
|---|---|---|
| 握手 401/403 | API Key、Project 权限或服务端环境变量错误 | 按 401/403 排查页检查,不把 Key 打进日志 |
| 事件串到错误会话 | 忽略 stream_id,所有 lane 共用一个缓冲 | 先按 stream_id,再按 response/item/event 路由 |
| 同一 lane 没有并发 | 相同 stream_id 按 FIFO 串行 | 真正独立的工作使用不同 lane,并保留容量限制 |
| previous_response_not_found | 连接缓存消失、ID 未存储或引用失效 | 停止重试旧 ID,按完整上下文或 compacted window 重建 |
| 连接关闭但 UI 显示成功 | 把 close 当 completed | 只有 response.completed 才写成功状态 |
| 浏览器泄露标准 Key | 前端直接创建 Responses WebSocket | 把连接放到受信后端;浏览器语音按 Realtime 临时凭证流程 |
上线前检查清单
- 核心任务确实是长工具链或多回合 Agent,已与 HTTP/SSE 做同任务基线。
- 服务端握手到正确的
/v1/responses,Key 不进入浏览器、仓库和普通日志。 - 每一回合发送
response.create,请求体没有误加stream或background。 - 不同 lane 的状态、response ID、工具调用和缓冲按 stream_id 隔离。
- completed、failed、incomplete、error、close 和超时都能落到明确终态。
- 60 分钟主动重连和三种上下文恢复路径都经过测试。
- 工具副作用具备幂等键,断线和重试不会重复支付、发信或写入。
- 监控 P50/P95、active responses、排队、重连与 previous_response_not_found。
常见问题
OpenAI Responses WebSocket 和 SSE 有什么区别?
SSE 是一次 HTTP 请求内的单向流式返回,适合一问一答和降低首 Token 等待;Responses WebSocket 保持持久双向连接,可在后续回合只发送新增输入与 previous_response_id,并用 stream_id 在同一连接上管理多条并发会话。
什么场景应该用 Responses WebSocket?
长时间运行、工具调用很多、需要连续 response.create 回合或在一条连接上并行多个会话的 Agent 更适合。普通的一次请求一次回答应继续使用 HTTP;语音打断、低首音频延迟和自然轮次应优先评估 Realtime API。
WebSocket 的 response.create 还要设置 stream true 吗?
不要。WebSocket 模式的每一回合都发送 response.create,但请求体不使用 stream 和 background 这类传输字段;服务端会持续回送 Responses 流式事件。
stream_id 有什么限制?
stream_id 仅用于 WebSocket response.create,长度为 1 到 256 个字符,只能包含字母、数字、下划线、连字符和句点。相同 stream_id 的请求按先进先出执行且不重叠,不同 stream_id 可并行,事件可能交错到达。
WebSocket 断开后能继续 previous_response_id 吗?
能否继续取决于状态是否仍可用。连接最长 60 分钟,断开后连接内缓存消失;如果此前响应已存储且 response ID 有效,可在新连接继续。store=false、ZDR、缓存未命中或 previous_response_not_found 时,应发送完整上下文重新开始,或使用已压缩的上下文窗口。
浏览器可以直接连 Responses WebSocket 吗?
生产应用不应把标准 OpenAI API Key 放进浏览器。Responses WebSocket 通常由受信任的后端连接并完成鉴权、权限、配额和审计;浏览器语音场景应按 Realtime 官方流程使用服务端创建的临时凭证或统一 WebRTC 接口。
OpenAI 说 WebSocket 一定会快 40% 吗?
不是保证。官方说明在包含 20 次以上工具调用的部分长链路中观察到最高约 40% 的端到端提速;实际收益取决于网络、模型、工具耗时、上下文和并发方式,应在自己的 P50、P95 与错误率基线上评测。
Responses WebSocket 适合持久文本与工具链路;麦克风媒体轨道、VAD、播放取消和未播放音频截断应参考 OpenAI Realtime API 指南。