Skip to content
anecho.ai

O pipeline dividido: por que o seu VAD e o seu STT querem áudios diferentes

A detecção de turnos quer um stream limpo. A transcrição quer o bruto. Agora temos os nossos próprios números para as duas metades dessa afirmação: o aprimoramento de fala (speech enhancement) nos custou cinco pontos de taxa de erro de palavras (WER) agrupada e cortou os falsos alarmes de detecção de atividade de voz (VAD) em um terço — enquanto vencia o bruto de forma direta em 11 das 18 condições individuais.

Anecho Engineering09 de dez. de 202513 min de leitura
architecturevadturn-takingsdklatency

Atualizado em 14 de agosto de 2026. Quando este post foi publicado pela primeira vez, a evidência para a divisão era de outras pessoas — um estudo estreito e uma posição declarada de fornecedor. Agora ela é nossa e é medida, e os números abaixo foram desde então regenerados em dezoito condições acústicas, em vez de treze. Os valores absolutos se moveram, a ordenação não, e tudo aqui é citado do benchmark.json publicado.

O seu agente de voz tem um stream de microfone e dois consumidores com requisitos opostos. O detector de turnos quer o sinal mais limpo possível, para não disparar um barge-in por causa de uma televisão. O reconhecedor quer o áudio com o qual de fato treinou, artefatos incluídos. Quase toda stack que já olhamos entrega a ambos o mesmo buffer, e então ajusta um às custas do outro.

Este post é o argumento de engenharia para dividir esse stream, as medições que agora o sustentam e as partes que são chatas de acertar: alinhamento de atraso, pre-roll e onde a divisão pertence no pipeline de um framework de agentes.

Os dois consumidores querem coisas diferentes

A troca de turnos (turn-taking) quer limpo. VAD e endpointing operam sobre energia e pistas espectrais em frames curtos. São máquinas de limiar. Derrube o SNR e todo limiar que você calibrou se move: a detecção de início dispara tarde, porque a razão fala-ruído demora mais para cruzar, e a detecção de fim dispara cedo ou nunca, porque o piso de ruído nunca cai abaixo do limiar de hangover. O modo de falha de maior valor em um agente de voz é um disparo falso de barge-in — um segundo locutor na sala, uma TV, babble de café com um fragmento inteligível dentro — e esse é exatamente o caso em que um supressor que isola o locutor primário transforma um sinal inutilizável em um utilizável.

A transcrição quer bruto. Modelos modernos de STT são treinados com quantidades muito grandes de áudio real e ruidoso. Ruído está dentro da distribuição. Artefatos de supressão neural — buracos espectrais, ruído musical, borrão de transientes, um piso de ruído que fica anormalmente morto entre as palavras — não estão. Um estudo independente (arXiv:2512.17562) constatou que o áudio aprimorado pontuou pior que o bruto em todas as 40 configurações testadas, com a ressalva de que usou apenas o MetricGAN+, em fala médica, em WER semântica. A AssemblyAI, citando-o, reportou separadamente o cancelamento de ruído da Krisp aproximadamente dobrando a WER quando a saída dele era alimentada ao STT, enquanto cortava os falsos disparos de VAD em cerca de 3.5x. Isso era evidência pública, e magra. Ela não é mais a única evidência que temos.

O que a nossa própria rodada diz sobre as duas metades

Rodamos dez engines de aprimoramento em dezoito condições acústicas contra um controle bruto, pontuando WER e VAD no mesmo passe. Procedência: um único reconhecedor, faster-whisper base.en (CTranslate2 int8 CPU, greedy), Silero VAD com limiar 0.5, 10 locutores / 180 clipes / aproximadamente 1406 segundos, cerca de 190 palavras de referência por condição, Apple M1 Max, onnxruntime 1.28.0 fixado em uma única thread intra-op. Método completo e ressalvas no post de lançamento do benchmark.

