Blog
AI/Voice
10 de diciembre de 20258 min

Pipeline de voz en tiempo real: del audio bruto a la IA

Puntos clave: Construir un pipeline de voz en tiempo real significa encadenar Deepgram STT (streaming con VAD), un LLM a través de OpenRouter (function calling), y OpenAI TTS — todo conectado por un WebSocket autenticado con JWT. Este artículo detalla cada paso, las latencias, los fallbacks y los errores que me costaron noches en vela.

El corazón de TAMSIV es la voz. No es un gadget, no es un botón de micrófono escondido en una esquina. La voz ES la interfaz principal. Tú presionas, tú hablas, la IA entiende y ejecuta. Pero construir un pipeline de voz en tiempo real en solitario es entrar en un mundo donde cada milisegundo cuenta y donde todo puede fallar en cualquier momento.

Después de tres semanas de desarrollo intensivo y una cantidad irrazonable de café, tengo un pipeline que responde en 1.5 a 3 segundos. Así es como funciona, chunk por chunk.

Ondas sonoras propagándose en un ambiente oscuro con partículas de luz azul y cian
De la onda sonora a la respuesta estructurada: cada milisegundo cuenta.

¿Cómo funciona la arquitectura del pipeline de voz?

El pipeline completo en una línea:

Audio PCM 16kHz mono → WebSocket (JWT) → Deepgram Live STT (VAD) → OpenRouter LLM → Function calling → OpenAI TTS → Respuesta vocal

Seis etapas, seis puntos de fallo potenciales. Cada una tiene sus limitaciones, sus latencias y sus trampas. Todo debe encadenarse en menos de 3 segundos para que la experiencia sea fluida. Más allá de eso, el usuario piensa que la aplicación se ha bloqueado.

Descompongamos cada etapa.

¿Por qué el WebSocket es indispensable para el audio en tiempo real?

El HTTP clásico no funciona para el streaming de audio. Necesitarías enviar toda la grabación de una vez, esperar el procesamiento y luego recibir la respuesta. La latencia sería inaceptable.

El WebSocket permite un flujo bidireccional continuo: el teléfono envía chunks de audio mientras el usuario habla, y el backend puede comenzar a procesar incluso antes de que el usuario haya terminado.

La autenticación JWT

Cada conexión WebSocket se autentica a través de un token JWT de Supabase:

ws://backend:3001?token=eyJhbGciOiJIUzI1NiIs...

El token es válido en la conexión. Si el token expira en medio de una conversación (los tokens de Supabase expiran después de 1 hora), el cliente detecta la desconexión y se reconecta automáticamente con un token fresco. Tuve que manejar este caso explícitamente — al principio, las conversaciones largas se bloqueaban misteriosamente.

La seguridad del WebSocket se detalla en el artículo sobre la auditoría de seguridad y el rate limiting.

¿Cómo gestiona Deepgram el Speech-to-Text en streaming?

El audio debe estar en PCM de 16 bits, 16 kHz, mono. El teléfono captura el audio en este formato y envía chunks binarios brutos a través del WebSocket. Sin compresión, sin codificación — el PCM bruto es el formato más rápido de procesar.

Deepgram recibe estos chunks y transcribe en streaming. Pero la verdadera magia es el VAD (Voice Activity Detection).

¿Por qué el VAD lo cambia todo?

Sin VAD, hay que implementar un timeout de silencio en el lado del cliente: si el usuario no habla durante X segundos, se considera que ha terminado. El problema:

  • Demasiado corto (1s): cortas al usuario que reflexiona entre dos frases.
  • Demasiado largo (3s): la aplicación se ralentiza, el usuario espera.
  • Variable según el usuario: algunos hablan rápido, otros se toman su tiempo.

El VAD de Deepgram detecta cuándo el usuario ha terminado de hablar con una precisión notable. Analiza la señal de audio en tiempo real y envía un evento speech_final cuando está seguro de que el usuario ha terminado. Esto tarda unos 200ms después de que termina el habla.

Los resultados intermedios vs finales

Deepgram envía dos tipos de resultados:

  • is_final: false — Resultados intermedios, inestables. La palabra detectada puede cambiar a medida que el contexto se enriquece.
  • is_final: true — Resultados confirmados. El texto ya no cambiará.

