Pular para o conteúdo

ADR-0013: Extensão de voz (ElevenLabs TTS + STT)

  • Status: Accepted
  • Data: 2026-05-24
  • Responsáveis pela decisão: Waldemar Szemat

A demo é um agente de adesão a medicamentos agnóstico de canal que demonstra como construir sistemas baseados em LLM para indústrias reguladas com citação verificável, conformidade e transparência de custo. Uma superfície apenas de texto não exercita a modalidade de voz que fluxos de trabalho regulados (telessaúde, centrais de atendimento ao paciente, acessibilidade) exigem. Adicionar entrada de voz (STT) e saída de voz (TTS) demonstra que o pipeline de processamento do agente — recuperação, aplicação de citação, lógica de recusa, auditoria — é genuinamente independente de canal, não acoplado a uma superfície apenas de texto.

A extensão precisa respeitar as restrições que as ADRs existentes travam:

  • Única instância scale-to-zero do Google Cloud Run, baseline de cerca de $0 em escala de demo (ADR-0007); CPU Basic do Hugging Face Spaces como alvo secundário genuinamente $0.
  • Provedores de LLM OpenAI / Anthropic / Groq (ADR-0002).
  • O contrato de resposta JSON de /chat está congelado; qualquer nova superfície não pode quebrar consumidores existentes.
  • Supabase camada gratuita para dados operacionais (ADR-0010, ADR-0015).

A camada de voz precisa ser uma extensão aditiva: desligada por padrão, desbloqueada apenas para chaves com o direito de voz e apenas após consentimento explícito, com consumo de recursos zero até então, e claramente separável do caminho apenas de texto para que o agente funcione de forma idêntica sem ela.

Como adicionamos E/S de voz à demo de uma forma que (a) prove que o pipeline de processamento é agnóstico de canal, (b) mantenha o baseline em $0/mês, (c) não quebre o contrato travado de /chat e (d) rastreie o custo contra o esquema de contabilidade existente do Supabase?

  • Prova de agnosticismo de canal: a voz precisa ser uma superfície de primeira classe, não um acréscimo improvisado, para demonstrar que o pipeline de processamento é genuinamente independente de canal.
  • Baseline de $0/mês: a demo apenas de texto permanece gratuita. O consumo de voz é opcional e orçado separadamente.
  • Estabilidade de contrato: o esquema JSON de /chat está congelado (ADR-0009). Os metadados de voz precisam trafegar ao lado, não dentro, desse esquema.
  • Observabilidade de custo: conforme a ADR-0010 e a ADR-0015, toda unidade faturável (tokens de LLM, caracteres de TTS, segundos de STT) precisa ser rastreada no Supabase para contabilidade de custo pós-demo.
  • Superfície mínima de fornecedor: o projeto já depende de OpenAI, Anthropic, Groq, Hugging Face e Supabase. A voz deve adicionar um fornecedor (ElevenLabs), não dois.
  • Orçamento de latência: a única instância scale-to-zero já tem um cold start na primeira requisição após ociosidade. A voz não pode adicionar latência ilimitada à experiência de chat no caminho quente.
  • Privacidade: áudio de conversas relacionadas a saúde é sensível. Áudio bruto não pode ser persistido; apenas o texto transcrito é registrado (após a redação dos guardrails conforme a ADR-0005).
  • Direito e consentimento: a voz é uma capacidade paga, não um padrão gratuito. Ela deve desbloquear apenas em chaves provisionadas com o direito de voz (os níveis estendido e revisor), e todo turno de áudio entregue deve ser condicionado a consentimento explícito registrado no servidor.
  • ElevenLabs eleven_multilingual_v2, sob demanda (escolhida): TTS por requisição disparado pelo usuário clicando em um botão de reprodução em um turno do assistente já concluído. Mapeamento de voz apropriado ao idioma: Sarah para es-419, Matilda para en-US, Bella para pt-BR.
  • TTS streaming sempre ativo: áudio empurrado pelo servidor a cada token do assistente. Rejeitada: cada turno transmitido consome caracteres quer o usuário escute ou não, estourando a cota da camada gratuita sob carga leve de demo e adicionando latência ao stream de eventos SSE.
  • API SpeechSynthesis nativa do navegador: sem dependência de fornecedor, mas a qualidade de voz é inconsistente entre navegadores e SOs; o contexto de saúde exige uma clareza que o TTS nativo não pode garantir.
  • ElevenLabs STT por WebSocket em tempo real (escolhida): um WebSocket persistente transmite o áudio do microfone ao ElevenLabs e retorna transcrições incrementais, do mesmo fornecedor, uma chave de API, superfície de faturamento consistente.
  • ElevenLabs STT em lote (encaminhamento de blob): gravar o enunciado completo, fazer POST do blob, receber uma transcrição. Mais simples, mas adiciona latência de fim de enunciado e não exercita o caminho de voz por streaming; retido como um modo de transcrição de fallback.
  • API Web Speech nativa do navegador: sem dependência de fornecedor, mas a qualidade é baixa demais para enunciados de contexto de saúde (terminologia médica, nomes de medicamentos, espanhol/português com sotaque). Erros na transcrição se propagariam para o pipeline de RAG e produziriam respostas inseguras.
  • OpenAI Whisper (API): alta qualidade, mas adiciona uma segunda dependência de fornecedor e uma segunda superfície de faturamento. A demo já exibe ElevenLabs para TTS; usar um provedor diferente para STT fragmenta a narrativa.
  • Direito de nível por chave + consentimento registrado no servidor (escolhida): a voz desbloqueia apenas em chaves provisionadas com o direito de voz (os níveis estendido e revisor); o servidor registra o consentimento por chave e retorna um 403 consent_required até que seja concedido, e um 403 voice_not_entitled para chaves sem o direito.
  • Um interruptor universal no lado do cliente: expor a voz a todo visitante via um interruptor de UI persistido em localStorage. Rejeitada: transforma uma capacidade paga e sensível a consentimento em um padrão gratuito controlado pelo cliente, não pode ser imposta no servidor, e coloca áudio sintetizado a um clique de distância para visitantes sem direito e sem consentimento.
  • Simulação de agente de voz full-duplex via ElevenLabs Conversational AI: áudio bidirecional em tempo real. Adiada: o modelo de custo é por minuto de tempo conectado, a complexidade de implementação é substancial, e o padrão de clicar-para-reproduzir + push-to-talk cobre a prova de conceito de agnosticismo de canal. Documentada como uma extensão opcional futura.
  • Nenhuma voz: rejeitada. Uma superfície apenas de texto não exercita a modalidade de voz que fluxos de trabalho regulados exigem.

