For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
Navegação principal

Modo WebSocket

Use uma única conexão WebSocket persistente para conversas paralelas, forks de cadeias de respostas e entradas incrementais em fluxos de trabalho agênticos com menor latência.

A Responses API oferece um modo WebSocket para fluxos de trabalho de longa duração com muitas chamadas de ferramentas. Além de reduzir a latência, stream_id permite a multiplexação WebSocket: uma única conexão persistente com /v1/responses pode executar conversas paralelas e criar um fork de uma conversa existente em um novo fluxo. Continue cada turno enviando apenas novos itens de entrada junto com previous_response_id.

O modo WebSocket é compatível tanto com zero retenção de dados (ZDR) quanto com store=false.

Por que usar o modo WebSocket

O modo WebSocket é especialmente útil quando um fluxo de trabalho envolve muitas trocas entre o modelo e as ferramentas (por exemplo, programação agêntica ou ciclos de orquestração com chamadas repetidas de ferramentas).

Como a conexão permanece aberta e cada turno envia apenas entradas incrementais, o modo WebSocket reduz a sobrecarga de continuação por turno e melhora a latência de ponta a ponta em cadeias longas. Em execuções com 20 ou mais chamadas de ferramentas, observamos uma execução de ponta a ponta até cerca de 40% mais rápida.

Conecte-se e crie respostas

Instale as dependências do WebSocket com pip install "openai[realtime]>=3.8.0" para Python, npm install openai@^7.10.0 ws para JavaScript ou gem install openai async-websocket para Ruby.

No modo WebSocket, inicie cada turno enviando um evento response.create a partir do cliente. O payload segue o corpo padrão de criação de respostas da Responses API, exceto pelos campos específicos do transporte, como stream e background, que não são usados.

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();
}

Os clientes podem, opcionalmente, preparar o estado da requisição com antecedência enviando response.create com generate: false. Isso é útil quando você já sabe quais ferramentas, instruções e/ou mensagens personalizadas pretende enviar em um próximo turno. generate: false não retorna uma saída do modelo, mas prepara o estado da requisição para que a geração do próximo turno possa começar mais rápido. A requisição de preparação retorna um ID de resposta que você pode usar como ponto de partida para um encadeamento com previous_response_id, inclusive em turnos posteriores de uma cadeia de respostas. A próxima seção explica como continuar uma sessão usando previous_response_id e entradas incrementais.

Continue com entradas incrementais

Para adicionar instruções do usuário enquanto uma resposta ainda está em andamento, use a Orientação durante o turno. A orientação preserva o trabalho concluído e inclui as novas instruções em uma continuação. Use o padrão de response.create a seguir para a continuação habitual entre turnos e para resultados de ferramentas.

Para continuar uma execução, envie outro response.create com:

  • previous_response_id definido como o ID da resposta anterior.
  • input contendo apenas novos itens (por exemplo, saídas de ferramentas e a próxima mensagem do usuário).
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();
}

Como funciona a continuação

O modo WebSocket usa a mesma semântica de encadeamento com previous_response_id do modo HTTP, mas acrescenta um caminho de continuação com menor latência no socket ativo.

Em uma conexão WebSocket ativa, o serviço mantém o estado recente de respostas anteriores em um cache em memória, local à conexão. Quando você usa stream_id, cada faixa mantém sua resposta mais recente em cache. Assim, continuar a partir da última resposta dessa faixa é rápido, pois o serviço pode reutilizar o estado local à conexão. Como o serviço retém o estado das respostas anteriores apenas na memória e não o grava em disco, você pode usar o modo WebSocket de forma compatível com store=false e zero retenção de dados (ZDR).

Se um previous_response_id não estiver no cache em memória, o comportamento dependerá de você armazenar ou não as respostas:

  • Com store=true, o serviço pode restaurar o estado de IDs de respostas mais antigas a partir do estado persistido, quando disponível. A continuação ainda pode funcionar, mas perde o benefício de latência do cache em memória.
  • Com store=false (incluindo ZDR), não há estado persistido ao qual recorrer como alternativa. Se o ID não estiver em cache, a requisição retornará previous_response_not_found.

Se uma continuação na mesma faixa retornar 4xx ou 5xx, o serviço removerá o previous_response_id referenciado do cache local à conexão. Um fork entre faixas que retorna um erro preserva a resposta de origem compartilhada para que a faixa de origem possa continuar.

Compactação e criação de novas respostas

Se você estiver usando compactação, há dois padrões diferentes de continuação:

Compactação no servidor (context_management)

Quando você ativa a compactação no servidor (context_management com compact_threshold), ela ocorre durante a geração normal em /responses. No modo WebSocket, a continuação funciona da maneira habitual: envie o próximo response.create com o previous_response_id mais recente e apenas novos itens de entrada.

Uso independente de /responses/compact

O endpoint /responses/compact independente retorna uma nova janela de entrada compactada, não um ID de resposta. Após a compactação, crie uma nova resposta na sua conexão WebSocket usando a janela compactada como input (junto com os próximos itens do usuário ou de ferramentas).

