Pipeline de voz em tempo real: do áudio bruto à IA
Pontos chave: Construir um pipeline de voz em tempo real significa encadear Deepgram STT (streaming com VAD), um LLM via OpenRouter (function calling), e OpenAI TTS — tudo conectado por um WebSocket autenticado por JWT. Este artigo detalha cada etapa, as latências, os fallbacks e os bugs que me custaram noites sem dormir.
O coração do TAMSIV é a voz. Não é um gadget, não é um botão de microfone escondido em um canto. A voz É a interface principal. Você aperta, você fala, a IA entende e executa. Mas construir um pipeline de voz em tempo real sozinho é entrar em um mundo onde cada milissegundo conta e onde tudo pode quebrar a qualquer momento.
Após três semanas de desenvolvimento intensivo e uma quantidade irrazoável de café, tenho um pipeline que responde em 1,5 a 3 segundos. Veja como funciona, pedaço por pedaço.
Como funciona a arquitetura do pipeline de voz?
O pipeline completo em uma linha:
Áudio PCM 16kHz mono → WebSocket (JWT) → Deepgram Live STT (VAD) → OpenRouter LLM → Function calling → OpenAI TTS → Resposta vocal
Seis etapas, seis pontos potenciais de falha. Cada uma tem suas restrições, suas latências e suas armadilhas. Tudo deve se encadear em menos de 3 segundos para que a experiência seja fluida. Além disso, o usuário pensa que o aplicativo travou.
Vamos decompor cada etapa.
Por que o WebSocket é indispensável para áudio em tempo real?
O HTTP clássico não funciona para streaming de áudio. Você precisaria enviar toda a gravação de uma vez, esperar o processamento e depois receber a resposta. A latência seria inaceitável.
O WebSocket permite um fluxo bidirecional contínuo: o telefone envia blocos de áudio enquanto o usuário fala, e o backend pode começar a processar antes mesmo que o usuário termine.
A autenticação JWT
Cada conexão WebSocket é autenticada via um token JWT Supabase:
ws://backend:3001?token=eyJhbGciOiJIUzI1NiIs...
O token é válido na conexão. Se o token expirar no meio da conversa (os tokens Supabase expiram após 1 hora), o cliente detecta a desconexão e se reconecta automaticamente com um token novo. Tive que gerenciar esse caso explicitamente — no início, conversas longas travavam misteriosamente.
A segurança do WebSocket é detalhada no artigo sobre auditoria de segurança e rate limiting.
Como o Deepgram gerencia o Speech-to-Text em streaming?
O áudio deve estar em PCM 16 bits, 16kHz, mono. O telefone captura o áudio neste formato e envia blocos binários brutos via WebSocket. Sem compressão, sem codificação — o PCM bruto é o formato mais rápido de processar.
Deepgram recebe esses blocos e transcreve em streaming. Mas a verdadeira mágica é o VAD (Voice Activity Detection).
Por que o VAD muda tudo?
Sem VAD, é preciso implementar um timeout de silêncio no lado do cliente: se o usuário não fala por X segundos, considera-se que ele terminou. O problema:
- Muito curto (1s): você corta o usuário que está pensando entre duas frases.
- Muito longo (3s): o aplicativo fica lento, o usuário espera.
- Variável dependendo do usuário: alguns falam rápido, outros levam seu tempo.
O VAD do Deepgram detecta quando o usuário terminou de falar com uma precisão notável. Ele analisa o sinal de áudio em tempo real e envia um evento speech_final quando tem certeza de que o usuário terminou. Isso leva cerca de 200ms após o fim da fala.
Os resultados intermediários vs finais
Deepgram envia dois tipos de resultados:
is_final: false— Resultados intermediários, instáveis. A palavra detectada pode mudar à medida que o contexto se enriquece.is_final: true— Resultados confirmados. O texto não mudará mais.
A armadilha: acumular os resultados intermediários para exibir uma prévia em tempo real (bom para a UX) enquanto transmite apenas os resultados finais para o LLM (bom para a qualidade). Eu exibo os resultados intermediários em cinza e os finais em branco — o usuário vê sua voz se transformar em texto ao vivo.
Para a comparação entre STT nativo (gratuito, no dispositivo) e Deepgram cloud (mais preciso), leia meu artigo detalhado STT nativo vs Deepgram.
Como o LLM orquestra as ações via function calling?
A transcrição completa vai para o OpenRouter com function calling. OpenRouter é um roteador que dá acesso a mais de 400 modelos LLM com fallback automático — se o modelo principal estiver fora do ar, um fallback assume em poucos segundos.
O LLM recebe a transcrição e deve entender a intenção do usuário. Ele dispõe de 7 funções:
create_task— Criar uma tarefaupdate_task— Modificar uma tarefa existentecreate_memo— Criar um memorandoupdate_memo— Modificar um memorando existentecreate_calendar_event— Criar um evento de calendárioask_clarification— Pedir uma clarificação ao usuárioend_conversation— Terminar a conversa
O LLM analisa a frase ("lembre-me de comprar pão amanhã às 10h"), identifica a ação (create_task), extrai os parâmetros (título, data, hora) e retorna uma chamada de função estruturada. O backend executa a ação e envia o resultado para o frontend.
O padrão PendingCreation é crucial aqui: o backend cria uma prévia do item, e o usuário pode validar, editar ou cancelar antes da gravação definitiva no banco de dados. Zero surpresas desagradáveis.
A latência do LLM
O LLM representa a maior parte da latência: entre 800ms e 2 segundos, dependendo do modelo e da complexidade da requisição. É aqui que a escolha do modelo via OpenRouter faz a diferença — um modelo rápido, mas menos preciso, versus um modelo lento, mas mais confiável. O TAMSIV usa um modelo configurável com fallback automático se o modelo principal estiver muito lento ou indisponível.
Como o OpenAI TTS gera a resposta de voz?
Uma vez que a ação é executada, o backend gera uma resposta textual ("Está anotado! Criei a tarefa 'Comprar pão' para amanhã às 10h"). Este texto é enviado para o OpenAI TTS com a voz nova.
O áudio é transmitido de volta via o mesmo WebSocket. O frontend começa a tocar os primeiros blocos de áudio, sem esperar a resposta completa. Isso reduz a latência percebida em cerca de 500ms — o usuário ouve o início da resposta enquanto o final ainda está sendo gerado.
Para personalização vocal e escolha da voz TTS, eu falo sobre isso no artigo sobre personalização vocal.
Quais são os três modos WebSocket do TAMSIV?
Ao longo do desenvolvimento, três modos WebSocket surgiram:
- LiveWebSocketServer (padrão) — STT nativo do dispositivo + fallback Deepgram, orquestração LLM, OpenAI TTS. Este é o modo padrão, o mais econômico.
- RealtimeWebSocketServer — API OpenAI Realtime, bidirecional, baixa latência. Mais caro, mas mais fluido para conversas longas.
- WebSocketServer — Batch STT/TTS, modo legado. Usado para casos onde o streaming não é necessário.
O modo é selecionável no painel de administração via a tabela app_config no Supabase. Isso permite alternar entre os modos sem implantar uma nova versão do aplicativo.
Como gerenciar erros em um pipeline em tempo real?
A regra de ouro: tudo pode falhar a qualquer momento. Deepgram pode estar fora do ar. OpenRouter pode atingir o tempo limite. OpenAI TTS pode retornar um erro 429. A conexão WebSocket pode cair no meio da conversa.
Aqui estão os mecanismos de resiliência:
- Retentativas inteligentes: Cada etapa tem um número configurado de retentativas com backoff exponencial. Sem retentativas em loop infinito.
- Circuit breakers: Se um serviço falha com muita frequência, paramos de chamá-lo por X segundos para evitar sobrecarregar um serviço já em dificuldade.
- Fallbacks em cada etapa: STT nativo se Deepgram estiver fora do ar, modelo LLM alternativo via OpenRouter, resposta de texto se TTS falhar.
- AlertService: Cada fallback aciona um alerta por e-mail (via Resend) + log Supabase. Eu sei em tempo real quando algo degrada.
Cada linha de gerenciamento de erros representa um bug vivenciado em produção. O pipeline é robusto hoje, mas foram necessárias dezenas de sessões de depuração para chegar a este ponto.
Qual é o orçamento de latência de cada etapa?
Decomposição de uma interação vocal completa:
- Captura de áudio + envio WebSocket: ~50ms (negligenciável)
- Deepgram STT + VAD: ~200-400ms após o fim da fala
- OpenRouter LLM (function calling): ~800ms-2000ms (variável)
- OpenAI TTS (primeiro chunk): ~300-500ms
Total: 1.3 a 3 segundos. O objetivo é permanecer abaixo de 2 segundos para 90% das interações. Além disso, a experiência torna-se frustrante.
O streaming TTS é a melhor alavanca: o usuário ouve o início da resposta após ~1.5s em média, mesmo que a geração completa leve 3 segundos. A latência percebida é muito menor do que a latência real.
Que lições tirar para construir o teu próprio pipeline de voz?
Após três semanas de desenvolvimento intensivo, aqui está o que eu aconselharia:
- Comece pelo WebSocket: É a espinha dorsal. Se o WebSocket for sólido, o resto se encaixa.
- Use o VAD do provedor STT: Não implemente a sua própria detecção de silêncio — é um abismo de complexidade para um resultado inferior.
- Transmita tudo: STT em streaming, TTS em streaming. Cada milissegundo economizado melhora a UX.
- Preveja os fallbacks desde o primeiro dia: Não em modo "verei mais tarde". Cada etapa deve ter um plano B.
- Meça a latência em produção: Os benchmarks locais são enganosos. A latência real depende da rede, da carga do servidor e da localização geográfica. O dashboard admin foi indispensável para isso.
FAQ
Por que Deepgram em vez de Google Speech-to-Text ou AWS Transcribe?
Deepgram oferece a melhor relação qualidade/latência para streaming. Google STT é excelente, mas mais caro e mais lento no modo streaming. AWS Transcribe é robusto, mas a integração WebSocket é mais complexa. Deepgram também tem um VAD integrado, o que simplifica enormemente o código.
O pipeline funciona em modo offline?
O STT nativo do dispositivo funciona offline. Mas o LLM e o TTS exigem uma conexão com a internet. No modo offline, o TAMSIV permite a entrada de texto clássica e enfileira as solicitações de voz para quando a conexão retornar.
Quanto custa uma interação vocal completa?
Com o STT nativo (gratuito), um LLM econômico via OpenRouter (~$0.001-0.01) e OpenAI TTS (~$0.015/1000 caracteres), uma interação custa entre $0.01 e $0.03. O detalhe dos custos está no artigo retrospectivo sobre os 650 commits.
É possível substituir o OpenAI TTS por uma solução de código aberto?
Tecnicamente sim. Projetos como Coqui TTS ou Bark produzem resultados corretos. Mas a qualidade da voz "nova" do OpenAI continua superior, e o streaming é melhor suportado. Para um projeto em produção, o custo adicional do OpenAI TTS ($15/milhão de caracteres) justifica-se pela qualidade.
Como gerenciar várias línguas no pipeline de voz?
Deepgram suporta detecção automática de idioma. O LLM via OpenRouter é naturalmente multilíngue. O TTS OpenAI gera áudio no idioma do texto fornecido. TAMSIV suporta 6 idiomas (FR, EN, DE, ES, IT, PT) sem configuração específica por idioma no pipeline. O detalhe da internacionalização está no artigo sobre i18n em 6 idiomas.