Opção escolhida: extensão de voz ElevenLabs com TTS sob demanda (eleven_multilingual_v2, clicar-para-reproduzir), STT por WebSocket em tempo real, desbloqueada como um direito de nível por chave atrás de consentimento registrado no servidor, com voz conversacional full-duplex adiada para um marco futuro.

TTS: ElevenLabs eleven_multilingual_v2, sob demanda

Seção intitulada “TTS: ElevenLabs eleven_multilingual_v2, sob demanda”

Cada turno do assistente já concluído é renderizado em áudio apenas quando o usuário clica no botão de reprodução naquele turno. A requisição de TTS é uma chamada em segundo plano à API do ElevenLabs; a URL de áudio resultante (ou blob base64) é retornada ao aplicativo para reprodução. Nenhum áudio é gerado proativamente.

Mapeamento de voz por localidade:

LocalidadeID da vozNome
en-USXrExE9yKIg1WjnnlVkGXMatilda
es-419yoZ06kpGZMiJkInNR0GtSarah
pt-BRtiBZYpS5hJTFXbHm5CwKBella

O mapeamento é configurável e pode ser sobreposto pelo operador sem uma mudança de código.

O navegador captura o áudio do microfone (acesso solicitado pelo navegador) e o transmite por um WebSocket ao backend, que o repassa ao canal de fala-para-texto em tempo real do ElevenLabs e recebe transcrições incrementais. A transcrição final substitui o que teria sido a entrada digitada e entra no pipeline normal de /chat (recuperação de RAG, guardrails, geração de LLM). O áudio é processado em trânsito e nunca retido; apenas o texto transcrito é registrado, sujeito à mesma redação de guardrail que a entrada digitada (ADR-0005). O servidor limita a duração máxima de captura do STT; qualquer limite de gravação no lado do cliente é apenas um recurso de UX e nunca é usado como base confiável para faturamento. Um modo de transcrição em lote com encaminhamento de blob é retido como fallback.

