For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
Navegación principal
Theme

Modo WebSocket

Usa una sola conexión WebSocket persistente para mantener conversaciones en paralelo, crear forks de cadenas de respuestas y enviar entradas incrementales para reducir la latencia en flujos de trabajo con agentes.

La API de Responses ofrece un modo WebSocket para flujos de trabajo de larga duración que realizan muchas llamadas a herramientas. Además de reducir la latencia, el modo WebSocket permite multiplexar una conexión mediante stream_id. Con una sola conexión persistente a /v1/responses, puedes mantener conversaciones en paralelo y crear un fork de una conversación existente en un flujo nuevo. Para continuar en cada turno, envía solo los elementos de entrada nuevos junto con previous_response_id.

El modo WebSocket es compatible tanto con la retención cero de datos (ZDR) como con store=false.

Por qué usar el modo WebSocket

El modo WebSocket resulta especialmente útil cuando un flujo de trabajo requiere muchos intercambios entre el modelo y las herramientas, como ocurre en la codificación con agentes o en los bucles de orquestación que llaman repetidamente a herramientas.

Como la conexión permanece abierta y en cada turno solo se envían entradas incrementales, el modo WebSocket reduce el trabajo necesario para pasar de un turno al siguiente y mejora la latencia de extremo a extremo en cadenas largas. En ejecuciones con 20 o más llamadas a herramientas, hemos observado mejoras de hasta aproximadamente un 40 % en la velocidad de ejecución de extremo a extremo.

Conectarse y crear respuestas

Instala las dependencias de WebSocket con pip install "openai[realtime]>=3.8.0" para Python, npm install openai@^7.10.0 ws para JavaScript o gem install openai async-websocket para Ruby.

Para Go, ejecuta go get github.com/openai/openai-go/v3@v3.98.0. Para Java, agrega la dependencia de Maven com.openai:openai-java:4.101.0 y usa Java 17 o una versión posterior para estos ejemplos. Estas versiones de los SDK de Go y Java ofrecen compatibilidad nativa con WebSocket en la API de Responses.

En el modo WebSocket, inicia cada turno enviando un evento response.create desde el cliente. El contenido del evento tiene la misma estructura que el cuerpo de una solicitud de creación de respuestas, salvo que se omiten los campos específicos del transporte, como stream y background.

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

Los clientes pueden preparar el estado de la solicitud con anticipación enviando response.create con generate: false. Esto resulta útil cuando ya sabes qué herramientas, instrucciones o mensajes personalizados vas a enviar en un próximo turno. generate: false prepara el estado de la solicitud sin devolver una salida del modelo, lo que permite iniciar más rápido el siguiente turno en el que se genere una respuesta. La solicitud de preparación devuelve un ID de respuesta que puedes usar con previous_response_id para encadenar respuestas, incluso en turnos posteriores de la cadena. En la siguiente sección se explica cómo continuar una sesión con previous_response_id y entradas incrementales.

Continuar con entradas incrementales

Para agregar instrucciones del usuario mientras se genera una respuesta, usa la reorientación durante el turno. Así se conserva el trabajo ya realizado y se incorporan las nuevas instrucciones al continuar. Usa el siguiente patrón de response.create para continuar entre turnos de la forma habitual y enviar resultados de herramientas.

Para continuar una ejecución, envía otro evento response.create con estos campos:

  • previous_response_id con el ID de la respuesta anterior.
  • input con solo los elementos nuevos, como las salidas de las herramientas y el siguiente mensaje del usuario.
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();
}

Cómo funciona la continuación

El modo WebSocket usa previous_response_id para encadenar respuestas de la misma forma que el modo HTTP, pero ofrece una vía de menor latencia para continuar a través del socket activo.

