ADR-0013: Extensão de voz (ElevenLabs TTS + STT)
- Status: Accepted
- Data: 2026-05-24
- Responsáveis pela decisão: Waldemar Szemat
Contexto e definição do problema
Seção intitulada “Contexto e definição do problema”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
/chatestá 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?
Fatores da decisão
Seção intitulada “Fatores da decisão”- 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
/chatestá 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.
Opções consideradas
Seção intitulada “Opções consideradas”TTS (text-to-speech)
Seção intitulada “TTS (text-to-speech)”- 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
SpeechSynthesisnativa 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.
STT (speech-to-text)
Seção intitulada “STT (speech-to-text)”- 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.
Controle de acesso e consentimento
Seção intitulada “Controle de acesso e consentimento”- 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_requiredaté que seja concedido, e um 403voice_not_entitledpara 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.
Agente de voz (full-duplex)
Seção intitulada “Agente de voz (full-duplex)”- 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.
Resultado da decisão
Seção intitulada “Resultado da decisão”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:
| Localidade | ID da voz | Nome |
|---|---|---|
| en-US | XrExE9yKIg1WjnnlVkGX | Matilda |
| es-419 | yoZ06kpGZMiJkInNR0Gt | Sarah |
| pt-BR | tiBZYpS5hJTFXbHm5CwK | Bella |
O mapeamento é configurável e pode ser sobreposto pelo operador sem uma mudança de código.
STT: ElevenLabs por WebSocket em tempo real
Seção intitulada “STT: ElevenLabs por WebSocket em tempo real”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.
Modelo de custo e rastreamento
Seção intitulada “Modelo de custo e rastreamento”- 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.
Dependência: SDK do ElevenLabs
Seção intitulada “Dependência: SDK do ElevenLabs”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.
Padrões da UI
Seção intitulada “Padrões da UI”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.
Agente de voz full-duplex: adiado
Seção intitulada “Agente de voz full-duplex: adiado”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.
Confirmação
Seção intitulada “Confirmação”- 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 403voice_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
ChatResponsepermanece inalterado quando o SSE não é negociado. - Consumidores SSE veem um novo tipo de evento
voice_audiopara 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.
Consequências
Seção intitulada “Consequências”Positivas
Seção intitulada “Positivas”- 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
/chatfica 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.
Negativas
Seção intitulada “Negativas”- 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.
Neutras
Seção intitulada “Neutras”- 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).
Prós e contras das opções
Seção intitulada “Prós e contras das opções”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_v2trata 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.
TTS streaming sempre ativo
Seção intitulada “TTS streaming sempre ativo”- 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.
SpeechSynthesis nativo do navegador
Seção intitulada “SpeechSynthesis nativo do navegador”- 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.
API Web Speech nativa do navegador para STT
Seção intitulada “API Web Speech nativa do navegador para STT”- 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.
OpenAI Whisper (API) para STT
Seção intitulada “OpenAI Whisper (API) para STT”- 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.
Nenhuma voz
Seção intitulada “Nenhuma voz”- 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.
Um interruptor universal no lado do cliente
Seção intitulada “Um interruptor universal no lado do cliente”- 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.
Mais informações
Seção intitulada “Mais informações”- Abstração de fornecedor de LLM: ADR-0002
- Alvo de implantação e restrição de $0/mês: ADR-0007
- Arquitetura de streaming e padrão de extensão SSE: ADR-0009
- Camada de dados e rastreamento de custo (Supabase): ADR-0010
- Melhoria contínua e armazenamento de uso: ADR-0015
- Guardrails e redação de privacidade: ADR-0005
- Documentação da API do ElevenLabs: https://elevenlabs.io/docs/api-reference
- Modelo
eleven_multilingual_v2do ElevenLabs: https://elevenlabs.io/docs/speech-synthesis/models - ElevenLabs Scribe STT: https://elevenlabs.io/docs/capabilities/speech-to-text
- Preços e limites de camada gratuita do ElevenLabs: https://elevenlabs.io/pricing
- MADR 4.0.0: https://adr.github.io/madr/