No braço da transcrição, o aprimoramento perdeu a média agrupada. A WER agrupada foi 14.77% do bruto contra 15.56% da melhor engine (ai-coustics Quail L) e 20.26% do GTCRN. As inserções — o reconhecedor inventando palavras que ninguém disse — foram de 101 no bruto para 210 no GTCRN e 380 no DeepFilterNet3. Nenhuma das dez venceu não fazer nada nessa média agrupada.

Por condição, a frase é outra, e o pipeline dividido depende de as duas serem verdadeiras. Pelo menos uma engine venceu o bruto em 11 das 18 condições, e as vitórias são grandes onde acontecem: em ruído de banda larga a 0 dB, oito das dez engines vencem o bruto, a melhor por 4.7 pontos. O aprimoramento paga onde o áudio é genuinamente ruim e te custa onde não é. A tabela completa e as suas ressalvas estão em A supressão de ruído realmente ajuda o speech-to-text?.

No braço da troca de turnos, o quadro se inverte — mas não na métrica que as pessoas costumam citar. O F1 de VAD agrupado mal se moveu: 0.950 do bruto contra 0.947 do melhor stream aprimorado. Se você avaliasse o aprimoramento pelo F1 de VAD, concluiria que ele não faz nada.

A taxa de falsos alarmes conta uma história completamente diferente, e falsos alarmes são o que um barge-in realmente é.

CondiçãoTaxa de falsos alarmes de VAD, brutoAprimoradoEngine
Babble a 5 dB97.7%68.2%FastEnhancer-L
Locutor concorrente a 5 dB58.1%37.2%ai-coustics Quail VF

Sob babble a 5 dB, o stream bruto dispara falso alarme em essencialmente todo frame sem fala que o detector vê — 97.7% é um VAD que deixou de funcionar como gate. O aprimoramento leva isso a 68.2%. Ainda é ruim, e é uma melhora de trinta pontos no único número que mapeia diretamente para o seu agente interrompendo uma pessoa que não falou.

Esta é a base medida para a divisão. O aprimoramento ganha o seu lugar no braço de troca de turnos incondicionalmente, e ganha o seu lugar no braço da transcrição apenas em condições específicas — na mesma rodada, no mesmo áudio, com o mesmo reconhecedor. Essas não são duas opiniões a serem negociadas. São dois braços que querem dois sinais diferentes, e um deles quer um sinal diferente dependendo da sala.

Dois limites honestos sobre isso. O F1 mal se mover significa que o aprimoramento está, em algum grau, trocando falsos alarmes por perdas, e 190 palavras de referência por condição separam efeitos grandes, não pequenos. E estas são estatísticas de VAD em nível de frame, não limites de turno rotulados: "menos barge-ins falsos" como métrica de produto de ponta a ponta ainda precisa de uma definição acordada de barge-in falso, que ainda não temos.

A arquitetura

Stream único é um compromisso que ninguém escolheu deliberadamente:

                     ┌──────────────┐
  mic ──▶ enhance ──▶│  same buffer │──▶ VAD / endpointing   (happy)
                     └──────────────┘──▶ STT                 (five points worse, pooled)

O pipeline dividido é a mesma análise, duas saídas:

                 ┌─▶ enhanced ──▶ VAD / endpointing / barge-in
  mic ──▶ enhance┤
                 └─▶ raw (delay-matched) ──▶ ring buffer ──▶ STT
                          ▲
                          └── turn boundaries index into HERE

Um passe de inferência, dois taps. O aprimorador já computa internamente uma estimativa de presença de fala; o custo extra de emitir os dois streams é uma cópia e uma linha de atraso, não um segundo modelo.

A parte que todo mundo erra: o stream bruto chega adiantado

Um aprimorador tem latência algorítmica — o frame de análise mais qualquer lookahead. Entre os modelos do nosso manifesto, isso é de 12 a 30 ms para tudo, exceto o DeepFilterNet3, que medimos em 100 ms através de um adaptador block-online. A saída aprimorada, portanto, fica atrás da entrada bruta por esse tanto.