La trampa: acumular los resultados intermedios para mostrar una vista previa en tiempo real (bueno para la UX) mientras se transmiten solo los resultados finales al LLM (bueno para la calidad). Yo muestro los resultados intermedios en gris y los finales en blanco — el usuario ve su voz transformarse en texto en vivo.

Para la comparación entre STT nativo (gratuito, en el dispositivo) y Deepgram cloud (más preciso), lee mi artículo detallado STT nativo vs Deepgram.

Micrófono de estudio profesional con indicador LED en un ambiente oscuro, iluminación azul y violeta
La captura de audio es la primera etapa crítica del pipeline.

¿Cómo orquesta el LLM las acciones a través de function calling?

La transcripción completa se envía a OpenRouter con function calling. OpenRouter es un enrutador que da acceso a más de 400 modelos LLM con fallback automático — si el modelo principal está caído, un fallback toma el relevo en segundos.

El LLM recibe la transcripción y debe comprender la intención del usuario. Dispone de 7 funciones:

  • create_task — Crear una tarea
  • update_task — Modificar una tarea existente
  • create_memo — Crear una nota
  • update_memo — Modificar una nota existente
  • create_calendar_event — Crear un evento de calendario
  • ask_clarification — Pedir una aclaración al usuario
  • end_conversation — Terminar la conversación

El LLM analiza la frase ("recuérdame comprar pan mañana a las 10h"), identifica la acción (create_task), extrae los parámetros (título, fecha, hora) y devuelve una llamada de función estructurada. El backend ejecuta la acción y devuelve el resultado al frontend.

El patrón PendingCreation es crucial aquí: el backend crea una vista previa del elemento, y el usuario puede validar, editar o cancelar antes de la guarda definitiva en la base de datos. Cero sorpresas desagradables.

La latencia del LLM

El LLM representa la mayor parte de la latencia: entre 800ms y 2 segundos según el modelo y la complejidad de la solicitud. Aquí es donde la elección del modelo a través de OpenRouter marca la diferencia — un modelo rápido pero menos preciso vs un modelo lento pero más fiable. TAMSIV utiliza un modelo configurable con fallback automático si el modelo principal es demasiado lento o no está disponible.

¿Cómo genera OpenAI TTS la respuesta de voz?

Una vez ejecutada la acción, el backend genera una respuesta textual ("¡Anotado! He creado la tarea 'Comprar pan' para mañana a las 10h"). Este texto se envía a OpenAI TTS con la voz nova.

El audio se transmite de vuelta a través del mismo WebSocket. El frontend comienza a reproducir los primeros chunks de audio, sin esperar la respuesta completa. Esto reduce la latencia percibida en unos 500ms — el usuario escucha el inicio de la respuesta mientras el final aún se está generando.

Para la personalización vocal y la elección de la voz TTS, hablo de ello en el artículo sobre la personalización vocal.

¿Cuáles son los tres modos WebSocket de TAMSIV?

A lo largo del desarrollo, surgieron tres modos WebSocket:

  1. LiveWebSocketServer (predeterminado) — STT nativo del dispositivo + Deepgram fallback, orquestación LLM, OpenAI TTS. Este es el modo estándar, el más económico.
  2. RealtimeWebSocketServer — API OpenAI Realtime, bidireccional, baja latencia. Más costoso pero más fluido para conversaciones largas.
  3. WebSocketServer — Batch STT/TTS, modo heredado. Utilizado para casos en los que el streaming no es necesario.

El modo es seleccionable en el lado del administrador a través de la tabla app_config en Supabase. Esto permite cambiar entre modos sin implementar una nueva versión de la aplicación.

Cables de fibra óptica luminosos azules y verdes en un centro de datos, visualización de flujos de datos
Los datos atraviesan el pipeline a la velocidad de la luz — en teoría.

¿Cómo gestionar los errores en un pipeline en tiempo real?

La regla de oro: todo puede fallar en cualquier momento. Deepgram puede estar caído. OpenRouter puede agotar el tiempo de espera. OpenAI TTS puede devolver un error 429. La conexión WebSocket puede cortarse en medio de una conversación.

