OpenAI Realtime API con WebRTC: cómo crear agentes de voz sin filtrar claves ni disparar costes

Un agente de voz en tiempo real no es solo streaming de audio. Necesita una frontera clara entre navegador, backend, Realtime API, tools, permisos, VAD, logs y costes para no convertirse en una demo peligrosa.

Compartir
OpenAI Realtime API con WebRTC: cómo crear agentes de voz sin filtrar claves ni disparar costes

Un agente de voz en tiempo real no es solo streaming de audio. Necesita una frontera clara entre navegador, backend, Realtime API, tools, permisos, VAD, logs y costes para no convertirse en una demo peligrosa.

OpenAI Realtime API con WebRTC permite crear agentes de voz de baja latencia donde el navegador envía y recibe audio por una conexión WebRTC, mientras un backend confiable inicializa la sesión, protege la API key real y define tools, permisos, logs y presupuesto.

Checklist

Qué es Realtime API con WebRTC

Realtime API mantiene una sesión abierta para enviar audio, recibir eventos, actualizar estado y dejar que el modelo responda mientras la conversación sigue viva. WebRTC es la vía recomendada para experiencias de voz en navegador porque mueve audio en tiempo real con menos fricción que intentar hacer streaming manual desde JavaScript.

La diferencia frente a un chatbot normal es importante. En chat puedes tolerar segundos de latencia, reintentos visibles y respuestas largas. En voz, 700 ms extra se sienten como interrupción, una tool lenta rompe el turno y una respuesta prolija parece mala UX aunque sea correcta.

Para developers, la arquitectura mental correcta es esta: el navegador captura audio y reproduce audio; el backend crea o negocia la sesión; Realtime API gestiona el modelo y eventos; tus sistemas internos ejecutan acciones con permisos mínimos; observabilidad y costes se miden por sesión, turno y tool call.

Diagrama de agente de voz con navegador, backend que emite token efímero, conexión WebRTC, canal de datos, modelo realtime, tools, guardrails y registro de costes
La frontera clave no es el audio: es separar cliente, backend confiable, sesión realtime, tools internas y controles de seguridad. El navegador nunca debería llevar la API key real.

Flujo paso a paso

  • 1. El usuario abre la UI y concede permisos de micrófono. La app todavía no llama a tools ni abre una sesión privilegiada.
  • 2. El cliente pide a tu backend crear una sesión realtime. El backend autentica al usuario, decide modelo, voz, VAD, herramientas permitidas y presupuesto máximo.
  • 3. El navegador crea un `RTCPeerConnection`, añade el track de audio local y prepara un canal de datos para eventos.
  • 4. La SDP offer viaja al backend o a Realtime API según el patrón elegido. La respuesta SDP queda como remote description y la sesión empieza.
  • 5. El audio de entrada fluye por WebRTC. El modelo devuelve audio, transcripción, eventos de respuesta y posibles tool calls.
  • 6. Las acciones sensibles pasan por tu servidor o por un MCP remoto con superficie limitada y aprobación. El resultado vuelve a la sesión como output de tool.
  • 7. Al cerrar, guardas métricas: duración, tokens de audio/texto, tool calls, errores, VAD, interrupciones, coste estimado y si hubo aprobación humana.

Código mínimo: backend Node para iniciar sesión

