← Todo el material
Curso Avanzado 24 de agosto de 2026

Voz e IA — Módulo 9: modelos speech-to-speech y realtime APIs

Noveno módulo del Curso de Voz e IA en tiempo real: los modelos que escuchan y hablan sin pasar por texto. Cómo funciona un modelo de audio nativo, las realtime APIs (OpenAI Realtime, Gemini Live), el transporte por WebSocket y WebRTC, y la decisión honesta entre cascada y speech-to-speech según tu caso.

☰ Índice del contenido

En el módulo 8 armamos un agente de voz con la arquitectura de cascada (pipeline de tres etapas): el audio entra, un modelo lo transcribe a texto (STT), un LLM piensa en texto, y otro modelo convierte la respuesta de vuelta a audio (TTS). Tres modelos en fila, cada uno con su latencia y su punto de control. Esa es una de las dos ramas grandes del diseño de voz. Este módulo abre la otra: los modelos speech-to-speech (de voz a voz, o audio nativo), donde un solo modelo escucha el audio y responde con audio sin pasar nunca por texto. No es una versión mejorada de la cascada: es un compromiso distinto, con lo que gana y lo que cede claramente marcados. La meta del módulo es que sepas leer ese trade-off y elegir con criterio, no por moda.

La tesis del módulo

Un modelo speech-to-speech elimina las traducciones a texto de ida y vuelta procesando audio directamente: gana latencia y expresividad —tono, emoción, risas, pausas— a cambio de control y observabilidad. No es “mejor” que la cascada: es otro punto en el mismo trade-off. La cascada te da un texto intermedio que puedes loggear, evaluar y filtrar; el audio nativo te da una conversación que suena viva pero cuyo “pensamiento” nunca se materializa en texto que puedas inspeccionar.

Cómo funciona el audio nativo

En la cascada, el audio se destruye tres veces. Cuando el STT (speech-to-text, voz a texto) transcribe “no… bueno, sí, dale”, produce el string no bueno si dale y tira todo lo demás: la duda en la voz, la subida de tono al final, el medio segundo de silencio antes del “dale”. El LLM nunca ve esa información porque no existe en el texto. La respuesta sale plana porque se generó desde un texto plano.

Un modelo de audio nativo no hace eso. Trabaja igual que un LLM, pero con una modalidad distinta de entrada y salida. Conecta con la idea que ya vimos en cómo ven los LLM multimodales: ahí un modelo tokenizaba imágenes para “verlas”; acá tokeniza sonido para “oírlo”. El mecanismo es el mismo, cambia la modalidad.

  1. Tokeniza el audio de entrada. Un codec de audio neuronal (neural audio codec, un modelo que comprime la onda de sonido en una secuencia de tokens discretos) convierte el audio del usuario en tokens de audio — la unidad mínima que el modelo manipula, como las palabras-token de un LLM de texto, pero representando fragmentos de sonido. Esos tokens conservan prosodia: el tono, el ritmo, la energía. No solo qué se dijo, sino cómo.
  2. Razona en un espacio compartido. El mismo Transformer que un LLM de texto procesa esos tokens de audio junto con lo que ya sabe. No hay un “traductor a texto” en el medio: el audio entra al mismo mecanismo de atención donde vive el conocimiento del modelo.
  3. Genera tokens de audio de salida. En vez de emitir tokens de texto para que un TTS los lea después, el modelo emite directamente tokens de audio, que el codec reconstruye en onda de sonido. La respuesta ya nace con entonación, porque el modelo decidió la entonación al generar.

De ahí salen las dos ventajas, y las dos son consecuencia directa de no tener pipeline:

  • Expresividad. Como la prosodia entra y sale sin pasar por texto, el modelo puede oír que estás dudando y responder con calma; puede reírse, susurrar, apurarse. La cascada no puede: el texto intermedio ya borró esa dimensión.
  • Latencia. Tres modelos en serie suman tres tiempos de espera; uno solo suma uno. No hay que esperar a que el STT termine la frase completa antes de que el LLM empiece a pensar. El modelo empieza a responder mientras aún te escucha.