Controle de acesso: direito por chave e consentimento registrado no servidor

Seção intitulada “Controle de acesso: direito por chave e consentimento registrado no servidor”

A voz é uma capacidade paga, não um padrão universal. Ela desbloqueia apenas em chaves de demo provisionadas com o direito de voz (os níveis estendido e revisor). Toda requisição de voz passa por um portão do lado do servidor antes que qualquer áudio seja servido ou transcrito: o servidor verifica primeiro o consentimento registrado (retornando 403 consent_required até que o titular da chave tenha consentido explicitamente, uma decisão registrada no lado do servidor e persistida entre sessões), depois o direito de voz (retornando 403 voice_not_entitled para chaves sem ele), depois o disjuntor de custo e uma sondagem de saúde do provedor, antes de transcrever ou sintetizar. O consentimento e o direito são impostos no servidor; nenhum dos dois é um sinalizador no lado do cliente que o navegador possa definir.

Transporte de metadados de áudio: sidecar SSE, não mutação de esquema

Seção intitulada “Transporte de metadados de áudio: sidecar SSE, não mutação de esquema”

O esquema JSON do ChatResponse está congelado. Os metadados de áudio do TTS (URL do áudio, duração, contagem de caracteres) são carregados em um tipo de evento SSE dedicado (voice_audio) emitido ao lado do evento de resposta existente. Consumidores que não optam por voz ignoram o tipo de evento inteiramente. A resposta JSON de /chat (quando o SSE não é negociado) permanece inalterada; o áudio só está disponível pelo canal SSE com voz habilitada.

Isso é consistente com o padrão da ADR-0009: novos tipos de evento estendem a superfície SSE sem mutar o contrato JSON base.

  • Camada gratuita do ElevenLabs: 10.000 caracteres/mês de TTS. O Scribe STT tem sua própria alocação de camada gratuita.
  • Para cargas de demo que excedam a camada gratuita, o operador complementa com uma chave paga do ElevenLabs. A demo baseline (apenas de texto) não é afetada.
  • Conforme a ADR-0010 e a ADR-0015, contagens de caracteres de TTS e segundos de STT são rastreados na tabela de uso de turnos de demo no Supabase ao lado de contagens de tokens de LLM. Isso habilita contabilidade de custo por sessão e por chave sem um sistema de faturamento separado.

O SDK Python do ElevenLabs (versão 2.49.0, já instalado no projeto) é a única nova dependência em tempo de execução. Nenhum SDK de fornecedor adicional é introduzido.

A voz está desligada por padrão. Uma chave sem o direito de voz nunca recebe os recursos de voz. Uma chave com o direito mantém a voz desligada até que seu titular conceda consentimento pelo portão de divulgação; até então, e sempre que a voz estiver desligada, nenhuma chamada de TTS ou STT é feita, nenhuma UI de áudio está ativa, e o chat se comporta de forma idêntica à build apenas de texto. Isso preserva o baseline apenas de texto para toda sessão que não desbloqueie e consinta explicitamente com a voz.

