Phase 06 - Lesson 14
Detección de Actividad de Voz y Alternancia de Turnos — Silero, Cobra y el Truco del Flush
This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.
Todo agente de voz vive o muere por dos decisiones: ¿el usuario está hablando ahora y ya terminó? La VAD responde a la primera. La detección de turno (VAD + margen de silencio + modelo de endpoint semántico) responde a la segunda. Equivócate en cualquiera de las dos y tu asistente o corta al usuario a mitad de frase o nunca se calla.
Tipo: Build Lenguajes: Python Prerrequisitos: Fase 6 · 11 (Audio en Tiempo Real), Fase 6 · 12 (Asistente de Voz) Tiempo: ~45 minutos
El Problema
Tres decisiones distintas que un agente de voz toma en cada bloque de 20 ms:
- ¿Este cuadro es habla? — VAD. Binaria, por cuadro.
- ¿El usuario empezó un nuevo enunciado? — detección de inicio.
- ¿El usuario terminó? — end-pointing (fin de turno).
La respuesta ingenua (umbral de energía) falla ante cualquier ruido — tráfico, teclados, bullicio de multitud. La respuesta de 2026: Silero VAD (abierta, basada en deep learning) + un modelo de detección de turno (endpointing semántico) + un margen de silencio calibrado por la VAD.
El Concepto
La cascada de VAD en tres niveles
Nivel 1: compuerta de energía. La más barata. Aplica umbral de RMS en -40 dBFS. Filtra el silencio obvio pero se dispara con cualquier ruido por encima del umbral.
Nivel 2: Silero VAD (2020-2026, MIT). 1 millón de parámetros. Entrenada en más de 6000 idiomas. Corre en ~1 ms por bloque de 30 ms en un solo hilo de CPU. 87,7% de TPR al 5% de FPR. El estándar de código abierto.
Nivel 3: detector semántico de turno. El modelo de detección de turno de LiveKit (2024-2026) o tu propio clasificador pequeño. Distingue "pausa a mitad de frase" de "terminó de hablar". Usa contexto lingüístico (entonación + palabras recientes), no solo silencio.
Parámetros clave y sus valores por defecto
- Umbral. Silero produce una probabilidad; clasifica como habla en > 0.5 (por defecto) o > 0.3 (sensible). Umbral más bajo = menos cortes de la primera palabra, más falsos positivos.
- Duración mínima de habla. Rechaza habla más corta que 250 ms — usualmente toses o ruido de silla.
- Margen de silencio (end-pointing). Después de que la VAD regrese a 0, espera 500-800 ms antes de declarar el fin del turno. Demasiado corto → interrumpe al usuario. Demasiado largo → se siente lento.
- Buffer de pre-roll. Conserva 300-500 ms de audio antes de que la VAD se dispare. Evita que el "hola" se corte.
El truco del flush (Kyutai 2025)
Los modelos de STT en streaming tienen un retardo de look-ahead (500 ms para Kyutai STT-1B, 2,5 s para STT-2.6B). Normalmente esperarías todo ese tiempo tras el fin del habla para obtener la transcripción. Truco del flush: cuando la VAD dispara el fin del habla, envía una señal de flush al STT que fuerza la salida inmediata. El STT procesa a ~4× tiempo real, así que el buffer de 500 ms termina en ~125 ms.
De extremo a extremo: 125 ms de VAD + flush del STT = latencia conversacional.
Comparación de VAD en 2026
| VAD | TPR @ 5% FPR | Latencia | Términos |
|---|---|---|---|
| WebRTC VAD (Google, 2013) | 50,0% | 30 ms | BSD |
| Silero VAD (2020-2026) | 87,7% | ~1 ms | MIT |
| Cobra VAD (Picovoice) | 98,9% | ~1 ms | comercial |
| pyannote segmentation | 95% | ~10 ms | tipo MIT |
Silero es el estándar correcto. Cobra es la mejora de cumplimiento / precisión. La VAD basada solo en energía no tiene lugar en la producción de 2026.
Constrúyelo
Paso 1: la compuerta de energía
def energy_vad(chunk, threshold_dbfs=-40.0):
rms = (sum(x * x for x in chunk) / len(chunk)) ** 0.5
dbfs = 20.0 * math.log10(max(rms, 1e-10))
return dbfs > threshold_dbfs
Paso 2: Silero VAD en Python
from silero_vad import load_silero_vad, get_speech_timestamps
vad = load_silero_vad()
audio = torch.tensor(waveform_16k, dtype=torch.float32)
segments = get_speech_timestamps(
audio, vad, sampling_rate=16000,
threshold=0.5,
min_speech_duration_ms=250,
min_silence_duration_ms=500,
speech_pad_ms=300,
)
for s in segments:
print(f"{s['start']/16000:.2f}s - {s['end']/16000:.2f}s")
Paso 3: máquina de estados de fin de turno
class TurnDetector:
def __init__(self, silence_hangover_ms=500, min_speech_ms=250):
self.state = "idle"
self.speech_ms = 0
self.silence_ms = 0
self.silence_hangover_ms = silence_hangover_ms
self.min_speech_ms = min_speech_ms
def update(self, is_speech, chunk_ms=20):
if is_speech:
self.speech_ms += chunk_ms
self.silence_ms = 0
if self.state == "idle" and self.speech_ms >= self.min_speech_ms:
self.state = "speaking"
return "START"
else:
self.silence_ms += chunk_ms
if self.state == "speaking" and self.silence_ms >= self.silence_hangover_ms:
self.state = "idle"
self.speech_ms = 0
return "END"
return None
Paso 4: el esqueleto del truco del flush
def flush_on_end(stt_client, audio_buffer):
stt_client.send_audio(audio_buffer)
stt_client.send_flush()
return stt_client.recv_transcript(timeout_ms=150)
El STT (Kyutai, Deepgram, AssemblyAI) debe soportar flush para que esto funcione. El Whisper en streaming no lo soporta — funciona por bloques y siempre espera los bloques.
Úsalo
| Situación | Elección de VAD |
|---|---|
| Abierta, rápida, general | Silero VAD |
| Call center comercial | Cobra VAD |
| En el dispositivo (teléfono) | Silero VAD ONNX |
| Investigación / diarización | pyannote segmentation |
| Fallback sin dependencias | WebRTC VAD (heredado) |
| Necesitas calidad en el fin de turno | Silero + detector de turno de LiveKit en capas |
Regla general: nunca despliegues VAD basada solo en energía a menos que realmente no tengas otra opción.
Errores Comunes
- Umbral fijo. Funciona en silencio, falla con ruido. Calíbralo en el dispositivo o cámbialo por Silero.
- Margen de silencio demasiado corto. El agente interrumpe a mitad de frase. 500-800 ms es el punto ideal para el habla conversacional.
- Margen demasiado largo. Se siente lento. Haz pruebas A/B con usuarios objetivo.
- Sin buffer de pre-roll. Se pierden los primeros 200-300 ms del audio del usuario. Conserva siempre un pre-roll rodante.
- Ignorar el endpointing semántico. "Mmm, déjame pensar..." contiene pausas largas. Los usuarios odian que los corten a mitad del pensamiento. Usa el detector de turno de LiveKit o similar.
Entrégalo
Guárdalo como outputs/skill-vad-tuner.md. Elige el modelo de VAD, el umbral, el margen, el pre-roll y la estrategia de detección de turno para una carga de trabajo.
Ejercicios
- Fácil. Ejecuta
code/main.py. Simula una secuencia de habla + silencio + habla + toses y prueba tres niveles de VAD. - Medio. Instala
silero-vad, procesa una grabación de 5 min, ajusta el umbral para minimizar tanto los cortes de la primera palabra como los disparos falsos. Reporta precisión/recall. - Difícil. Construye un mini detector de turno: Silero VAD + un MLP de 3 capas sobre los embeddings de las últimas 10 palabras (usa sentence-transformers). Entrénalo en un dataset de fin de turno etiquetado a mano. Supera a Silero por sí sola en un 10% de F1.
Términos Clave
| Término | Lo que la gente dice | Lo que realmente significa |
|---|---|---|
| VAD | Detector de voz | Binario por cuadro: ¿esto es habla? |
| Detección de turno | End-pointing | VAD + margen de silencio + endpoint semántico. |
| Margen de silencio | Espera tras el habla | Tiempo a esperar antes de declarar el fin del turno; 500-800 ms. |
| Pre-roll | Buffer pre-habla | Conserva 300-500 ms de audio antes de que la VAD se dispare. |
| Truco del flush | Hack de Kyutai | VAD → flush del STT → 125 ms en lugar de 500 ms de retardo. |
| Endpoint semántico | "¿Querían detenerse?" | Clasificador de ML que mira las palabras, no solo el silencio. |
| TPR @ FPR 5% | Punto en la ROC | Benchmark estándar de VAD; 87,7% para Silero, 50% para WebRTC. |
Lectura Adicional
- Silero VAD — la VAD abierta de referencia.
- Picovoice Cobra VAD — líder comercial en precisión.
- Kyutai — Unmute + truco del flush — el truco de ingeniería sub-200 ms.
- LiveKit — detección de turno — endpointing semántico en producción.
- WebRTC VAD — la línea base heredada.
- pyannote segmentation — segmentación a nivel de diarización.