Ojo: “sin pasar por texto” es literal y tiene una consecuencia dura. En la cascada, el texto que el LLM produjo existe: lo puedes guardar, revisar, buscar. En audio nativo ese texto nunca se creó. El modelo pasó de audio a audio. Si quieres una transcripción para tus logs, tienes que pedir una transcripción aparte — y esa transcripción es una reconstrucción posterior, no el “pensamiento” real del modelo. Volveremos sobre esto en la tabla de decisión, porque es el costo central.

Las realtime APIs

Montar un modelo de audio nativo con streaming bidireccional, detección de turnos e interrupciones desde cero es un proyecto grande. Las realtime APIs (APIs de tiempo real) empaquetan todo eso: abres una conexión persistente, mandas audio, recibes audio, y el servidor se encarga de la parte difícil. Dos son las referencias hoy.

OpenAI Realtime API

Es la API de audio nativo de OpenAI. A la fecha de esta revisión (septiembre de 2026) su modelo recomendado es gpt-realtime-2.1. Lo que trae resuelto:

  • Transporte flexible. Soporta WebSocket (para servidor a servidor), WebRTC (para navegador/cliente, con menor latencia) y SIP (Session Initiation Protocol, el protocolo estándar de telefonía, para conectar líneas de teléfono directamente). Elegir uno u otro es la decisión de transporte que retomamos abajo.
  • Detección de turnos (VAD) del lado del servidor. El VAD (Voice Activity Detection, detección de actividad de voz: decidir cuándo el usuario empezó y terminó de hablar) corre en el servidor de OpenAI, no en tu código. Ofrece dos modos: server VAD por umbral (reacciona al nivel de audio y a un silencio configurable) y semantic VAD, que intenta inferir si el hablante terminó una idea, no solo si dejó de sonar. El segundo maneja mejor las pausas naturales de alguien que piensa mientras habla.
  • Interrupciones (barge-in). El barge-in (interrumpir: el usuario habla encima del modelo mientras este responde) está resuelto de fábrica: cuando detecta voz del usuario durante la respuesta, corta el audio que estaba emitiendo.
  • Function calling. El modelo puede llamar tools igual que un LLM de texto, en medio de la conversación de voz. Lo veremos en detalle en la sección de function calling.

Lo que tú configuras: la voz, el system prompt (instrucciones), el modo de VAD y sus umbrales, las tools disponibles, y los formatos de audio de entrada/salida. La autenticación desde el navegador usa claves efímeras (ephemeral keys, credenciales de corta vida que pides desde tu servidor para no exponer tu clave real en el cliente).

Ojo: las firmas de esta API se mueven. Con la versión GA (agosto de 2025), por ejemplo, las claves efímeras pasaron a pedirse a /v1/realtime/client_secrets, el intercambio de conexión WebRTC a /v1/realtime/calls, y los eventos se renombraron (response.audio.delta → response.output_audio.delta); tutoriales de la beta que apuntan a /realtime/sessions ya no conectan. Verifica siempre la ruta y los nombres de eventos contra la doc oficial vigente antes de escribir código de producción.

Gemini Live API

Es la API de audio nativo de Google. Usa una conexión WebSocket con estado (stateful: el servidor recuerda el contexto de la sesión mientras la conexión vive) sobre la que streameas audio —y también video— de forma continua. Modelos de audio nativo como gemini-3.8-live (el estable por defecto hoy) y gemini-2.5-flash-native-audio-preview-12-2025 procesan y generan audio directamente. Lo que trae:

  • Barge-in de fábrica. Como el modelo procesa y emite audio nativamente, corta su propia salida en cuanto detecta que el usuario interrumpió y se adapta a la nueva entrada.
  • Tools. Integra function calling y también búsqueda de Google como herramientas dentro de la conversación.
  • Multimodal. Además de voz, acepta stream de video en la misma sesión — un caso que la cascada de voz pura no cubre.