Um agente de voz em tempo real usando ElevenLabs Conversational AI (WebSocket full-duplex) é arquiteturalmente compatível com esta extensão, mas é adiado para um marco futuro. O TTS por clicar-para-reproduzir e o STT por push-to-talk fornecem evidência suficiente de processamento agnóstico de canal sem o custo e a complexidade de um WebSocket de áudio persistente.

  • Uma chave sem o direito de voz nunca recebe os recursos de voz; uma chave com o direito recebe voz apenas após consentimento registrado no servidor.
  • Uma requisição de voz sem consentimento registrado é rejeitada no lado do servidor com 403 consent_required; uma requisição de uma chave sem o direito é rejeitada com 403 voice_not_entitled.
  • Com a voz desbloqueada e consentida, cada turno do assistente mostra um botão de reprodução; clicá-lo dispara uma chamada de TTS e reproduz o áudio retornado.
  • Com a voz desbloqueada e consentida, um botão de microfone transmite áudio por um WebSocket ao backend para STT em tempo real; a transcrição entra no pipeline de /chat.
  • O esquema JSON do ChatResponse permanece inalterado quando o SSE não é negociado.
  • Consumidores SSE veem um novo tipo de evento voice_audio para TTS; consumidores que o ignoram não são afetados.
  • A tabela de uso de turnos de demo no Supabase registra caracteres de TTS e segundos de STT por turno.
  • Uma sessão que nunca desbloqueia e consente com a voz produz zero chamadas à API do ElevenLabs.
  • O SDK do ElevenLabs é a única nova dependência em tempo de execução.
  • Validação de agnosticismo de canal: a E/S de voz prova que o pipeline de processamento funciona de forma idêntica entre superfícies de texto e de áudio, não apenas em teoria, mas em uma demo executável.
  • Baseline preservado: a voz está desligada por padrão e condicionada por direito e consentimento; sessões que nunca desbloqueiam a voz não custam nada do lado do ElevenLabs.
  • Estabilidade de contrato: o esquema JSON de /chat fica intocado. O áudio trafega em um tipo de evento SSE separado, seguindo o padrão de extensão da ADR-0009.
  • Único fornecedor novo: o ElevenLabs trata tanto TTS quanto STT. Sem segundo fornecedor de voz, sem dependência do OpenAI para o Whisper.
  • Observabilidade de custo: caracteres de TTS e segundos de STT são rastreados na tabela de uso de turnos de demo existente no Supabase, habilitando relatório de custo por sessão ao lado dos custos de LLM.
  • Privacidade por concepção: o áudio é transmitido e processado em trânsito e nunca retido. Apenas o texto transcrito é registrado, sujeito à redação de guardrail (ADR-0005).
  • Pronto para o futuro: o padrão de TTS + STT sob demanda é um trampolim para o Conversational AI full-duplex sem uma reescrita arquitetural.
  • Teto da camada gratuita do ElevenLabs: 10.000 caracteres/mês de TTS são aproximadamente 2.000 palavras. Uso de demo sustentado (sessões de avaliadores, demonstrações em conferências) vai esgotá-lo. O operador precisa complementar com uma chave paga para períodos de alto tráfego.
  • Latência na primeira chamada de TTS: a requisição de TTS sob demanda adiciona 1-3 segundos de latência por turno. Isso é aceitável para clicar-para-reproduzir (o usuário espera uma espera) mas não seria aceitável para streaming sempre ativo.
  • Nova dependência em tempo de execução: o SDK do ElevenLabs adiciona à árvore de dependências. O SDK é bem mantido e a superfície de API é estreita (gerar TTS, transcrever STT), mas é mais um pacote a acompanhar para atualizações de segurança.
  • Permissão de microfone do navegador: o STT exige que o usuário conceda acesso ao microfone. Algumas redes corporativas e navegadores restringem isso; o fallback de entrada de texto está sempre disponível.
  • Um novo módulo de voz é adicionado para o cliente ElevenLabs, renderização de TTS e transcrição de STT.
  • Para uma chave com direito e consentida, o aplicativo ganha um botão de reprodução por turno e um botão de microfone para entrada. Quando a voz está desligada ou a chave não tem o direito, esses elementos de UI ficam ocultos.
  • O stream de eventos SSE ganha um tipo de evento voice_audio. Consumidores SSE existentes que não tratam esse tipo de evento não são afetados (compatíveis com o futuro por concepção).

ElevenLabs eleven_multilingual_v2, sob demanda (escolhida)

Seção intitulada “ElevenLabs eleven_multilingual_v2, sob demanda (escolhida)”
  • Bom, porque clicar-para-reproduzir gera áudio apenas quando consumido, mantendo o uso de caracteres de TTS proporcional às escutas reais.
  • Bom, porque eleven_multilingual_v2 trata en-US, es-419 e pt-BR com um único modelo, evitando roteamento de modelo por idioma.
  • Bom, porque o mapeamento de voz por idioma (Sarah, Matilda, Bella) fornece tom apropriado à localidade para o contexto de saúde.
  • Ruim, porque a camada gratuita (10K caracteres/mês) é fácil de esgotar sob carga de demo sustentada.
  • Ruim, porque a geração sob demanda adiciona 1-3 s de latência por ação de reprodução.
  • Bom, porque o usuário ouve o áudio imediatamente sem um clique extra.
  • Ruim, porque cada turno do assistente gera uma renderização completa de áudio quer o usuário escute ou não, multiplicando o consumo de caracteres pelo número de turnos.
  • Ruim, porque o áudio em streaming intercalado com o stream de eventos SSE aumenta o orçamento de latência e a complexidade do tratamento de eventos no lado do cliente.
  • Ruim, porque o modelo de custo é imprevisível sob carga de demo.
  • Bom, porque não há dependência de fornecedor nem cota.
  • Ruim, porque a qualidade de voz varia entre navegadores e SOs; a clareza do contexto de saúde não é garantida.
  • Ruim, porque não exercita a integração com o ElevenLabs, deixando a modalidade de voz não comprovada.

