Skip to content

ADR-0013: Extensión de voz (ElevenLabs TTS + STT)

  • Estado: Accepted
  • Fecha: 2026-05-24
  • Responsables de la decisión: Waldemar Szemat

La demo es un agente de adherencia a la medicación agnóstico al canal que demuestra cómo construir sistemas basados en LLM para industrias reguladas con citación verificable, cumplimiento y transparencia de costos. Una superficie solo de texto no ejercita la modalidad de voz que los flujos de trabajo regulados (telesalud, líneas directas de pacientes, accesibilidad) requieren. Agregar entrada de voz (STT) y salida de voz (TTS) demuestra que la canalización de procesamiento del agente (recuperación, exigencia de citaciones, lógica de rechazo, auditoría) es genuinamente independiente del canal, no acoplada a una superficie solo de texto.

La extensión debe respetar las restricciones que fijan los ADR existentes:

  • Única instancia scale-to-zero de Google Cloud Run, base de cerca de $0 a escala de demo (ADR-0007); CPU Basic de Hugging Face Spaces como objetivo secundario genuinamente $0.
  • Proveedores de LLM OpenAI / Anthropic / Groq (ADR-0002).
  • El contrato de respuesta JSON de /chat está congelado; cualquier nueva superficie no debe romper a los consumidores existentes.
  • Capa gratuita de Supabase para datos operativos (ADR-0010, ADR-0015).

La capa de voz debe ser una extensión aditiva: desactivada por defecto, desbloqueada solo para claves con derecho y solo tras consentimiento explícito, con cero consumo de recursos hasta entonces, y limpiamente separable de la ruta solo de texto para que el agente funcione de forma idéntica sin ella.

¿Cómo agregamos E/S de voz a la demo de una manera que (a) demuestre que la canalización de procesamiento es agnóstica al canal, (b) mantenga la base en $0/mes, (c) no rompa el contrato congelado de /chat, y (d) rastree el costo contra el esquema de contabilidad existente de Supabase?

  • Prueba de agnosticismo al canal: la voz debe ser una superficie de primera clase, no un añadido posterior, para demostrar que la canalización de procesamiento es genuinamente independiente del canal.
  • Base de $0/mes: la demo solo de texto sigue siendo gratuita. El consumo de voz es opcional y se presupuesta por separado.
  • Estabilidad del contrato: el esquema JSON de /chat está congelado (ADR-0009). Los metadatos de voz deben viajar junto a ese esquema, no dentro de él.
  • Observabilidad de costos: según ADR-0010 y ADR-0015, cada unidad facturable (tokens de LLM, caracteres de TTS, segundos de STT) debe rastrearse en Supabase para la contabilidad de costos posterior a la demo.
  • Superficie mínima de vendors: el proyecto ya depende de OpenAI, Anthropic, Groq, Hugging Face y Supabase. La voz debería agregar un vendor (ElevenLabs), no dos.
  • Presupuesto de latencia: la única instancia scale-to-zero ya tiene un arranque en frío en la primera solicitud tras inactividad. La voz no debe agregar latencia no acotada a la experiencia de chat de ruta caliente.
  • Privacidad: el audio de conversaciones relacionadas con la salud es sensible. El audio en bruto no debe persistirse; solo se registra el texto transcrito (después de la redacción de las barreras de seguridad según ADR-0005).
  • Derecho y consentimiento: la voz es una capacidad de pago, no un valor predeterminado gratuito. Debe desbloquearse solo en claves aprovisionadas con el derecho de voz (los niveles extendido y de revisor), y cada turno de audio servido debe estar controlado detrás de consentimiento explícito registrado en el servidor.
  • ElevenLabs eleven_multilingual_v2, bajo demanda (elegida): TTS por solicitud activado cuando el usuario hace clic en un botón de reproducción en un turno del asistente completado. Mapeo de voz apropiado al idioma: Sarah para es-419, Matilda para en-US, Bella para pt-BR.
  • Transmisión de TTS siempre activa: audio empujado por el servidor en cada token del asistente. Rechazada: cada turno transmitido consume caracteres tanto si el usuario escucha como si no, agotando la cuota de la capa gratuita bajo una carga ligera de demo y agregando latencia al flujo de eventos SSE.
  • API SpeechSynthesis nativa del navegador: sin dependencia de vendor, pero la calidad de voz es inconsistente entre navegadores y sistemas operativos; el contexto de salud exige una claridad que el TTS nativo no puede garantizar.
  • STT WebSocket en tiempo real de ElevenLabs (elegida): un WebSocket persistente transmite el audio del micrófono a ElevenLabs y devuelve transcripciones incrementales, del mismo vendor, una clave de API, superficie de facturación consistente.
  • STT por lotes de ElevenLabs (reenvío de blob): grabar el enunciado completo, hacer POST del blob, recibir una transcripción. Más simple, pero agrega latencia de fin de enunciado y no ejercita la ruta de voz en streaming; se conserva como un modo de transcripción de respaldo.
  • API Web Speech nativa del navegador: sin dependencia de vendor, pero la calidad es demasiado baja para enunciados de contexto de salud (terminología médica, nombres de medicamentos, español/portugués con acento). Los errores en la transcripción se propagarían a la canalización de RAG y producirían respuestas inseguras.
  • OpenAI Whisper (API): alta calidad, pero agrega una segunda dependencia de vendor y una segunda superficie de facturación. La demo ya muestra ElevenLabs para TTS; usar un proveedor diferente para STT fragmenta la narrativa.
  • Derecho de nivel por clave + consentimiento registrado en el servidor (elegida): la voz se desbloquea solo en claves aprovisionadas con el derecho de voz (los niveles extendido y de revisor); el servidor registra el consentimiento por clave y devuelve un 403 consent_required hasta que se otorga, y un 403 voice_not_entitled para las claves sin el derecho.
  • Un interruptor universal del lado del cliente: exponer la voz a cada visitante mediante un interruptor de interfaz persistido en localStorage. Rechazada: convierte una capacidad de pago y sensible al consentimiento en un valor predeterminado gratuito controlado por el cliente, no puede aplicarse en el servidor, y pone el audio sintetizado a un clic de distancia para visitantes sin derecho y sin consentimiento.
  • Simulación de agente de voz full-duplex mediante ElevenLabs Conversational AI: audio bidireccional en tiempo real. Aplazada: el modelo de costo es por minuto de tiempo conectado, la complejidad de implementación es sustancial, y el patrón de clic-para-reproducir + pulsar-para-hablar cubre la prueba de concepto de agnosticismo al canal. Documentada como una extensión opcional futura.
  • Sin voz en absoluto: rechazada. Una superficie solo de texto no ejercita la modalidad de voz que los flujos de trabajo regulados exigen.

