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

WebRTC

Conecta aplicaciones de voz con WebRTC.

Elige la API que usas para ver los pasos de conexión y los eventos de sesión.

Conectar un navegador a GPT-Live

Recomendamos WebRTC para las conexiones de audio con GPT-Live en aplicaciones web, móviles y de servidor. Esta guía explica cómo establecer la conexión desde un navegador. El audio del micrófono y la voz generada se transmiten por las pistas multimedia que se negocian al establecer la conexión. Un canal de datos transmite eventos JSON para las transcripciones, las actualizaciones de sesión y las tareas delegadas.

El navegador crea una oferta mediante el protocolo de descripción de sesión (SDP). El servidor de tu aplicación envía la oferta mediante POST /v1/live/sessions con la clave de API del proyecto y recibe una respuesta SDP. Guarda la clave y la configuración de la sesión en tu servidor de confianza.

Antes de empezar

Necesitas lo siguiente:

  • Una clave de API del proyecto con acceso a GPT-Live.
  • Un entorno de ejecución de servidor compatible con el ejemplo del SDK que elijas. El ejemplo de Node.js requiere Node.js 22.6 o posterior.
  • Un navegador con permiso para usar el micrófono y acceso a la aplicación mediante HTTPS o desde localhost.

El ejemplo usa la delegación de Responses con gpt-5.6-terra y la búsqueda web alojada. Para obtener instrucciones sobre el backend y las herramientas de la aplicación, consulta Delegación y herramientas. Para obtener información sobre el consumo de voz y del backend, consulta Optimización de costos.

Secuencia de conexión

  1. Cuando el usuario realice una acción, solicita acceso al micrófono y agrega sus pistas a una conexión entre pares.
  2. Crea el canal de datos y registra los detectores de eventos antes de crear la oferta SDP.
  3. Establece la descripción local, espera a que termine la recopilación de candidatos ICE y envía la oferta a tu servidor.
  4. Desde tu servidor, envía a OpenAI una solicitud POST con un cuerpo JSON que contenga session y transport: { type: "webrtc", sdp: ... }.
  5. Usa la respuesta SDP recibida como descripción remota. Espera a recibir session.started en el canal de datos antes de enviar comandos de la aplicación.

La solicitud HTTP inicia la sesión. No envíes session.start por el canal de datos. La cadena oai-events del ejemplo es la etiqueta del canal de datos.

Al crear una sesión WebRTC con POST /v1/live/sessions, se cobran 15 segundos de voz durante la inicialización. Una vez que la sesión está en curso, ese importe se descuenta de los cargos por duración. No se suman 15 segundos a la duración de la sesión. Para saber cómo se calculan los costos, consulta Cargos por inicialización de WebRTC.

Crear el servidor de la aplicación

Guarda el ejemplo del servidor en un directorio nuevo y configura OPENAI_API_KEY en el entorno del servidor. Para Node.js, usa server.mjs e instala openai y express con npm install openai express. Para Python, instala openai. En este ejemplo, el servidor escucha en 127.0.0.1, acepta solicitudes de sesión de http://localhost:3000 y sirve index.html desde el directorio donde lo ejecutas.

Para Go, guarda el ejemplo como main.go, inicializa un módulo con go mod init live-demo e instala github.com/openai/openai-go/v3@v3.85.1 con go get. Ejecuta go run . con Go 1.26 o posterior. Para Java, usa Java 17 o posterior, agrega la dependencia de Maven com.openai:openai-java:4.95.0 y ejecuta la clase LiveConnectionWebrtcExample. Para Ruby 3.3 o posterior, instala openai en su versión 0.110.0 y webrick con gem install openai -v 0.110.0 y gem install webrick, guarda el ejemplo como server.rb y ejecuta ruby server.rb.

Elige un lenguaje para el servidor. Todas las variantes sirven index.html y exponen el mismo punto de acceso /api/session en el puerto 3000. Usa una versión del SDK compatible con Live. Ejecuta solo una variante a la vez.

import express from "express";
import OpenAI from "openai";
import { readFile } from "node:fs/promises";
import { resolve } from "node:path";