Se você emite os dois streams ingenuamente, o detector de turnos produz limites no tempo do stream aprimorado e você os aplica a índices do stream bruto, então cada enunciado que você fatia para o STT está deslocado em um frame. A 30 ms, isso é o suficiente para cortar uma plosiva no início de palavra, e um /p/ cortado é uma substituição ou uma deleção na sua transcrição. O bug é sutil porque não falha de forma barulhenta; ele só te custa uma fração de ponto de WER para sempre.

A correção é uma linha de atraso no caminho bruto, casada com o atraso declarado do aprimorador, para que os dois streams compartilhem um relógio de amostras. O nosso SDK o declara como model.audioDelay — 15 ms a 16 kHz — e torna o contrato checável: process() retorna exatamente tantas amostras quantas recebeu, e as primeiras audioDelay amostras de um stream voltam como silêncio. A linha de atraso em si é código seu, e é um ring buffer que você escreve uma vez:

import { Model, Processor, UsageReporter } from '@anecho/sdk';

const reporter = new UsageReporter(process.env.ANECHO_API_KEY);
const model = Model.fromFile('anecho_focus_16k_v2.anecho');  // from your dashboard
const proc = new Processor(model, await reporter.ensureLicense());

const rawTap = delayLine(model.audioDelay);  // yours: audioDelay samples deep

// 20 ms of float32 mono at 16 kHz = 320 samples — one model hop
for await (const block of micBlocks) {
  const enhanced = proc.process(block);
  const raw = rawTap.push(block);       // now sample-aligned with `enhanced`

  turnDetector.push(enhanced);          // clean stream drives boundaries
  rawRing.write(raw);                   // raw stream is what STT will read
}

O arquivo de modelo é o .anecho por cliente que você baixa do dashboard — um modelo, 16 kHz, 4.0 MB. A decisão de fala que alimenta o detector de turnos pode vir do Vad do SDK, que lê o mesmo arquivo de modelo, então a divisão não te custa um segundo modelo nem um segundo download.

Pre-roll, ou por que a sua primeira palavra desaparece

Um VAD declara fala depois de ter visto fala. Todo detector tem um atraso de início — os frames de que ele precisou para ter confiança. Se você começa a encher o buffer do STT no momento em que o VAD dispara, já jogou fora o ataque da primeira palavra.

É por isso que o ring buffer importa mais do que a própria divisão. Você quer um ring buffer bruto escrito continuamente, com algumas centenas de milissegundos de histórico, e limites de turno que indexam para dentro dele:

turnDetector.on('turn', async (turn) => {
  // turn.startSample / turn.endSample index the shared sample clock,
  // so they are valid indices into the raw ring too
  const utterance = rawRing.slice(
    turn.startSample - PREROLL_SAMPLES,
    turn.endSample + HANGOVER_SAMPLES,
  );
  await stt.transcribe(utterance);
});

Dois defaults que valem ser declarados explicitamente, porque são os que as pessoas ajustam primeiro:

  • Pre-roll de 300 ms. Seguro barato. A 16 kHz mono float32, são 19.2 KB de memória por stream.
  • Hangover depois do fim da fala. Fricativas finais e oclusivas finais não liberadas têm baixa energia e são cortadas por um endpointer apressado. Se as suas transcrições perdem sistematicamente o /s/ de plural, é por isso.

Em Python, o formato é o mesmo:

import os
from anecho import Model, Processor, UsageReporter

reporter = UsageReporter(os.environ["ANECHO_API_KEY"])
model = Model.from_file("anecho_focus_16k_v2.anecho")
proc = Processor(model, reporter.ensure_license())

raw_tap = DelayLine(model.audio_delay())   # yours, as above

