For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
主导航

WebSocket 模式

使用一个持久 WebSocket 连接并行运行对话、派生响应链并发送增量输入,降低智能体工作流的延迟。

Responses API 支持 WebSocket 模式,适用于长时间运行且频繁调用工具的工作流。除了降低延迟,stream_id 还支持 WebSocket 多路复用:通过一个连接到 /v1/responses 的持久连接,即可并行运行多个对话,并将现有对话派生到新流中。每轮只需发送新的输入项和 previous_response_id 即可续接。

WebSocket 模式同时兼容零数据保留(ZDR)和 store=false

为什么使用 WebSocket 模式

当工作流需要在模型和工具之间进行大量往返交互时,WebSocket 模式尤为适用,例如智能体编程或反复调用工具的编排循环。

由于连接保持打开,且每轮只发送增量输入,WebSocket 模式可以减少每轮续接的开销,降低长链路的端到端延迟。在包含 20 次及以上工具调用的运行中,我们观察到端到端执行速度最高提升了约 40%。

连接并创建响应

请安装 WebSocket 依赖项:Python 使用 pip install "openai[realtime]>=3.8.0",JavaScript 使用 npm install openai@^7.10.0 ws,Ruby 使用 gem install openai async-websocket

在 WebSocket 模式下,从客户端发送 response.create 事件即可开始一轮交互。其载荷与常规的 Responses 创建请求体一致,但不使用 streambackground 等与传输方式相关的字段。

import OpenAI from "openai";
import { ResponsesWS } from "openai/resources/responses/ws";

const client = new OpenAI();

const ws = new ResponsesWS(client);
try {
  ws.send({
    type: "response.create",
    stream_id: "main",
    model: "gpt-6-astra",
    store: false,
    input: [
      {
        type: "message",
        role: "user",
        content: [{ type: "input_text", text: "Find fizz_buzz()" }],
      },
    ],
    tools: [],
  });
  let completed = false;
  for await (const event of ws) {
    if (event.type === "error") throw event.error;
    if (event.type !== "message") continue;
    const message = event.message;
    if (message.type === "response.output_text.delta") {
      process.stdout.write(message.delta);
    } else if (message.type === "response.completed") {
      completed = true;
      break;
    } else if (
      message.type === "response.failed" ||
      message.type === "response.incomplete"
    ) {
      throw new Error(JSON.stringify(message));
    }
  }
  if (!completed)
    throw new Error("Connection closed before the response finished.");
} finally {
  ws.close();
}

客户端可以选择发送带有 generate: falseresponse.create 来预热请求状态。如果您已经知道下一轮计划发送的工具、指令和/或自定义消息,这种方式会很有用。generate: false 不会返回模型输出,而是预先准备请求状态,让下一轮生成更快开始。预热请求会返回一个响应 ID,您可以通过 previous_response_id 从该响应续接,包括在响应链的后续轮次中使用这种方式。下一节将介绍如何使用 previous_response_id 和增量输入续接会话。

使用增量输入续接

要在响应仍在运行时添加用户指令,请使用轮次中途引导。引导会保留已完成的工作,并在续接时纳入新指令。对于常规的轮次间续接和工具结果,请使用下面的 response.create 模式。

要继续一次运行,请再发送一个 response.create,其中:

  • previous_response_id 设置为上一条响应的 ID。
  • input 仅包含新项,例如工具输出和下一条用户消息。
import OpenAI from "openai";
import { ResponsesWS } from "openai/resources/responses/ws";

const client = new OpenAI();
const model = "gpt-6-astra";

const tools = [
  {
    type: "function",
    name: "get_test_results",
    description: "Return a local demo test result.",
    parameters: { type: "object", properties: {}, additionalProperties: false },
    strict: true,
  },
];

async function waitForResponse(ws) {
  for await (const event of ws) {
    if (event.type === "error") throw event.error;
    if (event.type !== "message") continue;
    const message = event.message;
    if (message.type === "response.output_text.delta") {
      process.stdout.write(message.delta);
    } else if (message.type === "response.completed") {
      return message.response;
    } else if (
      message.type === "response.failed" ||
      message.type === "response.incomplete"
    ) {
      throw new Error(JSON.stringify(message));
    }
  }
  throw new Error("Connection closed before the response finished.");
}