const app = express();
const client = new OpenAI({ maxRetries: 0 });
const port = 3000;
const origin = `http://localhost:${port}`;
const indexPath = resolve("index.html");

app.use(express.json({ limit: "64kb" }));
app.get("/", async (_request, response) => {
  response.type("html").send(await readFile(indexPath, "utf8"));
});

// Local-only demo. Add your application's authentication and authorization
// before exposing session creation to other users.
app.post("/api/session", async (request, response) => {
  if (request.headers.origin !== origin) {
    response.status(403).json({ error: "Unexpected request origin" });
    return;
  }
  if (typeof request.body?.sdp !== "string" || !request.body.sdp.trim()) {
    response.status(400).json({ error: "An SDP offer is required" });
    return;
  }
  if (!process.env.OPENAI_API_KEY) {
    response.status(503).json({ error: "Set OPENAI_API_KEY on the server" });
    return;
  }

  try {
    const result = await client.live.create({
      session: {
        model: "gpt-live-1",
        instructions:
          "Be concise. Delegate requests needing current information to the backend, which can search the web.",
        delegation: {
          type: "responses",
          responses: {
            model: "gpt-5.6-terra",
            instructions:
              "Use web search when current facts are needed. Return concise, grounded results for a spoken conversation.",
            tools: [{ type: "web_search" }],
            tool_choice: "auto",
          },
        },
      },
      transport: {
        type: "webrtc",
        sdp: request.body.sdp,
      },
    });
    // Preserve the SDK's typed session ID and SDP answer.
    response.status(201).json(result);
  } catch (error) {
    if (!(error instanceof OpenAI.APIError)) throw error;
    console.error("Live session creation failed", error.status);
    response
      .status(error.status ?? 502)
      .json({ error: "Live session creation failed" });
  }
});

app.listen(port, "127.0.0.1", () => console.log(`Open ${origin}`));

Antes de permitir que otros usuarios accedan al servidor, protege /api/session con la autenticación, la autorización y los límites de solicitudes de tu aplicación, y usa HTTPS. La comprobación del origen en este ejemplo local no autentica a los usuarios.

Crear el cliente para el navegador

Crea index.html en el directorio donde ejecutas el servidor:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>GPT-Live connection</title>
  </head>
  <body>
    <script type="module">
      // Paste the browser code below here.
    </script>
  </body>
</html>

Pega el siguiente código dentro del script de tipo módulo. El código agrega controles para iniciar y finalizar la conversación, conecta el micrófono y la salida de audio, y procesa los eventos de sesión. /api/session es una ruta del servidor de tu aplicación.

const start = document.createElement("button");
start.textContent = "Start conversation";
const stop = document.createElement("button");
stop.textContent = "End conversation";
stop.disabled = true;
const status = document.createElement("p");
const audio = new Audio();
audio.autoplay = true;
audio.controls = true;
document.body.append(start, stop, status, audio);

let peer;

let events;

let microphone;

let closeTimeout;
let ready = false;
let finalized = false;

function cleanup() {
  clearTimeout(closeTimeout);
  microphone?.getTracks().forEach((track) => track.stop());
  const closingEvents = events;
  events = undefined;
  closingEvents?.close();
  peer?.close();
  audio.srcObject = null;
  ready = false;
  start.disabled = false;
  stop.disabled = true;
}

