fix(pipeline): alucinacao do STT nao vira mais voz no cabo virtual (#47) - #73
Open
caioross wants to merge 1 commit into
Open
fix(pipeline): alucinacao do STT nao vira mais voz no cabo virtual (#47)#73caioross wants to merge 1 commit into
caioross wants to merge 1 commit into
Conversation
Segmento sem fala real (respiracao, clique, musica ao fundo) nao volta vazio
do faster-whisper: volta uma frase plausivel ("Legendas pela comunidade
Amara.org", "Thank you."). Como o Laguna FALA a traducao no VB-CABLE, isso
virava o app conversando sozinho na call de Discord — e ainda queimava uma
frase inteira de STT+MT+TTS de fila por ruido.
O unico filtro existente era `if not text.strip()`, e os metadados que o
proprio Whisper calcula (`no_speech_prob`, `avg_logprob`) eram descartados no
`" ".join(...)` de `STT.transcribe*`.
- `is_hallucination()` em laguna_pipeline.py: funcao pura (so texto e numeros,
sem audio/modelo/GPU) com blocklist normalizada PT+EN, detector de repeticao
patologica e o par de metadados em CONJUNCAO. Todos os limiares sao
conservadores: barrar fala real e pior que deixar passar um ruido.
- `STT.transcribe_detailed()` agrega os metadados por duracao e devolve
`STTResult`; `transcribe()`/`transcribe_with_lang()` delegam e mantem as
assinaturas — nenhum chamador (fase0_poc, bench, stress, test_offline,
test_lang_detect) precisou mudar.
- `_process_segment` barra o segmento antes do MT/TTS e emite `skipped` com a
chave propria `mt.hallucination`; a UI resolve `ev.key` (evento antigo de
mesmo idioma segue no fallback). Painel nunca fica mudo sem explicacao.
- tests_unit/test_hallucination_filter.py: frases-lixo PT/EN, repeticao,
fala curta legitima que NAO pode ser barrada, limiares nas duas bordas,
agregacao ponderada e o laco de traducao com dubles.
Closes #47
Owner
Author
Estado da CI: falha de infraestrutura do GitHub, não do códigoO job pytest (lógica pura) passou nas duas execuções (3m57s). O job compile + lint mínimo falhou duas vezes sem executar um único passo do repositório:
Nenhuma das duas chegou a rodar O gate local do HANDBOOK §6 — que é o que vale, já que o CI é subconjunto do T1 — está verde e foi rodado com
Para o PR Doctor: re-executar o job |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Contexto
Quando o VAD fecha um segmento sem fala de verdade (respiração, clique de teclado, música ao fundo), o faster-whisper não devolve vazio: devolve uma frase plausível —
"Legendas pela comunidade Amara.org","Thank you.","...", repetições. O pipeline não tinha defesa: o único filtro eraif not text.strip(), e os metadados que o próprio Whisper já calcula (no_speech_prob,avg_logprob) morriam no" ".join(...)deSTT.transcribe*.Como o Laguna fala a tradução no cabo virtual, isso significava o app conversando sozinho com a call de Discord enquanto o usuário está calado — e queimando uma frase inteira de STT+MT+TTS de fila (
seg_qtemmaxsize=4) por ruído.O que mudou
laguna_pipeline.pyis_hallucination(text, no_speech_prob=None, avg_logprob=None)— decisão pura (só texto e números; sem áudio, modelo, GPU ou rede), em três camadas:"obrigado"sozinho não está lá;"obrigado por assistir"está;no_speech_prob > 0.6Eavg_logprob < -1.0).STT.transcribe_detailed(pcm, detect_lang=False) -> STTResultagrega os metadados por duração dos segmentos.transcribe()etranscribe_with_lang()passaram a delegar e mantiveram as assinaturas —fase0_poc.py,bench_fase0.py,stress_*.py,test_offline.pyetest_lang_detect.pynão precisaram de uma linha.laguna_core.py—_process_segmentbarra o segmento reprovado antes do MT/TTS: nada de tradução, nada de síntese, nada no sink. O texto barrado ainda é emitido comostt(o usuário vê o que foi descartado) e o descarte viraskippedcom chave própriamt.hallucination. A latência de STT continua contando — o trabalho existiu, e a métrica não pode mentir por omissão (#21).static/— o handler deskippedpassa a resolverev.key; evento antigo (mesmo idioma) não manda chave e continua no fallbackmt.skipped. Chave nova nos dois dicionários.Por que a conjunção nos metadados, e não
avg_logprobsozinhoMedido agora, com o modelo real, no
dry_pt2en.wavdo dono:no_speech_probavg_logprobdry_pt2en.wav(fala real PT)dry_en2pt.wav(fala real EN)''A fala legítima em PT já vem com
avg_logprob-0,75, a 0,25 do limiar. Um filtro que reprovasse por esse eixo isoladamente estaria a um sotaque de distância de emudecer fala real — daí a conjunção e o viés declarado: falso negativo é preferível a falso positivo.Durante o desenvolvimento o limiar de repetição estava em 0,75 e barrava
"vai vai vai time"(grito de torcida, 3/4 palavras). Subiu para 0,8 com o caso registrado em comentário no código e em teste.Gate (HANDBOOK §6) — resultado real
compileall→COMPILE_OK; import-smoke defase0_poc, laguna_core, laguna_server→IMPORTS_OK.test_offline.py "dry_pt2en.wav" --direction pt2en --model small --device auto— exit 0,out_gate.wavgerado, fala real traduzida ponta a ponta:STT 629ms · MT 500ms · TTS 2592ms(cuda/float16, primeira execução da worktree, modelos recém-baixados). Zero regressão de recall.node --check static/app.jsestatic/i18n.js→JS_OK;tests_unit/test_i18n_parity.py→ 2 passed (paridade PT/EN mantida).tests_unit/→ 149 passed (18 novos emtest_hallucination_filter.py).Nenhuma constante de VAD ou default de modelo foi tocada — o caminho quente do áudio não mudou. O filtro roda sobre texto, depois do STT, e custa uma normalização de string por frase.
Riscos
"sim","ok","yes","Obrigado"e ênfase curta como não-barráveis. Se aparecer um caso real barrado, o conserto é remover a entrada da blocklist ou afrouxar um limiar — tudo em constantes nomeadas no topo do bloco."tchau"e"thank you"na blocklist são um trade-off explícito: são despedidas legítimas, mas estão entre as alucinações mais frequentes do Whisper em silêncio. Perder um "tchau" no fim de uma call custa menos que o app falar sozinho no meio dela. Fácil de reverter se o dono discordar.transcribe_detailedé o único ponto que fala com o modelo agora; os dois métodos históricos são casca. Comportamento idêntico ao anterior (mesmos parâmetros detranscribe), mas o T2 acima exercitou justamente esse caminho.Solicito quórum (HANDBOOK §7)
Closes #47