Una diferencia operativa importante: a diferencia de la API normal de generación, la Live API no maneja la respuesta de las tools por ti. Cuando el modelo pide una tool, tu código la ejecuta y le devuelve el resultado manualmente por la misma conexión. Lo verás en el esbozo de código.

Transporte: WebSocket vs WebRTC

Esta es la misma disyuntiva de transporte que planteamos en el módulo 6, aplicada ahora a las realtime APIs. La regla corta:

  • WebSocket cuando la conexión es servidor a servidor. Tu backend habla con la API. Es simple de operar, atraviesa firewalls sin drama y te deja meter tu lógica en el medio. Paga un poco más de latencia porque va sobre TCP (Transmission Control Protocol, que garantiza orden y entrega reordenando y reenviando paquetes — bueno para datos, costoso para audio en vivo).
  • WebRTC cuando el audio sale o entra desde el navegador o la app del cliente. Está diseñado para media en tiempo real: corre sobre UDP (User Datagram Protocol, que prioriza velocidad sobre garantía de entrega — mejor para voz, donde un paquete tarde no sirve), trae cancelación de eco y control de jitter, y da la menor latencia. A cambio, es más complejo de montar (negociación de conexión, servidores intermedios).

En la práctica muchos sistemas combinan: WebRTC entre el usuario y tu servidor para capturar la voz con baja latencia, y WebSocket entre tu servidor y la API. Así tienes el mejor transporte en cada tramo y tu backend queda en el medio como punto de control.

Function calling en tiempo real

Un modelo de audio nativo llama tools con la misma lógica que un LLM de texto: declaras las funciones disponibles, el modelo decide llamar una, tú la ejecutas y le devuelves el resultado. Lo distinto es qué pasa con el audio mientras la tool corre.

Cuando el modelo dispara una tool call, deja de hablar y espera el resultado. Ese hueco es audible: si tu tool tarda 800 ms, el usuario oye 800 ms de silencio y cree que la llamada se cortó. El patrón de producción es cubrir ese hueco: que el modelo diga “déjame revisar eso” antes de que empiece la espera, o que tu código reproduzca un audio de relleno. La conversación no puede quedar muda mientras una función consulta la base de datos.

Cuidado: una tool lenta en un chat de texto es una barra de progreso; en una conversación de voz es un silencio incómodo que el usuario interpreta como fallo. La latencia de tus tools deja de ser un detalle de backend y se vuelve parte de la experiencia conversacional. Presupuéstala como parte del turno.

Esbozo de conexión a una realtime API

El código de abajo es ilustrativo — muestra la forma del flujo de eventos, no una firma exacta lista para copiar. Los nombres de eventos y rutas de estas APIs cambian seguido (ver el aviso de arriba); verifica cada uno contra la doc oficial vigente antes de usarlo. La estructura conceptual, en cambio, es estable: configuras la sesión, mandas audio, recibes deltas de audio y, en el medio, eventos de tool call.

"""Esqueleto ILUSTRATIVO de un cliente de realtime API sobre WebSocket.
Los nombres de eventos siguen el estilo de la OpenAI Realtime API a 2026,
pero DEBEN verificarse contra la doc oficial: se renombran seguido.
El objetivo es mostrar el flujo, no ser copy-paste de produccion.
"""
import asyncio
import base64
import json
import websockets

URL = "wss://api.openai.com/v1/realtime?model=gpt-realtime-2.1"