En una conexión WebSocket activa, el servicio conserva el estado reciente de las respuestas anteriores en una caché en memoria propia de la conexión. Cuando usas stream_id, cada canal conserva su respuesta más reciente en la caché. Continuar desde esa respuesta es rápido porque el servicio puede reutilizar el estado local de la conexión. Como el servicio conserva el estado de las respuestas anteriores solo en memoria y no lo escribe en disco, puedes usar el modo WebSocket de forma compatible con store=false y la retención cero de datos (ZDR).

Si un previous_response_id no está en la caché en memoria, el comportamiento depende de si guardas las respuestas:

  • Con store=true, el servicio puede recuperar el estado de respuestas anteriores a partir de sus ID si ese estado está almacenado. En ese caso, aún es posible continuar, pero se pierde la ventaja de latencia que ofrece la caché en memoria.
  • Con store=false (incluida la ZDR), no hay un estado almacenado al que recurrir. Si el ID no está en la caché, la solicitud devuelve previous_response_not_found.

Si una continuación en el mismo canal devuelve un error 4xx o 5xx, el servicio elimina de la caché local de la conexión el previous_response_id al que se hizo referencia. Si un fork entre canales devuelve un error, se conserva la respuesta de origen compartida para que el canal de origen pueda continuar.

Compactación y creación de respuestas nuevas

Si usas la compactación, hay dos formas de continuar:

Compactación del lado del servidor (context_management)

Cuando habilitas la compactación del lado del servidor (context_management con compact_threshold), el contexto se compacta durante la generación normal en /responses. En el modo WebSocket, continúa como de costumbre. Envía el siguiente evento response.create con el previous_response_id más reciente y solo los elementos de entrada nuevos.

Uso independiente de /responses/compact

El punto de acceso /responses/compact funciona de forma independiente y devuelve una nueva ventana de entrada compactada en lugar de un ID de respuesta. Después de la compactación, crea una respuesta nueva en tu conexión WebSocket. Usa la ventana compactada como input y agrega los siguientes elementos del usuario o de las herramientas.

Para iniciar una cadena nueva, omite previous_response_id o asígnale el valor null. Envía la salida compactada tal como la recibes, sin recortar la ventana devuelta.

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

Ejecutar conversaciones en paralelo

Puedes mantener conversaciones en paralelo en la misma conexión con el parámetro stream_id. Envía eventos response.create independientes, uno tras otro, con distintos valores de stream_id. El servidor puede procesarlos de forma concurrente en una sola conexión. Como sus eventos pueden intercalarse, usa un solo bucle de lectura y dirige cada evento al canal correspondiente según su stream_id.

Un stream_id identifica un canal ordenado dentro de una conexión WebSocket. Distingue entre stream_id y previous_response_id:

  • stream_id determina a dónde se envían los eventos y qué solicitudes se ejecutan en orden de llegada.
  • previous_response_id determina de qué respuesta anterior deriva la conversación.

Esta separación permite usar dos patrones.

una sola conexión WebSocket
├─ stream_id="planner"   redacta un plan de despliegue
└─ stream_id="research"  enumera los riesgos del despliegue

Las solicitudes con el mismo stream_id se ejecutan en orden de llegada, sin superponerse. Las solicitudes con distintos valores de stream_id pueden ejecutarse de forma concurrente.

Límites por conexión

  • Una conexión admite hasta 16 respuestas en curso en total entre los canales con nombre y el canal predeterminado. La conexión acepta más eventos response.create y los deja en cola hasta que finaliza una respuesta activa.
  • Una conexión admite hasta 32 valores distintos de stream_id para los flujos con nombre. El canal predeterminado implícito no cuenta para este límite. Cuando alcances el límite, reutiliza un stream_id existente o abre otra conexión.

Crear un fork de una conversación en un flujo nuevo

Para crear una rama a partir de una respuesta completada, envía su ID como previous_response_id junto con un nuevo stream_id. Mientras esa respuesta siga disponible, el flujo nuevo hereda su contexto y el flujo original puede continuar. Una vez iniciado el fork, ambas ramas pueden ejecutarse de forma concurrente porque usan distintos ID de flujo.

