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。壓縮完成後,請將壓縮後的視窗作為 input,加上接下來的使用者/工具項目,在 WebSocket 連線上建立新回應。

省略 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 參數,在同一條連線上維持多個平行對話。請使用不同的 stream_id 值,接連傳送彼此獨立的 response.create 事件。伺服器可以在同一條連線上並行執行這些請求。各個請求的事件可能交錯出現,因此請只維持一個讀取迴圈,並依據 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
}