async def sesion_de_voz(api_key: str, tools: list[dict], correr_tool):
    headers = {"Authorization": f"Bearer {api_key}"}
    async with websockets.connect(URL, additional_headers=headers) as ws:

        # 1. Configurar la sesion: voz, instrucciones, VAD y tools.
        await ws.send(json.dumps({
            "type": "session.update",
            "session": {
                "type": "realtime",
                "output_modalities": ["audio"],
                "instructions": "Eres un asistente de voz breve y cordial.",
                "audio": {
                    # semantic_vad: detecta fin de IDEA, no solo silencio
                    "input": {"turn_detection": {"type": "semantic_vad"}},
                    "output": {"voice": "marin"},
                },
                "tools": tools,
            },
        }))

        # 2. Enviar audio del usuario en chunks (base64 de PCM16).
        async def enviar_audio(chunks):
            for pcm in chunks:
                await ws.send(json.dumps({
                    "type": "input_audio_buffer.append",
                    "audio": base64.b64encode(pcm).decode(),
                }))
            # Con server VAD no hay que "cerrar" el turno a mano;
            # el servidor detecta el silencio y responde solo.

        # 3. Recibir eventos del servidor: audio + tool calls.
        async for msg in ws:
            evento = json.loads(msg)
            tipo = evento["type"]

            if tipo == "response.output_audio.delta":
                # Fragmento de audio de la respuesta (base64). Se decodifica
                # y se reproduce a medida que llega: streaming, no espera.
                reproducir(base64.b64decode(evento["delta"]))

            elif tipo == "response.function_call_arguments.done":
                # El modelo pidio una tool y ya tiene todos los argumentos.
                nombre = evento["name"]
                args = json.loads(evento["arguments"])
                resultado = await correr_tool(nombre, args)  # ej. consultar_pedido
                # Devolver el resultado por la misma conexion y pedir
                # que el modelo continue hablando con esa info.
                await ws.send(json.dumps({
                    "type": "conversation.item.create",
                    "item": {
                        "type": "function_call_output",
                        "call_id": evento["call_id"],
                        "output": json.dumps(resultado),
                    },
                }))
                await ws.send(json.dumps({"type": "response.create"}))

            elif tipo == "error":
                print("error de sesion:", evento)


def reproducir(pcm_bytes: bytes) -> None:
    ...  # empujar los bytes al dispositivo de salida de audio

Fíjate en tres cosas que resumen el módulo. Primero, nunca aparece un texto de respuesta: recibes response.output_audio.delta, no un string. Segundo, el turn-taking lo maneja el servidor vía turn_detection; tú no decides cuándo el usuario terminó. Tercero, la tool call es un evento en medio del stream de audio: llega, la ejecutas, devuelves el resultado, y el modelo retoma la voz — todo sin salir de la misma conexión.

Cascada vs speech-to-speech, lado a lado

Los dos diseños en un diagrama. La diferencia no es de calidad, es de cuántas piezas hay y dónde puedes meter la mano.

   CASCADA (3 modelos)         SPEECH-TO-SPEECH (1 modelo)

   audio del usuario           audio del usuario
        │                            │
        ▼                            │
   ┌─────────┐                       │
   │   STT   │                       │
   └────┬────┘                       │
        │ texto ◄── loggeable        │
        ▼                            ▼
   ┌─────────┐                 ┌───────────┐
   │   LLM   │                 │  audio    │
   └────┬────┘                 │  nativo   │
        │ texto ◄── loggeable  │ (1 modelo)│
        ▼                      └─────┬─────┘
   ┌─────────┐                       │
   │   TTS   │                       │
   └────┬────┘                       │
        ▼                            ▼
   audio de salida             audio de salida

   3 saltos, texto             1 salto, sin texto
   visible en el medio         intermedio visible

El diagrama es toda la lección: la cascada tiene dos flechas etiquetadas “loggeable”; el audio nativo no tiene ninguna. Esa columna de texto que ves en la izquierda es lo que ganas en control y lo que pierdes en la derecha.

La decisión: ¿cuál uso?

Esta es la tabla ampliada de la que dimos en el módulo 8. Ninguna columna gana sola; decides por el peso relativo de las filas en tu caso.

