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

Mode WebSocket

Utilisez une seule connexion WebSocket persistante pour mener des conversations en parallèle, créer des forks de chaînes de réponses et envoyer des entrées incrémentales afin de réduire la latence des workflows agentiques.

L’API Responses prend en charge un mode WebSocket pour les workflows de longue durée qui font largement appel aux outils. En plus de réduire la latence, stream_id permet le multiplexage WebSocket : une seule connexion persistante à /v1/responses peut gérer des conversations en parallèle et forker une conversation existante vers un nouveau flux. Poursuivez chaque tour en envoyant uniquement les nouveaux éléments d’entrée accompagnés de previous_response_id.

Le mode WebSocket est compatible avec la politique de non-conservation des données (ZDR) et avec store=false.

Pourquoi utiliser le mode WebSocket

Le mode WebSocket est particulièrement utile lorsqu’un workflow implique de nombreux allers-retours entre le modèle et les outils, par exemple pour la programmation agentique ou les boucles d’orchestration avec des appels d’outils répétés.

Comme la connexion reste ouverte et que chaque tour n’envoie que les nouvelles données d’entrée, le mode WebSocket réduit le surcoût de continuation à chaque tour et améliore la latence de bout en bout sur les longues chaînes. Pour des séquences comportant au moins 20 appels d’outils, nous avons observé une exécution de bout en bout jusqu’à environ 40 % plus rapide.

Établissez une connexion et créez des réponses

Installez les dépendances WebSocket avec pip install "openai[realtime]>=3.8.0" pour Python, npm install openai@^7.10.0 ws pour JavaScript ou gem install openai async-websocket pour Ruby.

En mode WebSocket, commencez chaque tour en envoyant un événement response.create depuis le client. La charge utile reprend le corps habituel d’une requête de création Responses, à l’exception des champs propres au transport, comme stream et background, qui ne sont pas utilisés.

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

Les clients peuvent préparer l’état de la requête à l’avance en envoyant response.create avec generate: false. Cette option est utile lorsque vous connaissez déjà les outils, les instructions et/ou les messages personnalisés que vous comptez envoyer lors d’un prochain tour. generate: false ne renvoie aucune sortie du modèle, mais prépare l’état de la requête pour que le prochain tour de génération puisse démarrer plus rapidement. La requête de préparation renvoie un identifiant de réponse à partir duquel vous pouvez poursuivre la chaîne avec previous_response_id, y compris lors de tours ultérieurs de cette chaîne. La section suivante explique comment poursuivre une session à l’aide de previous_response_id et d’entrées incrémentales.

Poursuivez avec des entrées incrémentales

Pour ajouter des instructions utilisateur pendant la génération d’une réponse, utilisez la réorientation en cours de tour. La réorientation préserve le travail accompli et intègre les nouvelles instructions à la continuation. Utilisez le schéma response.create suivant pour poursuivre normalement entre deux tours et transmettre les résultats des outils.

Pour poursuivre une exécution, envoyez un nouvel événement response.create avec :

  • previous_response_id défini sur l’identifiant de la réponse précédente.
  • input contenant uniquement les nouveaux éléments, par exemple les sorties des outils et le prochain message utilisateur.
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();
}

Fonctionnement de la continuation

Le mode WebSocket utilise les mêmes règles de chaînage avec previous_response_id que le mode HTTP, mais ajoute un mécanisme de continuation à plus faible latence sur la connexion active.

Sur une connexion WebSocket active, le service conserve l’état des réponses précédentes récentes dans un cache en mémoire propre à la connexion. Lorsque vous utilisez stream_id, chaque voie conserve sa dernière réponse en cache. Poursuivre à partir de la dernière réponse de cette voie est donc rapide, car le service peut réutiliser l’état propre à la connexion. Comme le service conserve l’état des réponses précédentes uniquement en mémoire, sans l’écrire sur disque, vous pouvez utiliser le mode WebSocket de manière compatible avec store=false et la politique de non-conservation des données (ZDR).

Si un previous_response_id ne figure pas dans le cache en mémoire, le comportement dépend du stockage ou non des réponses :

  • Avec store=true, le service peut recharger l’état associé à d’anciens identifiants de réponse à partir des données persistantes, lorsqu’elles sont disponibles. La continuation peut toujours fonctionner, mais elle perd le gain de latence apporté par le cache en mémoire.
  • Avec store=false (y compris avec ZDR), aucune donnée persistante ne permet de prendre le relais. Si l’identifiant n’est pas en cache, la requête renvoie previous_response_not_found.

Si une continuation sur la même voie renvoie une erreur 4xx ou 5xx, le service retire du cache propre à la connexion l’entrée correspondant au previous_response_id référencé. Un fork vers une autre voie qui renvoie une erreur préserve le parent partagé pour que la voie source puisse continuer.

Compactage et création de nouvelles réponses

Si vous utilisez le compactage, deux méthodes de continuation sont possibles :

Compactage côté serveur (context_management)

Lorsque vous activez le compactage côté serveur (context_management avec compact_threshold), le compactage intervient pendant la génération normale via /responses. En mode WebSocket, poursuivez comme d’habitude : envoyez le prochain événement response.create avec le dernier previous_response_id et uniquement les nouveaux éléments d’entrée.

Appel autonome à /responses/compact

Le point de terminaison /responses/compact autonome renvoie une nouvelle fenêtre d’entrée compactée, et non un identifiant de réponse. Après le compactage, créez une nouvelle réponse sur votre connexion WebSocket en utilisant la fenêtre compactée comme input, avec les prochains éléments provenant de l’utilisateur ou des outils.