Opción elegida: extensión de voz de ElevenLabs con TTS bajo demanda (eleven_multilingual_v2, clic-para-reproducir), STT WebSocket en tiempo real, desbloqueada como un derecho de nivel por clave detrás de consentimiento registrado en el servidor, con voz conversacional full-duplex aplazada a un hito futuro.

TTS: ElevenLabs eleven_multilingual_v2, bajo demanda

Section titled “TTS: ElevenLabs eleven_multilingual_v2, bajo demanda”

Cada turno del asistente completado se renderiza en audio solo cuando el usuario hace clic en el botón de reproducción en ese turno. La solicitud de TTS es una llamada en segundo plano a la API de ElevenLabs; la URL de audio resultante (o el blob en base64) se devuelve a la aplicación para su reproducción. No se genera audio de forma proactiva.

Mapeo de voz por configuración regional:

Configuración regionalID de vozNombre
en-USXrExE9yKIg1WjnnlVkGXMatilda
es-419yoZ06kpGZMiJkInNR0GtSarah
pt-BRtiBZYpS5hJTFXbHm5CwKBella

El mapeo es configurable y el operador puede anularlo sin un cambio de código.

STT: WebSocket en tiempo real de ElevenLabs

Section titled “STT: WebSocket en tiempo real de ElevenLabs”

El navegador captura el audio del micrófono (el acceso lo solicita el navegador) y lo transmite sobre un WebSocket al backend, que lo retransmite al canal de voz a texto en tiempo real de ElevenLabs y recibe transcripciones incrementales. La transcripción final reemplaza lo que habría sido la entrada escrita y entra a la canalización normal de /chat (recuperación de RAG, barreras de seguridad, generación con LLM). El audio se procesa en tránsito y nunca se retiene; solo se registra el texto transcrito, sujeto a la misma redacción de barreras de seguridad que la entrada escrita (ADR-0005). El servidor limita la duración máxima de retención de STT; cualquier tope de grabación del lado del cliente es solo una comodidad de UX y nunca se confía en él para la facturación. Se conserva un modo de transcripción por lotes con reenvío de blob como respaldo.