ElevenLabs STT por WebSocket em tempo real (escolhida)

Seção intitulada “ElevenLabs STT por WebSocket em tempo real (escolhida)”
  • Bom, porque é feito sob medida pelo mesmo fornecedor, uma chave de API, uma superfície de faturamento.
  • Bom, porque a transcrição por streaming mantém a latência baixa e exercita o caminho de voz em tempo real de ponta a ponta.
  • Bom, porque trata terminologia médica e entrada multilíngue (en-US, es-419, pt-BR) melhor do que alternativas nativas do navegador.
  • Ruim, porque exige uma chave de API do ElevenLabs e consome a cota de STT, e um WebSocket persistente tem mais partes móveis do que um único POST em lote.
  • Bom, porque não há dependência de fornecedor nem cota.
  • Ruim, porque a qualidade de transcrição é baixa demais para enunciados de saúde (nomes de medicamentos, condições, entrada multilíngue com sotaque).
  • Ruim, porque erros se propagam para o pipeline de RAG e podem produzir respostas inseguras ou sem sentido.
  • Bom, porque o Whisper é um modelo de STT conhecido e de alta qualidade.
  • Ruim, porque introduz uma segunda dependência de fornecedor (OpenAI) para uma única capacidade, fragmentando a superfície de faturamento e a narrativa.
  • Ruim, porque fragmenta a superfície de fornecedor de voz: a demo usa ElevenLabs para TTS, então o STT deve vir do mesmo provedor para manter a superfície de faturamento e a superfície de integração unificadas.

Agente de voz full-duplex (ElevenLabs Conversational AI)

Seção intitulada “Agente de voz full-duplex (ElevenLabs Conversational AI)”
  • Bom, porque é a demonstração mais impressionante das capacidades do ElevenLabs.
  • Bom, porque áudio bidirecional em tempo real é o padrão de nível de produção para agentes de voz.
  • Ruim, porque o modelo de custo é por minuto de tempo conectado, o que é mais difícil de controlar sob carga de demo.
  • Ruim, porque a complexidade de implementação (gerenciamento de WebSocket, tratamento de interrupção, VAD) é substancial para uma implementação de referência.
  • Ruim, porque o padrão de clicar-para-reproduzir + push-to-talk já prova processamento agnóstico de canal a custo e complexidade menores.
  • Bom, porque não adiciona nenhum custo, nenhuma dependência, nenhuma complexidade.
  • Ruim, porque uma superfície apenas de texto não exercita a modalidade de voz, deixando a afirmação de agnosticismo de canal sem suporte de evidência executável.

Direito por chave + consentimento registrado no servidor (escolhida)

Seção intitulada “Direito por chave + consentimento registrado no servidor (escolhida)”
  • Bom, porque uma capacidade paga e sensível a consentimento é condicionada no servidor, onde não pode ser contornada por um sinalizador do cliente.
  • Bom, porque o consentimento é registrado por chave e persiste entre sessões, de modo que o contexto de consentimento não é rediscutido a cada visita.
  • Ruim, porque adiciona estado de condicionamento no lado do servidor (consulta de direito, registro de consentimento) que um simples interruptor no cliente não precisaria.
  • Bom, porque é o recurso mais simples possível, sem estado no servidor.
  • Ruim, porque torna uma capacidade paga um padrão gratuito e não pode ser imposta no lado do servidor.
  • Ruim, porque coloca áudio sintetizado a um clique de distância para todo visitante, incluindo os sem direito e sem consentimento.