VAD Lab v2

Os parâmetros do silero.VAD e do LiveKit — o que cada um faz de verdade

O que cada parâmetro muda no barge-in, no áudio que chega ao STT e no atraso percebido da conversa — e como eles interagem. Semântica verificada no código instalado (livekit-agents 1.3.10, plugin silero).

O modelo mental · As duas cadeias de latência · Parâmetros do silero.VAD · Parâmetros do AgentSession · Como interagem · Perguntas diretas · Valores para o nosso tráfego

1. O modelo mental: duas camadas, duas perguntas

Há dois conjuntos de parâmetros que costumam ser confundidos:

silero.VAD (detector)AgentSession (turn-taking)
Pergunta que responde "há fala neste áudio, de onde até onde?" "o usuário terminou o turno? devo interromper o agente?"
Parâmetros activation/deactivation_threshold, min_speech_duration, min_silence_duration, prefix_padding_duration, max_buffered_speech turn_detection, min/max_endpointing_delay, allow_interruptions, min_interruption_duration/words, false_interruption_timeout
Consomeframes de áudio (janelas de 32 ms)eventos do VAD + transcrições do STT

O VAD emite três eventos que alimentam tudo: START_OF_SPEECH (começou a falar), INFERENCE_DONE (a cada 32 ms, com probabilidade e contadores) e END_OF_SPEECH (parou de falar, carrega o chunk de áudio para o STT). Toda decisão é quantizada em janelas de 32 ms — nenhum parâmetro tem efeito mais fino que isso.

2. As duas cadeias de latência (o que o cliente sente)

CADEIA DE RESPOSTA — "o robô demora pra responder" cliente PARA de falar │ min_silence_duration ······· o VAD só fecha o segmento após esse silêncio (0.5–0.8 s) ▼ END_OF_SPEECH (chunk de áudio pronto) │ endpointing ················ modo VAD: max(silêncio já contado, min_endpointing_delay) │ → se min_endpointing ≤ min_silence, custo extra ZERO ▼ turno encerrado │ whisper-1 (batch!) ········· só começa a transcrever AGORA (o chunk nasce no END) │ LLM (primeiro token) + TTS (primeiro áudio) ▼ robô responde SILÊNCIO PERCEBIDO ≈ min_silence + STT + LLM + TTS CADEIA DE BARGE-IN — "falei por cima e o robô não calou" cliente COMEÇA a falar por cima │ rampa da probabilidade ····· 2–4 janelas (64–128 ms) até cruzar activation_threshold │ min_speech_duration ········ piso para confirmar fala (0.05 s ≈ 2 janelas) ▼ START_OF_SPEECH (pub_speech_duration começa a contar) │ min_interruption_duration ·· checado a CADA janela: speech_duration ≥ 0.5 s? ▼ agente é interrompido ATÉ CALAR ≈ rampa + max(min_speech, min_interruption_duration)
A assimetria importante
O min_silence_duration é um custo inevitável e integral na cadeia de resposta — não existe como saber que o cliente parou sem esperar o silêncio. Já o barge-in NÃO depende do min_silence: depende da rampa de detecção + min_interruption_duration. Por isso mexer no min_silence muda a "lentidão" do robô, e mexer no min_interruption muda a "teimosia" dele ao ser interrompido — são botões independentes.

3. Parâmetros do silero.VAD, um a um

activation_threshold

default livekit: 0.5 · nosso valor: 0.16 (banda-estreita G729→G711 deprime as probabilidades do Silero)
O que é
Probabilidade mínima (suavizada, EMA α=0.35) para uma janela contar como fala e abrir um segmento.
Barge-in
Mais baixo = rampa cruza o limiar mais cedo → disparo mais rápido (medimos p90 de onset caindo de 304 → 176 ms ao ir de 0.34 → 0.16). É o parâmetro que mais afeta a cauda da latência de onset.
STT
Mais baixo = captura fala baixa/fraca (recall↑), mas ativa mais em fundo/ruído (falsos positivos → chunks-lixo pro whisper, que alucina).
Atraso percebido
Não afeta a cadeia de resposta; afeta só o onset.

deactivation_threshold

default livekit: activation − 0.15 · nosso valor: 0.06
O que é
Histerese de saída: com o segmento aberto, a janela continua contando como fala enquanto prob > este valor. O silêncio só começa a acumular abaixo dele.
STT
Mais baixo = o segmento "segura" caudas fracas de fala (finais de palavra não são cortados), mas também segura fala de fundo — no nosso dataset a intrusão de fundo vem majoritariamente da continuação (deact baixa), não do onset.
Barge-in
Indireto: deact mais baixa atrasa o fechamento → atrasa a cadeia de resposta quando há ruído residual.
Interação
Deve ficar bem abaixo da activation (histerese); no grid, o eixo deact 0.05–0.10 é quase plano no score — escolha pelo comportamento de cauda.