Control de acceso: derecho por clave y consentimiento registrado en el servidor

Section titled “Control de acceso: derecho por clave y consentimiento registrado en el servidor”

La voz es una capacidad de pago, no un valor predeterminado universal. Se desbloquea solo en claves de demo aprovisionadas con el derecho de voz (los niveles extendido y de revisor). Cada solicitud de voz pasa por un control del lado del servidor antes de que se sirva o transcriba cualquier audio: el servidor verifica primero el consentimiento registrado (devolviendo 403 consent_required hasta que el titular de la clave ha consentido explícitamente, una decisión registrada del lado del servidor y persistida entre sesiones), luego el derecho de voz (devolviendo 403 voice_not_entitled para las claves sin él), luego el cortacircuitos de costos y una sonda de salud del proveedor, antes de transcribir o sintetizar. El consentimiento y el derecho se aplican en el servidor; ninguno es una bandera del lado del cliente que el navegador pueda establecer.

Transporte de metadatos de audio: complemento SSE, no mutación de esquema

Section titled “Transporte de metadatos de audio: complemento SSE, no mutación de esquema”

El esquema JSON de ChatResponse está congelado. Los metadatos de audio de TTS (URL de audio, duración, conteo de caracteres) se transportan en un tipo de evento SSE dedicado (voice_audio) emitido junto al evento de respuesta existente. Los consumidores que no optan por la voz ignoran por completo el tipo de evento. La respuesta JSON de /chat (cuando no se negocia SSE) permanece sin cambios; el audio solo está disponible sobre el canal SSE con la voz habilitada.

Esto es consistente con el patrón de ADR-0009: los nuevos tipos de evento extienden la superficie SSE sin mutar el contrato JSON base.

  • Capa gratuita de ElevenLabs: 10,000 caracteres/mes de TTS. El STT Scribe tiene su propia asignación de capa gratuita.
  • Para cargas de demo que excedan la capa gratuita, el operador la complementa con una clave de pago de ElevenLabs. La demo base (solo de texto) no se ve afectada.
  • Según ADR-0010 y ADR-0015, los conteos de caracteres de TTS y los segundos de STT se rastrean en la tabla de uso de turnos de demo en Supabase junto a los conteos de tokens de LLM. Esto habilita la contabilidad de costos por sesión y por clave sin un sistema de facturación separado.

El SDK de Python de ElevenLabs (versión 2.49.0, ya instalado en el proyecto) es la única nueva dependencia en tiempo de ejecución. No se introducen SDK de vendor adicionales.

La voz está desactivada por defecto. A una clave sin el derecho de voz nunca se le ofrecen las comodidades de voz en absoluto. Una clave con derecho mantiene la voz desactivada hasta que su titular otorga el consentimiento a través del control de divulgación; hasta entonces, y siempre que la voz esté desactivada, no se hacen llamadas de TTS ni de STT, no hay interfaz de audio activa, y el chat se comporta de forma idéntica a la compilación solo de texto. Esto preserva la base solo de texto para cada sesión que no desbloquea y consiente explícitamente la voz.