for block in mic_blocks(320):          # 20 ms at 16 kHz — one model hop
    enhanced = proc.process(block)
    turn_detector.push(enhanced)
    raw_ring.write(raw_tap.push(block))

Onde a divisão vive em uma stack real

Em um framework de agentes, a divisão tem que acontecer antes do roteamento do próprio framework, porque o framework presume uma única track de áudio. Esse ponto — o callback de áudio, o frame processor, o hook de mídia, como quer que o seu framework o chame — é onde o loop acima entra. Nós o entregamos como uma função de bloco para bloco, em vez de um plugin por framework, precisamente para que caiba em qualquer uma dessas costuras: o processador não tem opinião sobre o que está rio abaixo, e o roteamento continua sendo código seu, o que significa que continua mensurável.

A matriz de roteamento, declarada com clareza — cada linha é uma fiação dos dois taps, e alternar entre elas é uma linha no seu loop:

fiaçãoA troca de turnos recebeO STT recebeQuando usar
vad-onlyaprimoradobruto, com atraso casadodefault; ambientes ruidosos, problemas de barge-in
bothaprimoradoaprimoradotráfego com locutor concorrente, onde os nossos dados dizem que o STT também ganha
stt-onlybrutoaprimoradoraro; só se o seu VAD já é robusto a ruído e o seu STT não é
disabledbrutobrutocontrole, e aquilo contra o que você deve medir

Recomendamos vad-only como default porque é para onde as nossas próprias medições apontam, no nosso corpus, com um reconhecedor — não porque o tenhamos provado para o seu tráfego.

A linha both não é hipotética, e também não é rara. O aprimoramento venceu o bruto na transcrição em 11 das nossas 18 condições — de forma mais clara onde uma voz concorrente ou ruído de banda larga em SNR baixo está genuinamente destruindo pistas fonéticas. O maior resultado isolado é o ai-coustics Quail VF em locutor concorrente a 5 dB, cortando as inserções de 19 para 10 e a WER de 26.3% para 20.5%. Se as pessoas que te ligam ficam em salas com outro humano falando, both com aprimoramento é defensável com base em evidência. Agrupado em todas as dezoito condições, o mesmo modelo está mais de três pontos pior que o bruto, porque ele também roda sobre áudio limpo, reverberante e degradado por operadora, onde só pode remover informação — e é por isso que ele não é o default, e por isso esta é uma medição por implantação, em vez de uma configuração que possamos escolher por você.

O custo, e o que a divisão não custa

A objeção que ouvimos é que isso dobra o áudio que você está movendo. Não dobra, se você mantém o caminho bruto local. O aprimoramento roda onde o áudio já está — no worklet do navegador, no seu servidor de mídia ou no seu processo Node — e apenas o stream de que um dado consumidor precisa cruza uma fronteira de rede. O stream aprimorado alimenta um VAD que geralmente está no mesmo processo. O stream bruto vai para o seu fornecedor de STT, para onde ele já ia de qualquer jeito.

O que ela custa, de fato:

  • Memória. Um ring buffer por sessão. Aproximadamente 19 KB por 300 ms a 16 kHz mono float32; digamos 64 KB por sessão, com folga.
  • Uma linha de atraso. Um frame de latência do aprimorador adicionado ao caminho bruto para que os relógios casem. Isso não adiciona latência de resposta de ponta a ponta, porque o caminho bruto estava adiantado, não atrasado — você está se alinhando ao stream mais lento que você já tinha.
  • Mais uma coisa sobre a qual raciocinar. Dois streams significam dois lugares onde um bug pode se esconder. A mitigação é que os dois são derivados de uma única chamada a process() com um único relógio de amostras, em vez de dois pipelines independentes que podem derivar.