CriterioCascada (STT+LLM+TTS)Speech-to-speech (audio nativo)
LatenciaMayor: tres modelos en serieMenor: un solo salto
ExpresividadPlana: la prosodia se pierde en el textoAlta: tono, emoción, risas, pausas
Control del textoTotal: hay texto en cada etapaNulo: no existe texto intermedio
Observabilidad / logsFácil: loggeas la transcripción realDifícil: solo transcripción reconstruida aparte
EvalsDirectas sobre el texto del LLMComplicadas: hay que evaluar audio
GuardrailsFiltras el texto antes del TTSSin texto que filtrar en el medio
Elegir tu LLMSí: cualquier LLM en la etapa centralNo: usas el LLM que trae la API de voz
CostoTres facturas, más partes móvilesUna factura, menos piezas
MadurezMuy madura, componible, portableMás nueva, atada al proveedor
Cumplimiento / privacidadControlas dónde vive cada etapaAtado a dónde corre la API de voz

Ojo: las dos filas que suelen decidir en producción son “control del texto” y “guardrails”. Perder el texto intermedio complica tus evals —evaluar audio es mucho más caro que evaluar texto, como vimos en el curso de Evals— y complica tus guardrails: no hay un string que un clasificador pueda revisar antes de que la voz salga al usuario. Si tu caso exige loggear cada respuesta, filtrar contenido, o usar tu propio LLM afinado, la cascada sigue ganando pese a su latencia. Si lo que vende tu producto es que suene humano —un compañero de conversación, un tutor de idiomas, un asistente que ríe— el audio nativo es la respuesta.

La regla práctica: elige audio nativo cuando la experiencia conversacional es el producto; elige cascada cuando el control, el registro y la portabilidad son el producto. Y no es blanco o negro: hay sistemas híbridos que usan audio nativo para la conversación fluida y una transcripción paralela para logs y guardrails, pagando algo de las dos cuentas.

Errores comunes

  • Elegir speech-to-speech por moda y descubrir tarde que no puedes loggear. El equipo de cumplimiento pide “los transcritos de todas las llamadas” y resulta que el texto real nunca existió. Decide la observabilidad antes de elegir la arquitectura, no después.
  • Creer que audio nativo elimina los guardrails. No los elimina: los complica. Sin texto intermedio, el filtro de contenido tiene que operar sobre audio o sobre una transcripción posterior, ambos más caros y más lentos que revisar un string.
  • Dejar el hueco de la tool call en silencio. El modelo llama una función, se calla 700 ms y el usuario cree que se cortó. Cubre siempre la espera con una frase de relleno o audio de espera.
  • Usar WebSocket desde el navegador para el audio del usuario. Funciona, pero pagas latencia y te pierdes cancelación de eco y control de jitter. Para captura desde el cliente, WebRTC es el transporte correcto.
  • Copiar firmas de eventos de un tutorial viejo. Estas APIs renombran eventos y rutas seguido. Un ejemplo de hace seis meses puede apuntar a endpoints que ya no existen. Verifica contra la doc oficial vigente.
  • Asumir que el turn-taking es tu problema. En la cascada tenías que detectar el fin de turno tú; las realtime APIs traen VAD del lado del servidor. Configúralo (umbral vs semántico), no lo reimplementes.

La frase para llevarse

“La cascada te da un texto que puedes leer; el audio nativo te da una voz que suena viva. No preguntes cuál es mejor: pregunta si tu producto necesita el texto o la voz — y si necesita los dos, prepárate a pagar las dos cuentas.”

Qué viene

Módulo 10: la telefonía y el mundo real. Todo lo que armamos hasta ahora asumió audio limpio entrando por un micrófono decente. La red telefónica no es eso: es audio comprimido a 8 kHz, con latencia de red, cortes, DTMF (los tonos del teclado), y el protocolo SIP que las realtime APIs ya empiezan a soportar de fábrica. Vamos a bajar el agente de voz al teléfono de verdad — cómo suena distinto, qué se rompe, y qué hay que ajustar para que una conversación sobreviva a una línea celular mala.

Material relacionado

VozSpeech-to-speechRealtime APIMultimodalTiempo real

Preguntas sobre este curso

Regístrate gratis para hacer preguntas y guardar tu progreso.

✉ Entérate de lo nuevo

Recibe un aviso cuando publique nuevos cursos, workshops o investigaciones. Sin spam.