Blog
AI/Voice
10 de dezembro de 20258 min

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.

Ondas sonoras se propagando em um ambiente escuro com partículas de luz azul e ciano
Da onda sonora à resposta estruturada: cada milissegundo conta.

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.

Microfone profissional de estúdio com indicador LED em um ambiente escuro, iluminação azul e roxa
A captura de áudio é a primeira etapa crítica do pipeline.

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 tarefa
  • update_task — Modificar uma tarefa existente
  • create_memo — Criar um memorando
  • update_memo — Modificar um memorando existente
  • create_calendar_event — Criar um evento de calendário
  • ask_clarification — Pedir uma clarificação ao usuário
  • end_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:

  1. LiveWebSocketServer (padrão) — STT nativo do dispositivo + fallback Deepgram, orquestração LLM, OpenAI TTS. Este é o modo padrão, o mais econômico.
  2. RealtimeWebSocketServer — API OpenAI Realtime, bidirecional, baixa latência. Mais caro, mas mais fluido para conversas longas.
  3. 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.

Cabos de fibra óptica luminosos azuis e verdes em um centro de dados, visualização de fluxos de dados
Os dados atravessam o pipeline na velocidade da luz — em teoria.

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:

  1. Comece pelo WebSocket: É a espinha dorsal. Se o WebSocket for sólido, o resto se encaixa.
  2. 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.
  3. Transmita tudo: STT em streaming, TTS em streaming. Cada milissegundo economizado melhora a UX.
  4. Preveja os fallbacks desde o primeiro dia: Não em modo "verei mais tarde". Cada etapa deve ter um plano B.
  5. 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.