Con store=false (incluida la ZDR), solo se puede crear un fork entre canales si la respuesta de origen sigue en la caché local de la conexión. Si el fork queda en cola mientras el canal de origen avanza o falla, la respuesta de origen puede eliminarse de la caché antes de que se inicie el fork. En ese caso, el fork devuelve previous_response_not_found. Antes de continuar en el canal de origen, espera a que el canal del fork emita response.in_progress. También puedes volver a intentarlo con previous_response_id establecido en null y reenviar todo el contexto de entrada.

main:   resp_1 ──▶ resp_2 ──▶ resp_3
                       ╲
critic:                 resp_4 ──▶ resp_5

Si reutilizas un stream_id sin previous_response_id, se inicia una respuesta nueva sin continuar la conversación.

Estas son las llamadas principales:

# Un solo socket, dos conversaciones independientes.
send_create(connection, "planner", "Redacta un plan de despliegue.")
send_create(connection, "research", "Enumera los riesgos del despliegue.")

# Crea un fork de la respuesta de planner y luego continúa en paralelo en la rama original.
send_create(
    connection,
    "critic",
    "Identifica lo que falta en este plan.",
    previous_response_id=planner_response_id,
)
wait_for_in_progress(connection, "critic")
send_create(
    connection,
    "planner",
    "Agrega los pasos para revertir el despliegue.",
    previous_response_id=planner_response_id,
)

Ejemplo completo

Ejecuta conversaciones en paralelo y luego crea un fork de una de ellas
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();
}

Un stream_id debe tener entre 1 y 256 caracteres y solo puede contener letras, números, guiones bajos (_), guiones (-) y puntos (.). Úsalo solo en eventos response.create de WebSocket. Omítelo en las solicitudes HTTP POST /v1/responses.

En los flujos con nombre, los eventos del servidor incluyen el stream_id correspondiente, incluso los eventos finales y los errores específicos de una solicitud.

Si omites stream_id, la solicitud usa un canal predeterminado implícito y sus eventos no incluyen stream_id. Por lo demás, el canal predeterminado sigue las mismas reglas de orden y concurrencia que los flujos con nombre. Una cadena vacía no es un stream_id válido. Omite el campo para seleccionar el canal predeterminado.

Comportamiento y límites de la conexión

  • Los eventos de cada respuesta siguen el modelo de eventos de streaming de la API de Responses. Los eventos de distintos canales pueden intercalarse.
  • Las solicitudes con el mismo stream_id se ejecutan en orden de llegada, sin superponerse. Las solicitudes de distintos canales pueden ejecutarse de forma concurrente.
  • Las conexiones duran hasta 60 minutos. Vuelve a conectarte cuando alcances el límite.

Volver a conectarse y recuperar los canales

Cuando una conexión se cierra o alcanza el límite de 60 minutos, se pierde la caché local de la conexión para todos los canales. Abre una conexión WebSocket nueva y recupera cada canal de una de estas formas:

  1. Si guardaste una respuesta anterior (store=true) y tienes un ID de respuesta válido, continúa en ese canal con previous_response_id y los elementos de entrada nuevos.
  2. Si no puedes continuar en un canal (por ejemplo, porque usas store=false o ZDR, o porque recibiste previous_response_not_found), inicia una respuesta nueva. Establece previous_response_id en null (u omite el campo) y envía todo el contexto de entrada para el siguiente turno de ese canal.
  3. Si compactaste el contexto con /responses/compact, usa la ventana compactada que recibiste como base de input para la respuesta nueva y agrega al final los elementos más recientes del usuario o de las herramientas.

Errores que debes manejar

Cuando el servidor puede asociar un error con un canal con nombre, el evento de error incluye stream_id. Los demás canales pueden continuar después de un error que afecta solo a una solicitud.

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
}

Guías relacionadas