Un agente de voz en tiempo real que use ElevenLabs Conversational AI (WebSocket full-duplex) es arquitectónicamente compatible con esta extensión pero se aplaza a un hito futuro. El TTS clic-para-reproducir y el STT pulsar-para-hablar proporcionan evidencia suficiente de procesamiento agnóstico al canal sin el costo y la complejidad de un WebSocket de audio persistente.

  • A una clave sin el derecho de voz nunca se le ofrecen las comodidades de voz; a una clave con derecho se le ofrece la voz solo tras consentimiento registrado en el servidor.
  • Una solicitud de voz sin consentimiento registrado se rechaza del lado del servidor con 403 consent_required; una solicitud de una clave sin derecho se rechaza con 403 voice_not_entitled.
  • Con la voz desbloqueada y consentida, cada turno del asistente muestra un botón de reproducción; hacer clic en él activa una llamada de TTS y reproduce el audio devuelto.
  • Con la voz desbloqueada y consentida, un botón de micrófono transmite audio sobre un WebSocket al backend para STT en tiempo real; la transcripción entra a la canalización de /chat.
  • El esquema JSON de ChatResponse no cambia cuando no se negocia SSE.
  • Los consumidores de SSE ven un nuevo tipo de evento voice_audio para TTS; los consumidores que lo ignoran no se ven afectados.
  • La tabla de uso de turnos de demo en Supabase registra los caracteres de TTS y los segundos de STT por turno.
  • Una sesión que nunca desbloquea ni consiente la voz produce cero llamadas a la API de ElevenLabs.
  • El SDK de ElevenLabs es la única nueva dependencia en tiempo de ejecución.
  • Validación de agnosticismo al canal: la E/S de voz demuestra que la canalización de procesamiento funciona de forma idéntica a través de superficies de texto y de audio, no solo en teoría sino en una demo ejecutable.
  • Base preservada: la voz está desactivada por defecto y controlada por derecho y consentimiento; las sesiones que nunca desbloquean la voz no cuestan nada del lado de ElevenLabs.
  • Estabilidad del contrato: el esquema JSON de /chat queda intacto. El audio viaja en un tipo de evento SSE separado, siguiendo el patrón de extensión de ADR-0009.
  • Un único vendor nuevo: ElevenLabs maneja tanto TTS como STT. Sin un segundo vendor de voz, sin dependencia de OpenAI para Whisper.
  • Observabilidad de costos: los caracteres de TTS y los segundos de STT se rastrean en la tabla existente de uso de turnos de demo en Supabase, habilitando el reporte de costos por sesión junto a los costos de LLM.
  • Privacidad por diseño: el audio se transmite y se procesa en tránsito y nunca se retiene. Solo el texto transcrito se registra, sujeto a la redacción de las barreras de seguridad (ADR-0005).
  • Listo para el futuro: el patrón de TTS + STT bajo demanda es un paso intermedio hacia la Conversational AI full-duplex sin una reescritura arquitectónica.
  • Techo de la capa gratuita de ElevenLabs: 10,000 caracteres/mes de TTS son aproximadamente 2,000 palabras. El uso sostenido de la demo (sesiones de evaluadores, demostraciones en conferencias) lo agotará. El operador debe complementarlo con una clave de pago para períodos de alto tráfico.
  • Latencia en la primera llamada de TTS: la solicitud de TTS bajo demanda agrega 1-3 segundos de latencia por turno. Esto es aceptable para clic-para-reproducir (el usuario espera una pausa) pero no sería aceptable para la transmisión siempre activa.
  • Nueva dependencia en tiempo de ejecución: el SDK de ElevenLabs se suma al árbol de dependencias. El SDK está bien mantenido y la superficie de la API es estrecha (generar TTS, transcribir STT), pero es un paquete más que rastrear para actualizaciones de seguridad.
  • Permiso de micrófono del navegador: el STT requiere que el usuario conceda acceso al micrófono. Algunas redes corporativas y navegadores lo restringen; el respaldo de entrada de texto siempre está disponible.
  • Se agrega un nuevo módulo de voz para el cliente de ElevenLabs, el renderizado de TTS y la transcripción STT.
  • Para una clave con derecho y consentida, la aplicación gana un botón de reproducción por turno y un botón de micrófono para la entrada. Cuando la voz está desactivada o la clave no tiene derecho, estos elementos de interfaz están ocultos.
  • El flujo de eventos SSE gana un tipo de evento voice_audio. Los consumidores de SSE existentes que no manejan este tipo de evento no se ven afectados (compatible hacia adelante por diseño).

ElevenLabs eleven_multilingual_v2, bajo demanda (elegida)

Section titled “ElevenLabs eleven_multilingual_v2, bajo demanda (elegida)”
  • Buena, porque clic-para-reproducir genera audio solo cuando se consume, manteniendo el uso de caracteres de TTS proporcional a las escuchas reales.
  • Buena, porque eleven_multilingual_v2 maneja en-US, es-419 y pt-BR con un único modelo, evitando el enrutamiento de modelos por idioma.
  • Buena, porque el mapeo de voz por idioma (Sarah, Matilda, Bella) proporciona un tono apropiado a la configuración regional para el contexto de salud.
  • Mala, porque la capa gratuita (10K caracteres/mes) es fácil de agotar bajo una carga de demo sostenida.
  • Mala, porque la generación bajo demanda agrega 1-3 s de latencia por acción de reproducción.
  • Buena, porque el usuario escucha el audio de inmediato sin un clic adicional.
  • Mala, porque cada turno del asistente genera un renderizado de audio completo tanto si el usuario escucha como si no, multiplicando el consumo de caracteres por el número de turnos.
  • Mala, porque la transmisión de audio intercalada con el flujo de eventos SSE aumenta el presupuesto de latencia y la complejidad del manejo de eventos del lado del cliente.
  • Mala, porque el modelo de costo es impredecible bajo carga de demo.
  • Buena, porque no hay dependencia de vendor ni cuota.
  • Mala, porque la calidad de voz varía entre navegadores y sistemas operativos; la claridad del contexto de salud no está garantizada.
  • Mala, porque no ejercita la integración con ElevenLabs, dejando la modalidad de voz sin demostrar.