const ws = new ResponsesWS(client);
try {
  ws.send({
    type: "response.create",
    stream_id: "main",
    model,
    store: false,
    input: "Find the failing test and suggest a fix.",
    tools,
    tool_choice: { type: "function", name: "get_test_results" },
    parallel_tool_calls: false,
  });
  const first = await waitForResponse(ws);
  const call = first.output.find((item) => item.type === "function_call");
  if (!call || call.name !== "get_test_results") {
    throw new Error("Expected a get_test_results function call.");
  }
  const result = {
    test: "test_fizz_buzz",
    failure: 'Expected "FizzBuzz" for 15, got "Fizz".',
  };

  // Continue on the same socket with the actual response and tool-call IDs.
  ws.send({
    type: "response.create",
    stream_id: "main",
    model,
    store: false,
    previous_response_id: first.id,
    input: [
      {
        type: "function_call_output",
        call_id: call.call_id,
        output: JSON.stringify(result),
      },
      { role: "user", content: "Now optimize it." },
    ],
    tools,
    tool_choice: "none",
  });
  await waitForResponse(ws);
} finally {
  ws.close();
}

续接的工作原理

WebSocket 模式采用与 HTTP 模式相同的 previous_response_id 链式关联语义,同时在活动套接字上提供延迟更低的续接路径。

在活动的 WebSocket 连接上,服务会将近期的先前响应状态保存在该连接专属的内存缓存中。使用 stream_id 时,每个通道都会保留其最新的缓存响应。从该通道的最新响应续接时,服务可以复用连接本地状态,因此速度很快。由于服务仅在内存中保留先前响应状态,不会将其写入磁盘,因此您可以以兼容 store=false 和零数据保留(ZDR)的方式使用 WebSocket 模式。

如果内存缓存中没有某个 previous_response_id,具体行为取决于您是否存储响应:

  • 使用 store=true 时,如果存在持久化状态,服务可能会从中恢复较早响应 ID 对应的状态。续接仍可进行,但不再享有内存缓存带来的延迟优势。
  • 使用 store=false 时(包括 ZDR),没有持久化状态可供回退。如果该 ID 未被缓存,请求会返回 previous_response_not_found

如果同一通道内的续接返回 4xx5xx,服务会从连接本地缓存中移除所引用的 previous_response_id。跨通道派生返回错误时,则会保留共享的父响应,以便源通道继续运行。

压缩与创建新响应

如果您使用压缩,有两种不同的续接模式:

服务端压缩(context_management

启用服务端压缩(context_management 配合 compact_threshold)后,压缩会在正常的 /responses 生成过程中进行。在 WebSocket 模式下,续接方式与平时相同:发送下一个 response.create,携带最新的 previous_response_id,并且仅包含新的输入项。

独立的 /responses/compact

独立的 /responses/compact 端点返回一个新的压缩后输入窗口,而不是响应 ID。压缩完成后,在您的 WebSocket 连接上创建新响应,将压缩后的窗口用作 input,并附上接下来的用户项或工具项。

省略 previous_response_id 或将其设置为 null,即可开始一条新链。请原样传入压缩后的输出,不要删减返回的窗口。

import { toResponseInputItems } from "openai/lib/responses/ResponseInputItems";

// Compact your current window with an HTTP request.
const compacted = await client.responses.compact({
  model: "gpt-6-astra",
  input: longInputItems,
});
const nextInput = toResponseInputItems(compacted.output);
nextInput.push({
  type: "message",
  role: "user",
  content: [{ type: "input_text", text: "Continue from here." }],
});

// Start a new response on the WebSocket using the compacted window.
const ws = new ResponsesWS(client);
try {
  ws.send({
    type: "response.create",
    stream_id: "main",
    model: "gpt-6-astra",
    store: false,
    input: nextInput,
    tools: [],
  });
  let completed = false;
  for await (const event of ws) {
    if (event.type === "error") throw event.error;
    if (event.type !== "message") continue;
    const message = event.message;
    if (message.type === "response.output_text.delta") {
      process.stdout.write(message.delta);
    } else if (message.type === "response.completed") {
      completed = true;
      break;
    } else if (
      message.type === "response.failed" ||
      message.type === "response.incomplete"
    ) {
      throw new Error(JSON.stringify(message));
    }
  }
  if (!completed)
    throw new Error("Connection closed before the response finished.");
} finally {
  ws.close();
}

并行运行对话

您可以使用 stream_id 参数在同一连接上维持多个并行对话。连续发送独立的 response.create 事件,并为它们指定不同的 stream_id 值。服务端可以在一个连接上并发运行它们。各个对话的事件可能交错到达,因此请使用一个读取循环,并根据 stream_id 路由每个事件。

stream_id 用于标识一个 WebSocket 连接上的有序通道。请区分 stream_idprevious_response_id 的作用:

  • stream_id 控制事件的去向,以及哪些请求按先进先出顺序运行。
  • previous_response_id 控制对话的继承关系。

这种分离支持两种实用模式。

one WebSocket connection
├─ stream_id="planner"   draft a deployment plan
└─ stream_id="research"  list deployment risks

具有相同 stream_id 的请求始终按先进先出顺序运行,且不会重叠执行。具有不同 stream_id 值的请求可以并发运行。

每个连接的限制

  • 一个连接的命名通道和默认通道合计最多可有 16 个正在处理中的活动响应。连接可以接受更多 response.create 事件,并将其排队,直到有活动响应完成。
  • 一个连接最多接受 32 个不同的命名 stream_id 值。隐式默认通道不计入此命名流数量限制。达到上限后,请复用现有的 stream_id 或打开新连接。

将对话派生到新流

要从已完成的响应创建分支,请将该响应的 ID 作为 previous_response_id 发送,同时指定一个新的 stream_id。只要该响应仍然可用,新流就会继承其上下文,而原始流可以继续运行。派生开始后,两个分支使用不同的流 ID,因此可以并发运行。

使用 store=false 时(包括 ZDR),跨通道派生依赖于父响应仍保留在连接本地缓存中。如果派生请求正在排队,而源通道继续推进或发生错误,父响应可能在派生开始前被移除,此时派生请求会返回 previous_response_not_found。请等待派生通道发出 response.in_progress 后再推进源通道,或者将 previous_response_id 设置为 null,重新发送完整输入上下文以重试。

main:   resp_1 ──▶ resp_2 ──▶ resp_3

critic:                 resp_4 ──▶ resp_5

复用 stream_id 但不提供 previous_response_id 会启动一个新响应,而不会续接对话。

关键调用如下:

# One socket, two independent conversations.
send_create(connection, "planner", "Draft a deployment plan.")
send_create(connection, "research", "List deployment risks.")

# Fork the planner response, then continue the original branch in parallel.
send_create(
    connection,
    "critic",
    "Find gaps in this plan.",
    previous_response_id=planner_response_id,
)
wait_for_in_progress(connection, "critic")
send_create(
    connection,
    "planner",
    "Add rollback steps.",
    previous_response_id=planner_response_id,
)

完整示例

并行运行多个对话,然后从其中一个对话派生新流
import OpenAI from "openai";
import { ResponsesWS } from "openai/resources/responses/ws";

const client = new OpenAI();

const latestResponseIdByLane = new Map();

function sendCreate(
  ws,
  streamId,
  text,
  previousResponseId = latestResponseIdByLane.get(streamId)
) {
  ws.send({
    type: "response.create",
    stream_id: streamId,
    model: "gpt-6-astra",
    store: false,
    input: [
      {
        type: "message",
        role: "user",
        content: [{ type: "input_text", text }],
      },
    ],
    previous_response_id: previousResponseId,
  });
}

async function readMessage(events) {
  while (true) {
    const { value: event, done } = await events.next();
    if (done)
      throw new Error("Connection closed before all responses finished.");
    if (event.type === "error") throw event.error;
    if (event.type !== "message") continue;
    const message = event.message;
    if (
      message.type === "response.failed" ||
      message.type === "response.incomplete"
    ) {
      throw new Error(
        `Lane ${message.stream_id} failed: ${JSON.stringify(message)}`
      );
    }
    return message;
  }
}

async function drainUntilComplete(events, expectedStreamIds) {
  const remaining = new Set(expectedStreamIds);
  while (remaining.size > 0) {
    const message = await readMessage(events);
    const streamId = message.stream_id;
    if (!streamId || !remaining.has(streamId)) continue;
    if (message.type === "response.completed") {
      latestResponseIdByLane.set(streamId, message.response.id);
      remaining.delete(streamId);
    }
  }
}

async function waitForInProgress(events, streamId) {
  while (true) {
    const message = await readMessage(events);
    if (
      message.type === "response.in_progress" &&
      message.stream_id === streamId
    )
      return;
  }
}

const ws = new ResponsesWS(client);
// Keep one iterator so events stay queued while moving between phases.
const events = ws.stream();
try {
  // Run two independent conversations in parallel.
  sendCreate(
    ws,
    "planner",
    "Draft a deployment plan for a stateless API service."
  );
  sendCreate(
    ws,
    "research",
    "List common deployment risks for a stateless API service."
  );
  await drainUntilComplete(events, new Set(["planner", "research"]));

  // Fork the planner conversation and continue its original branch in parallel.
  const plannerResponseId = latestResponseIdByLane.get("planner");
  sendCreate(
    ws,
    "critic",
    "Find gaps in this deployment plan.",
    plannerResponseId
  );
  // Let the fork load its parent before advancing the original lane's cache.
  await waitForInProgress(events, "critic");
  sendCreate(
    ws,
    "planner",
    "Add rollback and monitoring steps to the plan.",
    plannerResponseId
  );
  await drainUntilComplete(events, new Set(["critic", "planner"]));
} finally {
  await events.return?.();
  ws.close();
}

stream_id 的长度必须为 1–256 个字符,且只能包含字母、数字、下划线(_)、连字符(-)和句点(.)。只能在 WebSocket response.create 事件中使用它,不要将其包含在 HTTP POST /v1/responses 中。

对于命名流,服务端事件会包含对应的 stream_id,终止事件和请求范围内的错误也不例外。

如果省略 stream_id,请求会使用隐式默认通道,其事件不包含 stream_id。除此之外,默认通道遵循与命名流相同的排序和并发规则。空字符串不是有效的 stream_id;要选择默认通道,请省略该字段。

连接行为与限制

  • 每个响应内的事件遵循现有的 Responses 流式事件模型。不同通道的事件可能交错到达。
  • 具有相同 stream_id 的请求按先进先出顺序运行,且不会重叠执行。不同通道上的请求可以并发运行。
  • 连接最长持续 60 分钟。达到时限后,请重新连接。

重新连接与恢复

当连接关闭(或达到 60 分钟的时限)时,所有通道在该连接上的本地缓存都会消失。请建立新的 WebSocket 连接,并使用以下方式之一恢复各个通道:

  1. 如果您已存储先前的响应(store=true)并且拥有有效的响应 ID,请使用 previous_response_id 和新的输入项继续该通道的对话。
  2. 如果您无法继续某个通道的对话(例如,使用 store=false/ZDR 或遇到 previous_response_not_found),请将 previous_response_id 设为 null(或省略该参数)以创建新响应,并发送该通道下一轮所需的完整输入上下文。
  3. 如果您已使用 /responses/compact 压缩上下文,请将返回的压缩后窗口作为新响应的基础 input,然后追加最新的用户项和工具项。

需要处理的错误

当服务器能够将错误关联到某个命名通道时,错误事件会包含 stream_id。发生请求级错误后,其他通道可以继续运行。

previous_response_not_found

{
  "type": "error",
  "status": 400,
  "stream_id": "main",
  "error": {
    "type": "invalid_request_error",
    "code": "previous_response_not_found",
    "message": "Previous response with id 'resp_abc' not found.",
    "param": "previous_response_id"
  }
}

invalid_stream_id

{
  "type": "error",
  "status": 400,
  "error": {
    "type": "invalid_request_error",
    "code": "invalid_stream_id",
    "message": "The 'stream_id' field must be a non-empty string with at most 256 characters and may only contain letters, numbers, underscores, hyphens, and periods.",
    "param": "stream_id"
  }
}

websocket_stream_limit_reached

{
  "type": "error",
  "status": 400,
  "stream_id": "agent_33",
  "error": {
    "type": "invalid_request_error",
    "code": "websocket_stream_limit_reached",
    "message": "This WebSocket connection has reached its maximum number of distinct stream IDs (32). Reuse an existing stream_id or open a new WebSocket connection.",
    "param": "stream_id"
  }
}

websocket_connection_limit_reached

{
  "type": "error",
  "error": {
    "type": "invalid_request_error",
    "code": "websocket_connection_limit_reached",
    "message": "Responses websocket connection limit reached (60 minutes). Create a new websocket connection to continue."
  },
  "status": 400
}