<div style="margin:28px 0;border:1px solid #dbe3ef;border-radius:12px;overflow:hidden;background:#0f172a;"> <div style="padding:10px 14px;background:#111827;color:#cbd5e1;font:13px Consolas,monospace;">server.js</div> <pre style="margin:0;padding:18px;overflow:auto;color:#e5e7eb;font:13px/1.55 Consolas,monospace;"><code>import express from "express"; const app = express(); app.use(express.text({ type: ["application/sdp", "text/plain"] })); app.post("/api/realtime/call", async (req, res) =&gt; { const user = await requireUser(req); await enforceRealtimeQuota(user.id); const form = new FormData(); form.set("sdp", req.body); form.set("session", JSON.stringify({ type: "realtime", model: "gpt-realtime-2.1", audio: { input: { turn_detection: { type: "semantic_vad", eagerness: "medium", interrupt_response: true } }, output: { voice: "ash" } }, instructions: [ "Eres un asistente tecnico de soporte.", "Responde breve en voz.", "Confirma antes de ejecutar acciones con impacto externo.", "No repitas secretos, tokens ni datos personales." ].join("\n"), tools: [ { type: "function", name: "lookup_ticket", description: "Busca un ticket permitido para el usuario autenticado", parameters: { type: "object", properties: { ticket_id: { type: "string" } }, required: ["ticket_id"], additionalProperties: false } } ] })); const r = await fetch("https://api.openai.com/v1/realtime/calls", { method: "POST", headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}`, "OpenAI-Safety-Identifier": hashUser(user.id) }, body: form }); if (!r.ok) { res.status(r.status).send(await r.text()); return; } res.type("application/sdp").send(await r.text()); }); app.listen(3000);</code></pre> </div>

¿Te está sirviendo? Hay una dosis cada semana

Te resumo herramientas de IA para devs, agentes, MCP, seguridad y workflows en un email de 5 minutos. En español y sin ruido.

Suscribirme gratis

Puntos a revisar

Lo que conviene comprobar

Este ejemplo deja la API key en servidor, aplica autenticación antes de crear sesión y evita que el cliente decida tools o presupuesto. En producción añadiría CORS estricto, CSRF si aplica, logs por sesión, límites por minuto, cierre explícito de sesiones abandonadas y una lista de tools por rol.

Checklist

Código mínimo: cliente WebRTC

<div style="margin:28px 0;border:1px solid #dbe3ef;border-radius:12px;overflow:hidden;background:#0f172a;"> <div style="padding:10px 14px;background:#111827;color:#cbd5e1;font:13px Consolas,monospace;">client.js</div> <pre style="margin:0;padding:18px;overflow:auto;color:#e5e7eb;font:13px/1.55 Consolas,monospace;"><code>const pc = new RTCPeerConnection(); const audio = document.querySelector("audio#assistant"); audio.autoplay = true; pc.ontrack = (event) =&gt; { audio.srcObject = event.streams[0]; }; const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); for (const track of stream.getTracks()) { pc.addTrack(track, stream); } const dc = pc.createDataChannel("oai-events"); dc.onmessage = (event) =&gt; { const msg = JSON.parse(event.data); if (msg.type === "response.done") recordTurn(msg); if (msg.type.includes("function_call")) queueToolReview(msg); }; const offer = await pc.createOffer(); await pc.setLocalDescription(offer); const sdp = await fetch("/api/realtime/call", { method: "POST", headers: { "Content-Type": "application/sdp" }, body: offer.sdp }).then((r) =&gt; r.text()); await pc.setRemoteDescription({ type: "answer", sdp });</code></pre> </div>

El cliente debe ser aburrido: capturar audio, negociar WebRTC, reproducir audio y mostrar estado. No debería decidir scopes, modelo caro, credenciales ni tools disponibles. Si necesitas cambiar permisos durante la sesión, hazlo desde servidor con una política verificable.

Checklist

Tools, MCP y acciones: dónde poner el límite

Realtime puede trabajar con function tools, MCP remoto y conectores. La tentación es conectar CRM, calendario, base de datos y ticketing desde el primer día. Mala idea. Voz reduce la fricción de pedir acciones, así que también reduce el tiempo que tiene el usuario para revisar qué está autorizando.

Para function tools, prefiero que tu aplicación ejecute la lógica y devuelva `function_call_output`. Eso te permite aplicar permisos reales, validar argumentos, registrar payloads y pedir aprobación humana antes de mutaciones. Para MCP remoto, limita `allowed_tools` y asume que cualquier dato enviado en una tool call puede ser visto por ese servidor.

La regla operativa: lectura con datos no sensibles puede ser automática; escritura, compra, envío, borrado, cambio de permisos o acceso a datos personales debe tener confirmación visible. En voz, la confirmación debe ser corta pero concreta: acción, destino, identificador y consecuencia.

Mide interrupciones como métrica de producto. Muchas interrupciones pueden indicar que el agente habla demasiado, tarda en reconocer el objetivo o usa preambles molestos. No arregles eso solo subiendo modelo: muchas veces se corrige con prompts más claros y respuestas más cortas.

Costes: lo que debes registrar desde el día uno

El coste de voz no se parece al coste de un prompt textual aislado. Hay audio de entrada, audio de salida, texto, posibles tokens cacheados, tools, reintentos y sesiones largas. Además, una mala UX puede duplicar coste si el usuario repite porque el agente lo interrumpió o contestó tarde.

Registra por sesión: modelo, duración, tokens por modalidad, respuestas canceladas, interrupciones, errores de tool, número de turns, coste estimado y usuario o tenant. No guardes audio completo por defecto salvo que tengas base legal y política clara; muchas veces bastan transcripciones redaccionadas y métricas agregadas.

Realtime soporta prompt caching de forma automática cuando hay coincidencia de tokens entre respuestas, pero no lo trates como garantía de presupuesto. Diseña prompts estables, no metas contexto variable enorme al inicio y resume estado largo si la sesión se alarga.

Seguridad y privacidad específicas de voz

La voz introduce riesgos distintos. Puede contener datos personales que el usuario dice sin pensar, ruido de fondo, nombres de terceros o instrucciones inyectadas por otra persona cerca del micrófono. El agente no debería aceptar una orden sensible solo porque la oyó.

Puntos a revisar

Lo que conviene comprobar

Añade controles simples: autenticación antes de sesión, scopes por usuario, denylist de datos que no se leen en voz, confirmación para acciones externas, timeouts, cierre al cambiar de pestaña si procede, y logs que no creen otra fuga. Para equipos regulados, separa entorno de demo y producción desde el primer prototipo.

La prompt injection indirecta también aplica. Si el agente lee una web, ticket o documento y luego actúa, ese contenido debe tratarse como dato no confiable. Una frase dentro de un ticket no puede autorizar que el agente mande un email, borre un registro o exponga un secreto.

Checklist

Cuándo usar Agents SDK y cuándo ir directo a Realtime

Si solo necesitas una UI web de voz con una o dos tools, ir directo a Realtime API con WebRTC puede ser más claro. Controlas la negociación, ves los eventos y entiendes bien la frontera cliente-servidor.

Si necesitas handoffs, guardrails, especialistas, sesiones server-side, aprobación o integraciones complejas, mira la capa realtime del Agents SDK. La documentación describe `RealtimeAgent`, `RealtimeRunner`, `RealtimeSession`, handoffs y guardrails específicos para respuestas y function-tool calls.

No lo conviertas en religión de SDK. La pregunta buena es quién orquesta. Si el navegador solo captura audio, tu backend gestiona permisos y el SDK te ayuda a coordinar especialistas, tiene sentido. Si solo añade abstracción antes de entender el flujo, espera.

Checklist de producción

  • API key estándar solo en servidor, nunca en navegador.
  • Sesiones creadas tras autenticar usuario y aplicar cuota.
  • Modelo, voz, VAD, tools y presupuesto definidos en backend.
  • Tools separadas por rol, tenant y tipo de acción.
  • Confirmación explícita para operaciones irreversibles o externas.
  • Logs con IDs, métricas y errores; audio bruto solo si hay necesidad real y política.
  • Evals de conversación con interrupciones, ruido, IDs, acentos y peticiones ambiguas.
  • Monitor de coste por sesión y alertas por duración o reintentos.
  • Fallback textual o humano si falla WebRTC, tool crítica o guardrail.

Preguntas frecuentes

¿Qué es OpenAI Realtime API con WebRTC?

Es una forma de conectar una app de navegador a modelos realtime mediante WebRTC para enviar audio, recibir audio y manejar eventos de conversación o tools con baja latencia.

¿Puedo usar mi API key de OpenAI en el navegador?

No deberías. La clave estándar debe quedarse en servidor. El navegador debe usar una sesión creada por backend o una credencial efímera de vida corta.

¿Qué diferencia hay entre WebRTC y WebSocket en Realtime API?

WebRTC encaja mejor para audio directo desde navegador. WebSocket suele tener más sentido en pipelines server-side, telephony o cuando tu servidor controla el flujo de audio.

¿Realtime API puede llamar tools o MCP?

Sí. Puede usar function tools, MCP remoto y conectores, pero las acciones sensibles necesitan permisos estrechos, validación y aprobación cuando haya impacto externo.

¿Cómo controlo el coste de un agente de voz?

Mide duración, tokens de audio y texto, turns, reintentos, tools, respuestas canceladas y coste estimado por sesión. Añade cuotas por usuario o tenant desde el backend.

¿Cuándo usar Agents SDK para agentes de voz?

Úsalo cuando necesites handoffs, guardrails, orquestación server-side o especialistas. Para una UI web simple, Realtime API directo puede ser más transparente al principio.

Cómo lanzar un agente de voz con OpenAI Realtime API y WebRTC sin abrir demasiado el sistema

  1. Definir caso de uso. Elige una tarea de voz acotada, con datos permitidos y acciones claras.
  2. Diseñar frontera de confianza. Decide qué vive en navegador, backend, Realtime API y sistemas internos.
  3. Crear endpoint de sesión. Autentica usuario, aplica cuota y crea la sesión con API key solo en servidor.
  4. Conectar WebRTC. Captura micrófono, negocia SDP, reproduce audio y escucha eventos por data channel.
  5. Añadir tools mínimas. Empieza por lectura segura y valida argumentos antes de ejecutar negocio real.
  6. Configurar VAD. Prueba `server_vad` y `semantic_vad`, mide interrupciones y latencia percibida.
  7. Instrumentar coste. Registra duración, tokens, tools, errores, reintentos y coste estimado por sesión.
  8. Meter guardrails. Bloquea datos sensibles, acciones no autorizadas y contenido externo que intente cambiar instrucciones.
  9. Probar con conversaciones reales. Incluye ruido, acentos, IDs dictados, interrupciones y peticiones ambiguas antes de producción.

Fuentes y referencias

También te puede interesar

OpenAI Agents SDK: MCP, guardrails y tracingOpenTelemetry GenAI para agentesPrompt injection en agentes de IALiteLLM Proxy: gateway IA, costes y modelosMCP outputSchema y structuredContent

Recibe una lectura semanal de herramientas IA para devs

Cada semana te resumo herramientas de IA para devs, agentes, MCP, seguridad y workflows en un email de 5 minutos. En español y sin ruido.

Suscribirme gratis

Lo mejor de la IA para desarrolladores, cada martes

Newsletter en español, gratis. Las herramientas, modelos y trucos de IA para devs que de verdad importan — sin ruido.