STT WebSocket en tiempo real de ElevenLabs (elegida)

Section titled “STT WebSocket en tiempo real de ElevenLabs (elegida)”
  • Buena, porque está diseñado para el propósito por el mismo vendor, una clave de API, una superficie de facturación.
  • Buena, porque la transcripción en streaming mantiene baja la latencia y ejercita la ruta de voz en tiempo real de extremo a extremo.
  • Buena, porque maneja la terminología médica y la entrada multilingüe (en-US, es-419, pt-BR) mejor que las alternativas nativas del navegador.
  • Mala, porque requiere una clave de API de ElevenLabs y consume la cuota de STT, y un WebSocket persistente es más piezas móviles que un único POST por lotes.

API Web Speech nativa del navegador para STT

Section titled “API Web Speech nativa del navegador para STT”
  • Buena, porque no hay dependencia de vendor ni cuota.
  • Mala, porque la calidad de transcripción es demasiado baja para los enunciados de salud (nombres de medicamentos, condiciones, entrada multilingüe con acento).
  • Mala, porque los errores se propagan a la canalización de RAG y pueden producir respuestas inseguras o sin sentido.
  • Buena, porque Whisper es un modelo de STT bien conocido y de alta calidad.
  • Mala, porque introduce una segunda dependencia de vendor (OpenAI) para una única capacidad, fragmentando la superficie de facturación y la narrativa.
  • Mala, porque fragmenta la superficie de vendor de voz: la demo usa ElevenLabs para TTS, por lo que el STT debería venir del mismo proveedor para mantener unificadas la superficie de facturación y la superficie de integración.

Agente de voz full-duplex (ElevenLabs Conversational AI)

Section titled “Agente de voz full-duplex (ElevenLabs Conversational AI)”
  • Buena, porque es la demostración más impresionante de las capacidades de ElevenLabs.
  • Buena, porque el audio bidireccional en tiempo real es el patrón de grado de producción para agentes de voz.
  • Mala, porque el modelo de costo es por minuto de tiempo conectado, que es más difícil de controlar bajo carga de demo.
  • Mala, porque la complejidad de implementación (gestión de WebSocket, manejo de interrupciones, VAD) es sustancial para una implementación de referencia.
  • Mala, porque el patrón de clic-para-reproducir + pulsar-para-hablar ya demuestra el procesamiento agnóstico al canal a menor costo y complejidad.
  • Buena, porque no agrega costo, ni dependencia, ni complejidad.
  • Mala, porque una superficie solo de texto no ejercita la modalidad de voz, dejando la afirmación de agnosticismo al canal sin respaldo de evidencia ejecutable.

Derecho por clave + consentimiento registrado en el servidor (elegida)

Section titled “Derecho por clave + consentimiento registrado en el servidor (elegida)”
  • Buena, porque una capacidad de pago y sensible al consentimiento se controla en el servidor, donde no puede eludirse con una bandera del cliente.
  • Buena, porque el consentimiento se registra por clave y persiste entre sesiones, de modo que el contexto de consentimiento no se vuelve a litigar en cada visita.
  • Mala, porque agrega estado de control del lado del servidor (búsqueda de derecho, registro de consentimiento) que un simple interruptor del cliente no necesitaría.

Un interruptor universal del lado del cliente

Section titled “Un interruptor universal del lado del cliente”
  • Buena, porque es la comodidad más simple posible, sin estado del servidor.
  • Mala, porque convierte una capacidad de pago en un valor predeterminado gratuito y no puede aplicarse del lado del servidor.
  • Mala, porque pone el audio sintetizado a un clic de distancia para cada visitante, incluidos los que no tienen derecho ni consentimiento.