min_speech_duration

default livekit: 0.05 s · nosso valor: 0.05 s (eixo de grid interessante: 0.10–0.15)
O que é
Tempo acumulado de fala (janelas acima do limiar) antes de emitir START_OF_SPEECH. 0.05 s ≈ 2 janelas.
Barge-in
Soma diretamente à latência de onset (é um piso). Subir para 0.15 adiciona ~100 ms ao disparo.
STT
Filtra "blips": estalos, cliques e os falsos positivos de 0.1–0.3 s que medimos (inclusive os causados pelo dither do pipeline). Subir este valor é a forma mais barata de matar órfãos.
Trade-off central
onset p90 (gate ≤ 0.25 s) vs taxa de órfãos — os dois lados estão nos gates/vetor do avaliador; o grid arbitra.

min_silence_duration

default livekit: 0.55 s · nosso valor: 0.5 s (campeão antigo usava 0.8)
O que é
Silêncio acumulado (prob < deactivation) antes de emitir END_OF_SPEECH e fechar o chunk.
Atraso percebido
Sim, é o piso do atraso de resposta — o robô nunca responde antes de min_silence após o cliente calar, por construção. 0.5 s é perceptível mas natural; 0.8 s já soa "lento" somado a STT+LLM+TTS.
STT / picote
Controla o picote: pausas do cliente maiores que este valor viram cortes (2 chunks); menores ficam dentro do mesmo chunk. Menor valor = mais picote (mais chunks curtos → mais chamadas ao whisper, respostas a meia-frase se o endpointing deixar); maior valor = menos picote, porém mais "pontes" (emenda turnos distintos) e mais atraso.
Barge-in
Não afeta o disparo da interrupção. Afeta quando o turno do cliente é dado como encerrado depois dela.
Interação com o labeling
Nossa convenção rotula pausas <0.8 s dentro da mesma fala → min_silence entre 0.5 e 0.8 é a janela coerente: abaixo de 0.5 fragmenta falas rotuladas (pausas internas de 0.5–0.8 s viram corte); acima de 0.8 começa a emendar falas distintas (ponte punida).

prefix_padding_duration

default livekit: 0.5 s · nosso valor: 1.0 s
O que é
Áudio retroativo mantido antes do início detectado da fala no chunk emitido. O VAD guarda um buffer circular; quando confirma fala, o chunk já nasce com esse contexto anterior.
Atraso percebido
Zero. É retrospectivo — o áudio já estava gravado. Não confundir com espera.
STT
É o seguro contra cortar o primeiro fonema: se o onset atrasou 150 ms, o padding cobre o que o detector perdeu. Whisper transcreve visivelmente melhor a primeira palavra com contexto. Custo: payload maior (1 s × nº de chunks) e, na régua stt_emitted do avaliador, aparece como "spill" — esperado.
Barge-in
Nenhum efeito.

max_buffered_speech

default livekit: 60 s · nosso valor: 60 s
O que é
Teto do buffer de fala de um segmento. Monólogo mais longo que isso tem o excedente descartado do chunk (com warning no log) — o VAD continua detectando, mas o áudio extra não vai ao STT.
Quando importa
Ligações com falas muito longas (nosso dataset tem rótulo de 49 s — perto do teto). Se transcrição integral de monólogos importar, subir; custo é memória.

sample_rate (8000 | 16000) e a janela de 32 ms

nosso valor: 16000 (chega G711 16 kHz do SBC, conteúdo banda-estreita)
O que é
Taxa do modelo Silero. A 16 kHz a janela de inferência é 512 amostras = 32 ms; a 8 kHz, 256 amostras = 32 ms também.
Observação do nosso tráfego
O conteúdo real é 0–3.4 kHz (G729); alimentar o modo 8 kHz (decimando 2×, sem perda) é um experimento pendente que pode calibrar melhor as probabilidades — ver RELATORIO_V2 §6.

4. Parâmetros do AgentSession (livekit-agents 1.3.10)

turn_detection

default: automático na ordem realtime_llm → vad → stt → manual
O que é
A estratégia para decidir "o cliente terminou o turno": "vad" (eventos do VAD), "stt" (fim-de-fala do provedor de STT), "realtime_llm", "manual", ou um plugin detector de fim-de-turno (modelo EOU).
No nosso caso
Com whisper-1 batch não há sinal de endpointing do STT em tempo real → o modo efetivo é "vad": o turno fecha pelos eventos do nosso detector. Ou seja, a qualidade do VAD é a qualidade do turn-taking.

