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
- Cuando el usuario realice una acción, solicita acceso al micrófono y agrega sus pistas a una conexión entre pares.
- Crea el canal de datos y registra los detectores de eventos antes de crear la oferta SDP.
- Establece la descripción local, espera a que termine la recopilación de candidatos ICE y envía la oferta a tu servidor.
- Desde tu servidor, envía a OpenAI una solicitud POST con un cuerpo JSON que contenga
sessionytransport: { type: "webrtc", sdp: ... }. - Usa la respuesta SDP recibida como descripción remota. Espera a recibir
session.starteden 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.
WebRTC ofrece un amplio conjunto de interfaces estándar para crear aplicaciones en tiempo real. La Realtime API de OpenAI permite conectarse a modelos en tiempo real mediante una conexión WebRTC entre pares.
Para crear aplicaciones de voz a voz en el navegador, recomendamos empezar por la guía Agentes de voz, que explica cómo administrar sesiones de Realtime con las funciones auxiliares y las API de alto nivel del SDK de agentes. La interfaz de WebRTC es potente y flexible, pero opera a un nivel más bajo que el SDK de agentes.
Si te conectas a un modelo de Realtime desde el cliente, por ejemplo, desde un navegador o un dispositivo móvil, recomendamos usar WebRTC en lugar de WebSockets para obtener un rendimiento más estable.
Para saber más sobre cómo crear interfaces de usuario con WebRTC, consulta la documentación de MDN.
Descripción general
La Realtime API admite dos mecanismos de conexión desde el navegador: la interfaz unificada y las claves de API efímeras (generadas mediante la API REST de OpenAI). Usa la interfaz unificada para simplificar la configuración y establecer conexiones más rápido. Con este método, el servidor de tu aplicación forma parte de la ruta crítica de inicialización de la sesión.
Conectarse mediante la interfaz unificada
Para inicializar una conexión WebRTC mediante la interfaz unificada desde un navegador, el proceso es el siguiente:
- El navegador envía una solicitud a un servidor que tú controlas con los datos SDP de su conexión WebRTC entre pares.
- El servidor combina esos datos SDP con la configuración de la sesión en un formulario multiparte y lo envía a la Realtime API de OpenAI. Para autenticar la solicitud, usa su clave de API estándar.
Crear una sesión mediante la interfaz unificada
Para crear una sesión de la Realtime API mediante la interfaz unificada, crea una pequeña aplicación de servidor o integra esta función en una aplicación existente para enviar una solicitud a /v1/realtime/calls. Usa una clave de API estándar para autenticar la solicitud en tu servidor de backend.
Crea una sesión de la Realtime API desde el servidor de tu aplicación:
import express from "express";
const app = express();
// Parse raw SDP payloads posted from the browser
app.use(express.text({ type: ["application/sdp", "text/plain"] }));
const sessionConfig = JSON.stringify({
type: "realtime",
model: "gpt-realtime-2.1",
audio: { output: { voice: "marin" } },
});
// An endpoint which creates a Realtime API session.
app.post("/session", async (req, res) => {
const fd = new FormData();
fd.set("sdp", req.body);
fd.set("session", sessionConfig);
try {
const r = await fetch("https://api.openai.com/v1/realtime/calls", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
"OpenAI-Safety-Identifier": "hashed-user-id",
},
body: fd,
});
// Send back the SDP we received from the OpenAI REST API
const sdp = await r.text();
res.set(
"Content-Type",
r.ok ? "application/sdp" : (r.headers.get("Content-Type") ?? "text/plain")
);
const location = r.headers.get("Location");
if (location) res.set("Location", location);
res.status(r.status).send(sdp);
} catch (error) {
console.error("Session creation error:", error);
res.status(500).json({ error: "Failed to create session" });
}
});
app.listen(3000);Si tu aplicación asigna un identificador de seguridad a cada usuario final, inclúyelo en el encabezado OpenAI-Safety-Identifier de la solicitud que envía el servidor. Usa un valor estable que proteja la privacidad, como el hash de un ID interno de usuario. Configura el encabezado en tu backend de confianza, nunca en el navegador.
Conectarse al servidor
En el navegador, puedes usar las API estándar de WebRTC para conectarte a la Realtime API a través del servidor de tu aplicación. El cliente envía sus datos SDP al servidor en una solicitud POST.
// Create a peer connection
const pc = new RTCPeerConnection();
// Set up to play remote audio from the model
audioElement.current = document.createElement("audio");
audioElement.current.autoplay = true;
pc.ontrack = (e) => (audioElement.current.srcObject = e.streams[0]);
// Add local audio track for microphone input in the browser
const ms = await navigator.mediaDevices.getUserMedia({
audio: true,
});
pc.addTrack(ms.getTracks()[0]);
// Set up data channel for sending and receiving events
const dc = pc.createDataChannel("oai-events");
// Start the session using the Session Description Protocol (SDP)
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
const sdpResponse = await fetch("/session", {
method: "POST",
body: offer.sdp,
headers: {
"Content-Type": "application/sdp",
},
});
const answer = {
type: "answer",
sdp: await sdpResponse.text(),
};
await pc.setRemoteDescription(answer);Conectarse con un token efímero
Para inicializar una conexión WebRTC con una clave de API efímera desde un navegador, el proceso es el siguiente:
- El navegador envía una solicitud a un servidor que tú controlas para que genere una clave de API efímera.
- Tu servidor usa una clave de API estándar para solicitar una clave efímera a la API REST de OpenAI y devuelve la nueva clave al navegador.
- El navegador usa la clave efímera para autenticar la sesión directamente con la Realtime API de OpenAI mediante una conexión WebRTC entre pares.
Crear un token efímero
Para crear un token efímero que puedas usar en el cliente, crea una pequeña aplicación de servidor o integra esta función en una aplicación existente para solicitar una clave efímera a la API REST de OpenAI. Usa una clave de API estándar para autenticar la solicitud en tu servidor de backend.
Crea una clave de API efímera desde el servidor de tu aplicación mediante la API REST:
import express from "express";
const app = express();
const sessionConfig = JSON.stringify({
session: {
type: "realtime",
model: "gpt-realtime-2.1",
audio: {
output: {
voice: "marin",
},
},
},
});
// An endpoint which would work with the client code above - it returns
// the contents of a REST API request to this protected endpoint
app.get("/token", async (req, res) => {
res.set("Cache-Control", "no-store");
try {
const response = await fetch(
"https://api.openai.com/v1/realtime/client_secrets",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
"Content-Type": "application/json",
"OpenAI-Safety-Identifier": "hashed-user-id",
},
body: sessionConfig,
}
);
const data = await response.json();
res.status(response.status).json(data);
} catch (error) {
console.error("Token generation error:", error);
res.status(500).json({ error: "Failed to generate token" });
}
});
app.listen(3000);Puedes crear un punto de acceso como este en cualquier plataforma que pueda enviar y recibir solicitudes HTTP. Asegúrate de usar las claves de API estándar de OpenAI únicamente en el servidor, nunca en el navegador.
Si usas tokens efímeros, establece OpenAI-Safety-Identifier en la solicitud que envía el servidor para crear el secreto del cliente. La Realtime API vincula el identificador al token efímero generado, por lo que el navegador no necesita enviar el identificador de seguridad cuando se conecta con ese token.
Conectarse al servidor
En el navegador, puedes usar las API estándar de WebRTC para conectarte a la Realtime API con un token efímero. Primero, el cliente obtiene un token del punto de acceso de tu servidor. Luego envía sus datos SDP, junto con el token efímero, a la Realtime API en una solicitud POST.
// Get a session token for OpenAI Realtime API
const tokenResponse = await fetch("/token");
const data = await tokenResponse.json();
const EPHEMERAL_KEY = data.value;
// Create a peer connection
const pc = new RTCPeerConnection();
// Set up to play remote audio from the model
audioElement.current = document.createElement("audio");
audioElement.current.autoplay = true;
pc.ontrack = (e) => (audioElement.current.srcObject = e.streams[0]);
// Add local audio track for microphone input in the browser
const ms = await navigator.mediaDevices.getUserMedia({
audio: true,
});
pc.addTrack(ms.getTracks()[0]);
// Set up data channel for sending and receiving events
const dc = pc.createDataChannel("oai-events");
// Start the session using the Session Description Protocol (SDP)
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
const sdpResponse = await fetch("https://api.openai.com/v1/realtime/calls", {
method: "POST",
body: offer.sdp,
headers: {
Authorization: `Bearer ${EPHEMERAL_KEY}`,
"Content-Type": "application/sdp",
},
});
const answer = {
type: "answer",
sdp: await sdpResponse.text(),
};
await pc.setRemoteDescription(answer);Reducir la latencia de conexión con WARP
WebRTC Abridged Roundtrip Protocol (WARP) reduce el tiempo necesario para iniciar una sesión de voz de la Realtime API. Puedes habilitar sus optimizaciones por separado o combinarlas para realizar la negociación completa de WARP.
Consulta WebRTC con WARP para obtener información sobre la configuración de clientes nativos, la compatibilidad con navegadores, las instrucciones para las pruebas de origen y el flujo de conexión unificado.
Enviar y recibir eventos
Para administrar las sesiones de la Realtime API, se combinan los eventos que envía el cliente, que tú emites desde tu aplicación, con los eventos que envía el servidor, que la Realtime API genera para señalar lo que ocurre durante el ciclo de vida de la sesión.
Cuando te conectas a un modelo de Realtime mediante WebRTC, no necesitas procesar los eventos de audio del modelo con el mismo nivel de detalle que al usar WebSockets. Si configuras el objeto de conexión WebRTC entre pares como se indica arriba, el objeto se encarga de procesarlos.
Para enviar y recibir otros eventos del cliente y del servidor, puedes usar el canal de datos de la conexión WebRTC entre pares.
// Reuse dc from the preceding browser connection example.
// Listen for server events
dc.addEventListener("message", (e) => {
const event = JSON.parse(e.data);
console.log(event);
});
// Send client events
const event = {
type: "conversation.item.create",
item: {
type: "message",
role: "user",
content: [
{
type: "input_text",
text: "hello there!",
},
],
},
};
const sendGreeting = () => dc.send(JSON.stringify(event));
if (dc.readyState === "open") {
sendGreeting();
} else if (dc.readyState === "connecting") {
dc.addEventListener("open", sendGreeting, { once: true });
} else {
throw new Error("The data channel has closed before greeting was sent.");
}Para saber más sobre cómo administrar conversaciones de Realtime, consulta la guía de conversaciones de Realtime.
Prueba la Realtime API con WebRTC en esta aplicación de ejemplo ligera.