Inicie uma nova cadeia omitindo previous_response_id ou definindo-o como null. Passe a saída compactada sem alterações; não remova itens da janela retornada.

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();
}

Execute conversas em paralelo

Você pode manter conversas paralelas na mesma conexão usando o parâmetro stream_id. Envie eventos response.create independentes em sequência, com valores diferentes de stream_id. O servidor pode executá-los simultaneamente em uma única conexão. Os eventos podem se intercalar, portanto mantenha um único loop de leitura e encaminhe cada evento de acordo com stream_id.

Um stream_id nomeia uma faixa ordenada em uma conexão WebSocket. Mantenha stream_id e previous_response_id separados:

  • stream_id controla o destino dos eventos e quais requisições são executadas na ordem de chegada.
  • previous_response_id controla a linhagem da conversa.

Essa separação permite dois padrões úteis.

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

Requisições com o mesmo stream_id seguem a ordem de chegada e não se sobrepõem. Requisições com valores diferentes de stream_id podem ser executadas simultaneamente.

Limites por conexão

  • Uma conexão pode ter até 16 respostas ativas em andamento entre as faixas nomeadas e a faixa padrão. A conexão aceita mais eventos response.create e os coloca em fila até que uma resposta ativa termine.
  • Uma conexão aceita até 32 valores distintos de stream_id para fluxos nomeados. A faixa padrão implícita não conta para esse limite de fluxos nomeados. Reutilize um stream_id existente ou abra uma nova conexão ao atingir o limite.

Crie um fork de uma conversa em um novo fluxo

Para criar uma ramificação a partir de uma resposta concluída, envie o ID dela como previous_response_id com um novo stream_id. Enquanto essa resposta permanecer disponível, o novo fluxo herdará seu contexto, e o fluxo original poderá continuar. Depois que o fork começar, ambas as ramificações poderão ser executadas simultaneamente, pois usam IDs de fluxo diferentes.

Com store=false (incluindo ZDR), um fork entre faixas depende de a resposta de origem permanecer no cache local à conexão. Se o fork entrar na fila enquanto a faixa de origem avança ou falha, a resposta de origem poderá ser removida do cache antes que o fork comece, e o fork retornará previous_response_not_found. Espere a faixa do fork emitir response.in_progress antes de avançar a faixa de origem, ou tente novamente com previous_response_id definido como null e reenvie todo o contexto de entrada.

main:   resp_1 ──▶ resp_2 ──▶ resp_3

critic:                 resp_4 ──▶ resp_5

Reutilizar um stream_id sem previous_response_id inicia uma nova resposta; isso não dá continuidade à conversa.

As principais chamadas são assim:

# 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,
)

Exemplo completo

Execute conversas em paralelo e depois crie um fork de uma delas
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();
}

Um stream_id deve ter de 1 a 256 caracteres e pode conter apenas letras, números, sublinhados (_), hífens (-) e pontos (.). Use-o apenas em eventos response.create via WebSocket; não o inclua em requisições HTTP POST /v1/responses.

Para fluxos nomeados, os eventos do servidor incluem o stream_id correspondente, inclusive eventos de término e erros restritos à requisição.

Se você omitir stream_id, a requisição usará uma faixa padrão implícita, e seus eventos não incluirão stream_id. Nos demais aspectos, a faixa padrão segue as mesmas regras de ordenação e concorrência dos fluxos nomeados. Uma string vazia não é um stream_id válido; omita o campo para selecionar a faixa padrão.

Comportamento e limites da conexão

  • Os eventos de cada resposta seguem o modelo existente de eventos de streaming da Responses API. Eventos de faixas diferentes podem se intercalar.
  • Requisições com o mesmo stream_id são executadas na ordem de chegada e não se sobrepõem. Requisições em faixas diferentes podem ser executadas simultaneamente.
  • As conexões duram até 60 minutos. Reconecte-se ao atingir esse limite.

Reconecte-se e recupere o estado

Quando uma conexão é encerrada (ou atinge o limite de 60 minutos), o cache local da conexão é perdido para todas as vias. Abra uma nova conexão WebSocket e recupere cada via com uma destas abordagens:

  1. Se você armazenou uma resposta anterior (store=true) e tem um ID de resposta válido, continue essa via com previous_response_id e novos itens de entrada.
  2. Se não for possível continuar uma via (por exemplo, store=false/ZDR ou previous_response_not_found), inicie uma nova resposta definindo previous_response_id como null (ou omitindo esse parâmetro) e envie o contexto de entrada completo para o próximo turno dessa via.
  3. Se você compactou o contexto com /responses/compact, use a janela compactada retornada como base para o input dessa nova resposta e, em seguida, acrescente os itens mais recentes do usuário e das ferramentas.

Erros a tratar

Quando o servidor consegue associar um erro a uma via nomeada, o evento de erro inclui stream_id. As outras vias podem continuar após um erro restrito a uma requisição.

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
}