Aquí están los mecanismos de resiliencia:

  • Reintentos inteligentes: Cada etapa tiene un número de reintentos configurado con retroceso exponencial. No hay reintentos en bucle infinito.
  • Circuit breakers: Si un servicio falla con demasiada frecuencia, dejamos de llamarlo durante X segundos para evitar sobrecargar un servicio que ya está en dificultades.
  • Fallbacks en cada etapa: STT nativo si Deepgram está caído, modelo LLM alternativo a través de OpenRouter, respuesta de texto si TTS falla.
  • AlertService: Cada fallback activa una alerta por correo electrónico (a través de Resend) + registro de Supabase. Sé en tiempo real cuándo algo se degrada.

Cada línea de gestión de errores representa un error experimentado en producción. El pipeline es robusto hoy, pero se necesitaron docenas de sesiones de depuración para llegar a este punto.

¿Cuál es el presupuesto de latencia de cada etapa?

Descomposición de una interacción vocal completa:

  • Captura de audio + envío WebSocket: ~50ms (despreciable)
  • Deepgram STT + VAD: ~200-400ms después de finalizar el habla
  • OpenRouter LLM (function calling): ~800ms-2000ms (variable)
  • OpenAI TTS (primer chunk): ~300-500ms

Total: 1.3 a 3 segundos. El objetivo es mantenerse por debajo de 2 segundos para el 90% de las interacciones. Más allá de eso, la experiencia se vuelve frustrante.

El streaming TTS es la mejor palanca: el usuario escucha el inicio de la respuesta después de ~1.5s en promedio, incluso si la generación completa tarda 3 segundos. La latencia percibida es mucho menor que la latencia real.

¿Qué lecciones puedes sacar para construir tu propio pipeline de voz?

Después de tres semanas de desarrollo intensivo, esto es lo que te aconsejaría:

  1. Empieza por el WebSocket: Es la columna vertebral. Si el WebSocket es sólido, el resto encaja.
  2. Utiliza el VAD del proveedor de STT: No implementes tu propia detección de silencio — es un abismo de complejidad para un resultado inferior.
  3. Transmite todo: STT en streaming, TTS en streaming. Cada milisegundo ahorrado mejora la UX.
  4. Prevé los fallbacks desde el día 1: No en modo "ya veré más tarde". Cada etapa debe tener un plan B.
  5. Mide la latencia en producción: Los benchmarks locales son engañosos. La latencia real depende de la red, la carga del servidor y la ubicación geográfica. El panel de administración me fue indispensable para esto.

Preguntas Frecuentes

¿Por qué Deepgram en lugar de Google Speech-to-Text o AWS Transcribe?

Deepgram ofrece la mejor relación calidad/latencia para el streaming. Google STT es excelente pero más caro y más lento en modo streaming. AWS Transcribe es robusto pero la integración de WebSocket es más compleja. Deepgram también tiene un VAD integrado, lo que simplifica enormemente el código.

¿El pipeline funciona en modo sin conexión?

El STT nativo del dispositivo funciona sin conexión. Pero el LLM y el TTS requieren una conexión a internet. En modo sin conexión, TAMSIV permite la entrada de texto clásica y pone en cola las solicitudes de voz para cuando la conexión se restablezca.

¿Cuánto cuesta una interacción vocal completa?

Con el STT nativo (gratuito), un LLM económico a través de OpenRouter (~$0.001-0.01), y OpenAI TTS (~$0.015/1000 caracteres), una interacción cuesta entre $0.01 y $0.03. El detalle de los costos está en el artículo retrospectivo sobre los 650 commits.

¿Se puede reemplazar OpenAI TTS por una solución de código abierto?

Técnicamente sí. Proyectos como Coqui TTS o Bark producen resultados correctos. Pero la calidad de la voz "nova" de OpenAI sigue siendo superior, y el streaming está mejor soportado. Para un proyecto en producción, el costo adicional de OpenAI TTS ($15/millón de caracteres) se justifica por la calidad.

¿Cómo gestionar múltiples idiomas en el pipeline de voz?

Deepgram soporta la detección automática de idioma. El LLM a través de OpenRouter es naturalmente multilingüe. El TTS de OpenAI genera audio en el idioma del texto proporcionado. TAMSIV soporta 6 idiomas (FR, EN, DE, ES, IT, PT) sin configuración específica por idioma en el pipeline. El detalle de la internacionalización está en el artículo sobre la i18n en 6 idiomas.