O que ainda não se sabe

  • Alguns reconhecedores podem ser treinados com áudio aprimorado. Se um fornecedor pré-processa na própria ingestão, alimentá-lo com bruto é certo por uma razão diferente, ou errado por uma razão sutil. Nenhum de nós consegue ver esses conjuntos de treinamento.
  • O ponto de cruzamento de SNR. O aprimoramento claramente vence em SNR baixo e claramente perde em áudio limpo, mas ainda não conseguimos pôr um número em onde ele vira. A densidade de vencedores tem pico a 0 dB — oito de dez engines — e rareia nas duas direções, com uma engine ainda vencendo o bruto até na condição clean. Isso é uma rampa, não um limiar, e vai diferir por engine. Localizá-lo direito é o que tornaria o aprimoramento condicional entregável.
  • Os ganhos de troca de turnos são em nível de frame, não de turno. Reportamos F1 de VAD e taxa de falsos alarmes. "Menos barge-ins falsos" precisa de limites de turno rotulados e de uma métrica acordada. Essa é a próxima coisa que devemos ao benchmark.
  • Telefonia é um caso à parte. Em um caminho de banda estreita de 8 kHz, o reconhecedor está fora da distribuição antes de você tocar em qualquer coisa, nenhum aprimorador da nossa matriz produziu melhora significativa ali e vários o degradaram muito, e o raciocínio muda; veja 8 kHz é onde a IA de voz realmente quebra. Desde então, rodamos um experimento separado a 8 kHz de ponta a ponta, com três modelos genuinamente nativos de banda estreita dentro: o nativo vence o reamostrar-e-torcer por 0.7 ponto para o mesmo fornecedor, e absolutamente nada vence o áudio intocado de quem liga.

A razão para manter o roteamento no seu próprio loop, em vez de enterrado em um default de framework, é que isso torna a pergunta testável por implantação. Alterne o tap do STT entre bruto, aprimorado e desligado, rode o seu próprio áudio pelo harness e leia o ΔWER contra o controle passthrough. Se o aprimorado vence no seu tráfego, entregue o aprimorado — o SDK não se importa com qual resposta você obtém, e nós também não, desde que a medição seja real.

O quickstart e a referência do SDK estão em /docs. O harness que produz os números de ΔWER está em github.com/anecho, e a matriz publicada está em /benchmark.

A seguir
  • 24 de out. de 2026

    Alternativas à ai-coustics para agentes de voz e call centers (2026)

    Por que equipes olham além do ai-coustics Quail Voice Focus, e uma lista curta com fontes conforme listado em outubro de 2026: Krisp VIVA, AssemblyAI Voice Focus, isolamento de voz do LiveKit Cloud, NVIDIA Speaker Focus, Anecho e referências de código aberto. Comparamos a ai-coustics com o nosso próprio modelo em conversas ao vivo: empate com o faster-whisper, vantagem do v8_2 com o Gemini Live, menos barge-ins falsos para a ai-coustics, e a condição decide quem lidera.

  • 22 de out. de 2026

    Alternativas à Krisp para agentes de voz e call centers (2026)

    Por que equipes olham além do Krisp VIVA, e uma lista curta com fontes conforme listado em outubro de 2026: ai-coustics Quail Voice Focus, AssemblyAI Voice Focus, isolamento de voz do LiveKit Cloud, NVIDIA Speaker Focus, Anecho e referências de código aberto, com onde cada um roda, seu preço público e se o medimos. Não medimos a Krisp.

  • 20 de out. de 2026

    SDKs de isolamento de locutor comparados (2026): Krisp, ai-coustics, AssemblyAI, LiveKit e Anecho

    Uma comparação com fontes do isolamento de locutor para agentes de voz e call centers conforme listado em outubro de 2026: onde cada produto roda, latência declarada, taxas de amostragem, integração, preço público e licenciamento. Krisp VIVA e Anecho declaram 15 ms em CPU; a ai-coustics lista menos de 30 ms; AssemblyAI e LiveKit vendem isolamento dentro de um serviço em nuvem. Números de qualidade apenas do nosso próprio benchmark.