Commencez une nouvelle chaîne en omettant previous_response_id ou en le définissant sur null. Transmettez la sortie compactée telle quelle ; ne retirez aucun élément de la fenêtre renvoyée.

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

Menez des conversations en parallèle

Vous pouvez maintenir des conversations en parallèle sur une même connexion à l’aide du paramètre stream_id. Envoyez successivement des événements response.create indépendants avec des valeurs stream_id différentes. Le serveur peut les exécuter simultanément sur une seule connexion. Leurs événements peuvent s’entrelacer : conservez donc une seule boucle de lecture et acheminez chaque événement selon son stream_id.

Un stream_id nomme une voie ordonnée sur une connexion WebSocket. Distinguez bien stream_id et previous_response_id :

  • stream_id détermine la destination des événements et les requêtes qui s’exécutent dans l’ordre premier entré, premier sorti.
  • previous_response_id détermine la filiation de la conversation.

Cette séparation permet deux usages utiles.

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

Les requêtes partageant le même stream_id restent traitées dans l’ordre premier entré, premier sorti et ne se chevauchent pas. Les requêtes dont les valeurs stream_id diffèrent peuvent s’exécuter simultanément.

Limites par connexion

  • Une connexion peut avoir jusqu’à 16 réponses actives en cours d’exécution, toutes voies confondues, nommées ou par défaut. La connexion accepte les événements response.create supplémentaires et les met en attente jusqu’à ce qu’une réponse active se termine.
  • Une connexion accepte jusqu’à 32 valeurs stream_id distinctes pour les flux nommés. La voie implicite par défaut ne compte pas dans cette limite. Une fois la limite atteinte, réutilisez un stream_id existant ou ouvrez une nouvelle connexion.

Forkez une conversation vers un nouveau flux

Pour créer une branche à partir d’une réponse terminée, envoyez son identifiant dans previous_response_id avec un nouveau stream_id. Tant que cette réponse reste disponible, le nouveau flux hérite de son contexte et le flux d’origine peut continuer. Une fois le fork démarré, les deux branches peuvent s’exécuter simultanément, car elles utilisent des identifiants de flux différents.

Avec store=false (y compris avec ZDR), un fork vers une autre voie nécessite que le parent reste dans le cache propre à la connexion. Si le fork est mis en attente pendant que la voie source avance ou échoue, le parent peut être retiré du cache avant le démarrage du fork, qui renvoie alors previous_response_not_found. Attendez que la voie du fork émette response.in_progress avant de faire avancer la voie source, ou réessayez avec previous_response_id défini sur null en renvoyant l’intégralité du contexte d’entrée.

main:   resp_1 ──▶ resp_2 ──▶ resp_3

critic:                 resp_4 ──▶ resp_5

Réutiliser un stream_id sans previous_response_id démarre une nouvelle réponse ; cela ne poursuit pas la conversation.

Voici les principaux appels :

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

Exemple complet

Menez des conversations en parallèle, puis forkez l’une d’elles
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 doit comporter entre 1 et 256 caractères et ne peut contenir que des lettres, des chiffres, des traits de soulignement (_), des traits d’union (-) et des points (.). Utilisez-le uniquement dans les événements WebSocket response.create ; ne l’incluez pas dans les requêtes HTTP POST /v1/responses.

Pour les flux nommés, les événements du serveur incluent le stream_id correspondant, y compris les événements de fin et les erreurs propres à une requête.

Si vous omettez stream_id, la requête utilise une voie implicite par défaut et ses événements n’incluent pas stream_id. Cette voie suit par ailleurs les mêmes règles d’ordre et d’exécution simultanée que les flux nommés. Une chaîne vide n’est pas une valeur valide pour stream_id ; omettez le champ pour sélectionner la voie par défaut.

Comportement et limites des connexions

  • Les événements de chaque réponse suivent le modèle existant des événements de streaming de Responses. Les événements de différentes voies peuvent s’entrelacer.
  • Les requêtes partageant le même stream_id s’exécutent dans l’ordre premier entré, premier sorti et ne se chevauchent pas. Les requêtes sur des voies différentes peuvent s’exécuter simultanément.
  • Les connexions durent jusqu’à 60 minutes. Rétablissez la connexion une fois cette limite atteinte.

Rétablissez la connexion et reprenez l’exécution

Lorsqu’une connexion se ferme (ou atteint la limite de 60 minutes), son cache local disparaît pour toutes les voies. Ouvrez une nouvelle connexion WebSocket et reprenez chaque voie selon l’une des méthodes suivantes :

  1. Si vous avez stocké une réponse précédente (store=true) et disposez d’un identifiant de réponse valide, poursuivez sur cette voie avec previous_response_id et de nouveaux éléments d’entrée.
  2. Si vous ne pouvez pas poursuivre sur une voie (par exemple, avec store=false/ZDR ou en cas d’erreur previous_response_not_found), démarrez une nouvelle réponse en définissant previous_response_id sur null (ou en l’omettant) et envoyez le contexte d’entrée complet pour le prochain tour de cette voie.
  3. Si vous avez compacté le contexte avec /responses/compact, utilisez la fenêtre compactée renvoyée comme base pour input dans cette nouvelle réponse, puis ajoutez les derniers éléments provenant de l’utilisateur ou des outils.

Erreurs à gérer

Lorsque le serveur peut associer une erreur à une voie nommée, l’événement d’erreur inclut stream_id. Les autres voies peuvent continuer après une erreur limitée à une requête.

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
}