min_endpointing_delay

default: 0.5 s
O que é
Tempo mínimo desde a última fala detectada antes de declarar o turno encerrado. Em modo VAD comporta-se como max(silêncio do VAD, min_endpointing_delay) (docstring do 1.3.10) — o silêncio que o VAD já contou vale para o endpointing.
Consequência prática
Se min_endpointing_delay ≤ min_silence_duration, o endpointing custa zero extra: quando o END_OF_SPEECH chega, a espera já foi cumprida. Com nossos valores (0.5 e 0.5), é o caso. Subir o min_endpointing acima do min_silence adiciona a diferença ao atraso de resposta.
Detalhe fino
O evento de "usuário em silêncio" usa raw_accumulated_silence ≤ min_endpointing_delay/2 para tolerar micro-pausas durante fala ativa — mais um motivo para não deixar esse valor minúsculo.

max_endpointing_delay

default: 3.0 s
O que é
Teto de espera quando um detector de fim-de-turno (plugin EOU) acha que o cliente ainda não terminou ("...meu CPF é" → espera mais). Sem plugin EOU, na prática não estica o turno.
Quando importa
Se um dia plugarmos um turn detector semântico, este vira o teto do "aguarde, ele vai continuar a frase".

allow_interruptions · min_interruption_duration · min_interruption_words

defaults: True · 0.5 s · 0 palavras
O que é
O barge-in. A cada INFERENCE_DONE (32 ms), se speech_duration ≥ min_interruption_duration, o agente é interrompido (verificado no código do 1.3.10). min_interruption_words exige N palavras transcritas — só funciona com STT streaming; com whisper batch, deixe 0.
Latência do barge-in
≈ rampa de detecção (64–128 ms) + min_interruption_duration. Com 0.5 s: o robô cala ~0.6 s depois do cliente começar a falar por cima. Baixar para 0.2–0.3 s deixa mais responsivo, mas aí qualquer órfão de 0.3 s cala o robô sozinho — e nossos falsos positivos medem justamente 0.1–0.3 s. O par (min_interruption, taxa de órfãos do VAD) tem que ser calibrado junto.

false_interruption_timeout · resume_false_interruption

defaults: 2.0 s · True
O que é
A rede de segurança dos órfãos: se após a interrupção o usuário fica em silêncio e nenhuma transcrição aparece em 2 s, o LiveKit emite agent_false_interruption e (por default) retoma a fala do agente de onde parou.
Por que importa aqui
É a mitigação nativa da nossa gravidade nº 2 no lado do barge-in: um órfão ainda cala o robô por ~2 s (ruim, mas recuperável). O custo real do órfão que sobra é o chunk-lixo indo ao whisper — por isso o pós-filtro por no_speech_prob proposto no RELATORIO_V2 §6 completa a defesa.

min_consecutive_speech_delay · discard_audio_if_uninterruptible

defaults: 0.0 s · True
O que é
Espaçamento mínimo entre falas consecutivas do agente; e se o áudio do usuário é descartado enquanto o agente fala em modo não-interrompível (evita processar "fala represada" depois).

5. Mapa de interações

ParComo interagem
min_silence × min_endpointing_delay Em modo VAD o endpointing é max() dos dois → o maior deles define o piso do atraso de resposta. Mantê-los iguais (0.5/0.5) é o ponto sem desperdício: nenhum espera à toa pelo outro.
min_silence × convenção de labeling (0.8 s) Pausas <0.8 s são "a mesma fala" no ground truth. min_silence <0.5 fragmenta o que o rótulo diz ser uma fala só (punido como fragmentação); >0.8 emenda falas distintas (punido como ponte). A janela sã é 0.5–0.8.
activation × min_speech Controlam falsos positivos por caminhos opostos: activation filtra por confiança, min_speech por duração. Com act baixa (0.16, necessário no nosso áudio), min_speech maior (0.10–0.15) recupera a proteção contra blips sem perder fala baixa — trade visível no par (órfãos, onset p90) do avaliador.
min_speech × min_interruption_duration O speech_duration que dispara a interrupção só começa a contar no START (após min_speech). Efetivamente: barge-in ≈ rampa + max(min_speech, min_interruption). Como min_interruption (0.5) ≫ min_speech (0.05), quem manda é o min_interruption.
min_interruption_duration × taxa de órfãos do VAD Interrupção barata (0.2 s) exige VAD com pouquíssimo órfão; interrupção cara (0.5 s) tolera órfãos curtos mas deixa o robô "teimoso". É o mesmo trade-off medido pelos gates (onset p90) e pelo vetor (órfãos) — calibrar junto.
prefix_padding × activation/min_speech O padding perdoa onset atrasado no payload (o áudio perdido volta pelo buffer), mas NÃO perdoa no barge-in (o evento continua atrasado). Por isso o avaliador mede as duas réguas separadas (vad_decision vs stt_emitted) e a latência de onset à parte.
deactivation × fala de fundo Deact baixa mantém o segmento aberto sobre voz de fundo fraca (intrusão via continuação — o modo de falha dominante no nosso dataset). A trava de nível do VAD2 ataca exatamente isso, ao custo de recall.

