AI voice agents en tu centralita: pipeline streaming bajo 600ms
Cómo construimos el pipeline STT → LLM → TTS de SIVO con barge-in real, JSON output y latencia end-to-end ~600ms. Decisiones técnicas y trampas evitadas.
Un agente de voz IA conversacional utilizable tiene que cumplir tres cosas a la vez:
- Latencia humana (≤700ms desde fin-de-frase hasta primer audio del bot).
- Barge-in real (el caller corta al bot y este aborta inmediatamente).
- Output estructurado para integrar con IVR, webhooks y CRMs.
Esta es la receta con la que llegamos a ~600ms end-to-end en producción.
La arquitectura general
Caller audio
↓ mod_audio_fork (UDP → WebSocket bidireccional)
ai-voice service (Node.js + drachtio)
├→ VAD (cliente) → speech_start / speech_end
├→ STT streaming (Deepgram / ElevenLabs Scribe / Whisper)
├→ LLM streaming (OpenAI compatible: Groq, OpenAI, Cerebras...)
└→ TTS streaming (ElevenLabs v2/v3 WS, OpenAI TTS)
↓ PCM base64 chunks
HTTP POST → app backend
↓ escribe .r8 al volumen compartido
ESL → uuid_execute playback al canal
mod_audio_fork es unidireccional (FS lee audio del canal). El TTS playback va por una ruta alternativa: ai-voice hace POST HTTP al backend con el PCM, el backend lo escribe como .r8 y dispara playback via ESL. Más rodeo, pero rompe la dependencia de un módulo bidireccional menos estable.
STT: por qué streaming y no batch
Whisper batch da mejor calidad pero suma 1.5-2s de latencia. Para agentes en directo se descarta. Las opciones streaming serias:
- Deepgram Nova-2/3: WebSocket auth via header, primer parcial en ~250ms. Mejor relación coste/calidad para producción.
- ElevenLabs Scribe v2 Realtime: WebSocket auth via
xi-api-keyheader (no query params). Brilla en entornos ruidosos y acentos no nativos.
Trampa que nos costó dos días: ElevenLabs cambió la autenticación de ?api_key= a header en algún punto. Si todavía pasas query string, da auth_error sin pista clara.
LLM: la decisión clave es TTFT
Time-To-First-Token es la métrica que decide la sensación de latencia, no el throughput total. Medidas reales en producción:
| Proveedor | TTFT | Tokens/s |
|---|---|---|
| OpenAI GPT-4o | 667-2400ms | 80 |
| OpenAI GPT-4o-mini | 350-800ms | 120 |
| Groq Llama 3.1 70B | ~120ms | 320 |
| Cerebras Llama 3.1 70B | ~150ms | 450 |
Groq es la opción default para producción de voz. Cerebras la alternativa si necesitas más throughput sostenido.
TTS: la elección depende de si quieres audio tags
Hay dos modelos serios en ElevenLabs:
eleven_multilingual_v2: WebSocket streaming (stream-inputendpoint). 30+ idiomas. NO soporta audio tags.eleven_v3: solo HTTP streaming (WS devuelve 403). SÍ soporta[laughs],[sighs],[whispers]. Calidad superior.
El system prompt es condicional al modelo TTS:
si modelo TTS contiene "v3":
"Puedes usar [laughs], [sighs], [whispers] para inflexión natural."
si no:
"No uses corchetes. Expresa emociones con puntuación: '...!?'"
Sin esto, v2 acaba leyendo “abre corchete laughs cierra corchete” en voz alta. Vergonzoso.
Pipelining sentence-level
El truco que rebaja la latencia ~40%: no esperar la respuesta completa del LLM antes de empezar TTS. SIVO usa un JsonResponseExtractor que parsea el JSON incrementalmente conforme llega del LLM y dispara TTS al detectar .!?\n dentro del campo response:
{
"response": "Perfecto, ¿me confirmas tu DNI?| ← sentence boundary
"action": "continue",
"variables": { ... }
}
Mientras la frase 1 se reproduce, la frase 2 ya se sintetiza. Mientras la frase 2 se reproduce, la 3 ya está siendo generada por el LLM. Solapamos las tres etapas.
Barge-in: el detalle del cooldown
Implementación naïve del barge-in:
VAD detecta speech_start → abortar TTS + LLM en curso
Problema: el propio audio del bot reproducido vuelve por el micro del caller (cuando llama desde móvil con altavoz, especialmente). VAD detecta speech_start, aborta el bot, el bot empieza a procesar “su propio eco” como utterance, responde “no he entendido”… loop infinito.
Mitigación con tres capas:
greetingPlayingflag durante los primeros 150ms trasuuid_audio_fork start(settling time).bargeInCooldownUntil: tras cada playback, ignorar speech_start duranteplayMs + 300ms.speechMinMs: 400: ignorar utterances de<400ms(golpes, suspiros, ruido ambiente).
Después de estas tres, los falsos positivos bajan de ~30% a menos del 2%.
Manejo del abort del LLM por barge-in
Cuando aborta el TTS, también hay que abortar el LLM (cancelar el fetch con AbortController). Importante: no tratar como error fatal. El catch debe verificar state.ttsAborted:
try {
await streamLLM(...);
} catch (err) {
if (state.ttsAborted) {
logger.debug('LLM abortado por barge-in (esperado)');
return;
}
logger.error('LLM real failure', err);
}
Sin esto, el log se llena de “errores” que son barge-ins normales.
La regla dura: dónde NO arrancar transcripción
Esta merece subrayado triple. La captura de audio (uuid_audio_fork) solo se inicia en:
bridge-agent-start(cuando la llamada llega al agente humano).- Al entrar en un nodo
ai_agentdel IVR.
Nunca durante IVR / menú DTMF / cola / música de espera. ¿Por qué?
- Coste: si transcribes el menú “Pulse 1 para X, 2 para Y” en cada llamada, multiplicas el gasto STT por N.
- Privacidad: el caller no ha consentido todavía (consentimiento se da al ser atendido o entrar en bot).
- Compliance: GDPR Art. 6 — sin base legal clara para transcribir tonos de menú.
Es una regla dura, no una optimización. SIVO falla un test de CI si el codepath de IVR puede arrancar transcripción.
Resultado en producción
Con Groq Llama 3.1 70B + Deepgram Nova-2 + ElevenLabs v2:
- TTFT del LLM: ~120ms
- Primer audio TTS desde llegada del último token: ~80ms
- VAD detect → primer audio bot: ~600ms (P50), 850ms (P95)
- Falsos positivos de barge-in: menos del 2%
- Coste por minuto conversado:
$0.012 (Groq) + $0.013 (Deepgram) + $0.18 (ElevenLabs v2) = **$0.20/min**
Para un caso de uso “FAQ + cualificación de lead + transferencia a humano si hay duda” es operativamente sólido.
Lo que queda por hacer
- Speaker separation en STT (ya en uso para transcripción de agente humano, pendiente en AI agents para escenarios warm-transfer).
- Local Whisper en GPU EU para clientes con compliance estricto que no pueden enviar audio a USA.
- Métricas de tokens/coste en Grafana desglosadas por agente IA (las tenemos por llamada, faltan dashboards de coste agregado).
→ Si tu equipo está montando un agente de voz, hablamos — compartimos los benchmarks completos.