start.addEventListener("click", async () => {
  start.disabled = true;
  finalized = false;
  status.textContent = "Connecting…";
  try {
    const connection = new RTCPeerConnection();
    peer = connection;
    connection.addEventListener("track", (event) => {
      audio.srcObject = new MediaStream([event.track]);
      audio.play().catch(() => {
        status.textContent =
          "Select play on the audio controls to hear the assistant.";
      });
    });
    microphone = await navigator.mediaDevices.getUserMedia({ audio: true });
    for (const track of microphone.getAudioTracks()) {
      connection.addTrack(track, microphone);
    }

    // Create the event channel before creating the SDP offer.
    events = connection.createDataChannel("oai-events");
    events.addEventListener("message", ({ data }) => {
      const event = JSON.parse(data);
      if (event.type === "session.started") {
        ready = true;
        stop.disabled = false;
        status.textContent = "Connected: " + event.session.id;
      } else if (event.type === "session.closed") {
        finalized = true;
        console.log("Final session usage", event.usage);
        status.textContent = "Conversation ended.";
        cleanup();
      } else {
        // Save transcript and nested Responses events as needed by your app.
        console.log(event);
      }
    });
    events.addEventListener("close", (event) => {
      if (event.target !== events) return;
      if (!finalized) {
        status.textContent = "Disconnected without final session usage.";
        cleanup();
      }
    });

    const offer = await connection.createOffer();
    await connection.setLocalDescription(offer);
    if (connection.iceGatheringState !== "complete") {
      await new Promise((resolve, reject) => {
        const timeout = setTimeout(() => {
          connection.removeEventListener("icegatheringstatechange", onState);
          reject(new Error("Timed out while gathering ICE candidates"));
        }, 10_000);
        function onState() {
          if (connection.iceGatheringState !== "complete") return;
          clearTimeout(timeout);
          connection.removeEventListener("icegatheringstatechange", onState);
          resolve(undefined);
        }
        connection.addEventListener("icegatheringstatechange", onState);
        onState();
      });
    }

    const sdp = connection.localDescription?.sdp;
    if (!sdp) throw new Error("Missing local SDP offer");
    const response = await fetch("/api/session", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ sdp }),
    });
    if (!response.ok) throw new Error(await response.text());

    const result = await response.json();
    console.log("Created session", result.session.id);
    await connection.setRemoteDescription({
      type: "answer",
      sdp: result.transport.sdp,
    });
    // The HTTP request started this session. Do not send session.start here.
  } catch (error) {
    status.textContent =
      error instanceof Error ? error.message : String(error);
    cleanup();
  }
});

stop.addEventListener("click", () => {
  if (!ready || !events || events.readyState !== "open") return;
  stop.disabled = true;
  status.textContent = "Finishing the conversation…";
  // The session.closed handler is already registered. Keep media and events
  // alive while pending work drains; only clean up after the final event.
  events.send(JSON.stringify({ type: "session.close" }));
  closeTimeout = setTimeout(() => {
    status.textContent = "Incomplete finalization: no session.closed event.";
    cleanup();
  }, 15_000);
});

Ejecuta el servidor que elegiste (node server.mjs o python server.py), abre http://localhost:3000 y selecciona Start conversation. Cuando el estado cambie a Connected, haz una pregunta que requiera información actualizada para probar la búsqueda web alojada. Si el navegador bloquea la reproducción automática, usa los controles de audio.

Leer la respuesta de la sesión

Si la solicitud se completa correctamente, recibes el código HTTP 201 y un cuerpo JSON con el ID de la sesión y la respuesta SDP:

{
  "session": { "id": "live_123" },
  "transport": { "type": "webrtc", "sdp": "<SDP answer>" }
}

Lee result.session.id y pasa result.transport.sdp a setRemoteDescription. Trata el ID de la sesión como un valor opaco y consérvalo tal como lo recibes, incluido el prefijo.

Para reducir la latencia al establecer la conexión, consulta WebRTC con WARP.

Manejar el audio y los eventos

Envía el audio del micrófono y recibe la voz generada a través de las pistas multimedia. WebRTC negocia el formato de audio mediante SDP, así que omite audio.format en la configuración de la sesión. No envíes session.input_audio.append ni esperes recibir session.output_audio.delta por el canal de datos.

Usa el canal de datos para recibir actualizaciones incrementales de las transcripciones, enviar comandos de sesión y recibir mensajes response.event anidados. Consulta Administrar sesiones para saber cómo procesar las transcripciones y los eventos del ciclo de vida de la sesión. Si tu servidor necesita su propia conexión para eventos, consulta Controles del servidor.

Para finalizar la conversación, envía session.close y sigue recibiendo eventos hasta que llegue session.closed. Luego cierra la conexión entre pares y las pistas del micrófono. El ejemplo registra el detector del evento final antes de enviar el comando. Si la conexión falla o se agota el tiempo de espera antes de que llegue ese evento, el consumo final queda sin confirmar. Para saber cómo procesar los datos de consumo final, consulta Consumo y cierre ordenado.