6. Perguntas diretas

"min_silence_duration adiciona atraso perceptível à conversa?"
Sim — é o piso estrutural do atraso de resposta (o robô não tem como saber que você parou sem esperar esse silêncio). Com 0.5 s + whisper batch + LLM + TTS, o silêncio total percebido fica tipicamente em 1.5–2.5 s. Baixar min_silence reduz esse piso, mas aumenta picote e respostas a meia-frase. Não é o parâmetro certo para "deixar mais esperto" — é o certo para o ritmo do turno.
"min_silence picota mais o áudio pro STT?"
Quanto menor, mais picote: toda pausa do cliente maior que o valor vira um corte e um chunk novo. Quanto maior, menos chunks, porém turnos distintos começam a ser emendados (e a resposta atrasa). O sinal de picote demais no avaliador é a fragmentação; de picote de menos, a ponte.
"prefix_padding atrasa algo?"
Não. É retroativo (buffer já gravado), custo zero de latência. Só aumenta o tamanho do payload e melhora a primeira palavra no whisper. O único "efeito colateral" é visual: na régua stt_emitted ele aparece como spill — esperado e inofensivo.
"o que faz o robô calar rápido quando falo por cima?"
Nesta ordem: activation_threshold baixo (rampa curta), min_speech baixo e min_interruption_duration baixo. O terceiro domina (0.5 s vs 0.05 s dos outros). E a rede de segurança false_interruption_timeout (2 s) desfaz interrupções causadas por falso positivo, retomando a fala.
"e o que faz o robô responder rápido quando termino de falar?"
min_silence_duration (piso), min_endpointing_delay (só custa algo se for maior que o min_silence) e, fora do VAD, a latência do whisper batch + LLM + TTS — que no nosso desenho só começam DEPOIS do END_OF_SPEECH, porque o chunk nasce nele.

7. Valores para o nosso tráfego (e como testar mudanças)

ParâmetroDefault livekitNosso valorRacional
activation_threshold0.50.16banda-estreita G729 deprime as probs; sweet spot medido no grid
deactivation_thresholdact−0.150.06segura caudas de fala fraca; eixo quase plano 0.05–0.10
min_speech_duration0.050.05 (testar 0.10–0.15)anti-blip vs onset p90 — o grid + gates arbitram
min_silence_duration0.550.5janela sã 0.5–0.8 pela convenção de labeling; 0.5 = resposta mais ágil
prefix_padding_duration0.51.0seguro de primeiro fonema para o whisper; custo zero de latência
min_endpointing_delay0.50.5≤ min_silence → custo extra zero (modo VAD usa max())
max_endpointing_delay3.03.0só relevante com plugin EOU
min_interruption_duration0.50.5 (reavaliar se órfãos → 0)barge-in ~0.6 s; baixar exige VAD limpo de órfãos
min_interruption_words00exige STT streaming; whisper é batch
false_interruption_timeout2.02.0rede de segurança dos falsos positivos de barge-in
Como testar qualquer hipótese daqui
Os parâmetros do silero.VAD (thresholds, min speech/silence, padding) são todos eixos do runner: replay em ~60 ms, grid com gates, e o /eval mostra fragmentação/ponte/órfãos/onset por config. Os parâmetros do AgentSession não passam pelo avaliador (são do turn-taking, não do detector) — mas as métricas que os alimentam sim: a latência de onset p90 diz o que esperar do barge-in, e a taxa de órfãos diz se dá para baixar o min_interruption_duration com segurança.

Fontes: livekit-agents 1.3.10 (voice/agent_session.py, voice/agent_activity.py), plugin livekit-plugins-silero (espelhado em oracle/vad.py), medições em RELATORIO_V2.md e na página Metodologia.