Ir al contenido

Integración de la API de Texto a Voz: streaming, procesamiento por lotes y reintentos

Publicado
Última actualización

EscucharEscucha este artículo

Integrar una API de Texto a Voz es sencillo… una vez que hayas tomado algunas decisiones concretas: qué modo de transferencia usar, cómo elegir un modelo y un formato de salida, cómo hacer streaming, cómo gestionar grandes volúmenes sin superar tu límite de simultaneidad, cómo almacenar en caché y reintentar para no pagar nunca dos veces por generar el mismo audio y cómo comparar el tiempo hasta el primer byte con otro proveedor.

Para ayudarte a integrar una API de Texto a Voz, hemos desglosado cada una de estas decisiones de arquitectura y qué hacer en cada caso. Esta guía te ayudará a integrar la API de Texto a Voz de ElevenLabs y escalar, con fragmentos de código que puedes pegar directamente en producción para empezar a trabajar.

Para conocer en profundidad los conceptos mencionados aquí, consulta nuestras guías sobre cómo entender el streaming de audio, cómo optimizar la latencia y la visión general de los modelos de ElevenLabs

Resumen

  • Hay una única ruta de API de Texto a Voz de ElevenLabs, a la que puedes acceder de tres formas: conversión por lotes, streaming por HTTP y WebSocket stream-input.
  • En HTTP, cada solicitud en curso cuenta para tu límite de simultaneidad, mientras que en WebSocket solo cuenta la generación activa.
  • Limita el paralelismo justo por debajo del límite de tu plan y guarda en caché un hash de cada parámetro que afecte a la salida para no facturar nunca dos veces el mismo texto.
  • Reintenta los errores 429 y 5xx con espera exponencial y jitter completo para reducir el ritmo antes de alcanzar el límite de simultaneidad.

Tres formas de integrar la API de Texto a Voz

Hay una única ruta de Texto a Voz, pero cómo la integres determinará la latencia, complejidad y coste. 

La misma llamada POST /v1/text-to-speech/{voice_id} funciona de tres formas, cada una adecuada para una tarea ligeramente distinta. Estas son las tres maneras de integrar la API de Texto a Voz:

  • La conversión por lotes (convert) es la integración más sencilla: envías una solicitud y recibes una respuesta de audio. Es la opción menos compleja y la que tiene el mayor tiempo hasta el primer audio, porque se sintetiza el clip completo antes de que recibas ningún byte.
  • El streaming HTTP (stream) mantiene la misma solicitud, pero divide la respuesta en fragmentos: añades /stream a la ruta, llamas al método stream y el audio vuelve como una respuesta fragmentada. El código es prácticamente idéntico y la latencia percibida es mucho menor.
  • El WebSocket (stream-input) mantiene una conexión persistente: envías texto de forma incremental y recibes fragmentos de audio conforme se generan. Está diseñado para agentes interactivos y para enviar la salida de un LLM a la síntesis de voz a medida que se producen los tokens, antes de terminar la frase.

El streaming no hace que el modelo genere audio más rápido; el tiempo de inferencia no cambia. Lo que cambia es cuándo recibes el primer fragmento: se envía antes de que termine el clip completo, por lo que la espera que percibe el usuario es menor aunque el trabajo total sea el mismo.

Tabla de decisión: lotes frente a streaming frente a WebSocket

Al decidir entre estos tres métodos, debes tener en cuenta varios factores.

Como guía rápida: elige lotes para la generación sin conexión, streaming HTTP para texto conocido que un usuario está esperando y WebSocket para agentes y conversión de LLM a voz en directo. 

La siguiente tabla desglosa las ventajas e inconvenientes en las dimensiones relevantes a escala.

Batch (convert)
Time-to-first-audio
Highest (wait for full clip)
Implementation complexity
Lowest
Text known up front?
Required
Streaming LLM output into TTS
Awkward
Concurrency cost
Each request counts fully
Best for
Offline rendering, audiobooks, caching
HTTP streaming
Time-to-first-audio
Low
Implementation complexity
Low
Text known up front?
Required
Streaming LLM output into TTS
Awkward
Concurrency cost
Each request counts fully
Best for
Web/app playback of known text
WebSocket (stream-input)
Time-to-first-audio
Lowest
Implementation complexity
Highest (connection lifecycle, framing)
Text known up front?
Not required - send incrementally
Streaming LLM output into TTS
Native fit
Concurrency cost
Only active generation counts
Best for
Voice agents, live LLM to speech

En HTTP, tanto en lotes como en streaming, cada solicitud en curso cuenta para el límite de simultaneidad de tu plan durante toda su duración. En WebSocket, solo cuenta el tiempo en que el modelo genera audio activamente; un socket abierto pero inactivo prácticamente no tiene coste.

Para un agente de voz en cascada que mantiene una conexión abierta durante toda una conversación, pero solo genera audio durante los turnos del agente, la diferencia es considerable. Es la razón principal para usar WebSockets al crear agentes. El protocolo completo está documentado en la guía de WebSocket de Texto a Voz en tiempo real.

Elegir un modelo y un formato de salida

Dos decisiones determinan el audio que recibes de la integración de tu API de TTS. La primera es el modelo, que define la calidad y la velocidad. La segunda es el formato de salida, que define el contenedor, la tasa de bits y la frecuencia de muestreo.

Acertar con ambas desde el principio hará que todo lo posterior, como la latencia y la compatibilidad con telefonía, encaje correctamente.

Modelos

Ofrecemos varios modelos de Texto a Voz. No están ordenados de mejor a peor; cada uno plantea ventajas e inconvenientes distintos.

Best for
eleven_flash_v2_5
Real-time, agents, bulk throughput (~75ms model inference)
eleven_flash_v2
Real-time, English only (~75ms)
eleven_multilingual_v2
Highest stable fidelity, narration
eleven_v3
Most expressive, widest language range
Languages
eleven_flash_v2_5
32
eleven_flash_v2
English
eleven_multilingual_v2
29
eleven_v3
70+
Character limit
eleven_flash_v2_5
40,000
eleven_flash_v2
30,000
eleven_multilingual_v2
10,000
eleven_v3
5,000

Como referencia, la cifra de ~75 ms corresponde a la inferencia del modelo en condiciones representativas y excluye la latencia de red y de la aplicación. Aumenta con entradas más largas y bajo carga. Mide siempre desde tu aplicación, no basándote en una cifra de referencia.

Los modelos Flash son más pequeños y usan aproximaciones más agresivas para reducir el tiempo de inferencia. Eleven v3 y Multilingual v2 son modelos más grandes que dedican más tiempo por carácter a producir una salida más rica. No hay ningún ajuste que ofrezca la calidad de Eleven v3 a la velocidad de Flash, porque esa calidad requiere computación adicional.

Para una ruta en tiempo real o para agentes, utiliza eleven_flash_v2_5; es la opción multilingüe de menor latencia. Para narración, audiolibros o locuciones de marketing, usa eleven_multilingual_v2 si buscas alta fidelidad estable, o eleven_v3 si necesitas la máxima expresividad y rango emocional. 

Cuando la pronunciación importa, por ejemplo en números de teléfono, fechas o importes, normaliza los números en tu aplicación antes de que el texto llegue a la API. Escribe la forma hablada que quieras obtener. 

Normalizarlo por tu cuenta permite que la pronunciación sea predecible en todos los modelos y evita depender de valores predeterminados específicos de cada modelo que podrían cambiar.

Formato de salida

El parámetro output_format controla el contenedor, la frecuencia de muestreo y la tasa de bits del audio que recibes. Estos son los valores que usarás con más frecuencia:

Use case
mp3_44100_128
General playback, downloads, highest mp3 quality shown here
mp3_22050_32
Lower-bandwidth playback, smaller files
pcm_24000 / pcm_16000
Raw PCM for your own audio pipeline or further processing
ulaw_8000
Telephony - the format used with Twilio and similar systems
Languages
mp3_44100_128
32
mp3_22050_32
English
pcm_24000 / pcm_16000
29
ulaw_8000
70+
Character limit
mp3_44100_128
40,000
mp3_22050_32
30,000
pcm_24000 / pcm_16000
10,000
ulaw_8000
5,000

Ajustes de voz

Los siguientes ajustes controlan cómo se reproduce la voz generada:

  • Stability: controla el equilibrio entre consistencia y expresividad. Los valores más bajos producen una voz más variada y expresiva, mientras que los más altos ofrecen una entonación más estable y predecible.
  • SimilarityBoost: controla hasta qué punto la salida se ajusta a la voz de referencia.
  • Style: exagera el estilo natural de habla de la voz al aumentarlo.
  • useSpeakerBoost: mejora la similitud con el hablante original a costa de un pequeño aumento de la latencia.
  • Speed: ajusta el ritmo de la entonación respecto al valor predeterminado de 1.0.

De estos ajustes, Stability suele tener el mayor impacto en la calidad percibida. Los valores más bajos crean una salida más expresiva, pero menos consistente, mientras que los valores más altos priorizan la consistencia y la previsibilidad.

Al elegir una voz, la combinación de menor latencia es Flash con una Clonación de Voz instantánea o una voz predeterminada; las clonaciones de voz profesionales suenan excelentes, pero añaden una sobrecarga por generación que debes tener en cuenta.

A lo largo de esta guía, el ID de voz de ejemplo es JBFqnCBsd6RMkjVDRZzb (George).

Integración de streaming (HTTP y WebSocket)

En esta sección abordamos la parte práctica de la integración de la API de Texto a Voz. Veremos cómo instalar el SDK, abrir un stream y consumir el audio conforme llega. La ruta HTTP cubre la mayoría de los casos de reproducción web y en aplicaciones, mientras que la ruta WebSocket cubre agentes y salida de LLM en directo.

Ambas rutas asumen que has inicializado el cliente de ElevenLabs como se muestra a continuación.

npm install @elevenlabs/elevenlabs-js
import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";

const elevenlabs = new ElevenLabsClient({
  apiKey: process.env.ELEVENLABS_API_KEY,
});

La ruta de streaming abre un stream y consume los fragmentos conforme llegan. voiceId es el primer argumento posicional, seguido de un objeto de opciones con claves en camelCase (modelId, outputFormat, voiceSettings):

const stream = await elevenlabs.textToSpeech.stream("JBFqnCBsd6RMkjVDRZzb", {
  text,
  modelId: "eleven_flash_v2_5",
  outputFormat: "mp3_44100_128",
  voiceSettings: { stability: 0, similarityBoost: 1.0, style: 0, useSpeakerBoost: true, speed: 1.0 },
});

for await (const chunk of stream) {
  // chunk is a Buffer; feed it to the player as it arrives
}

Para la variante WebSocket, conéctate a wss://api.elevenlabs.io/v1/text-to-speech/{voice_id}/stream-input, envía un primer mensaje con los ajustes de voz y un espacio inicial, después envía mensajes de texto conforme estén disponibles y lee los frames JSON devueltos, cuyo campo audio contiene fragmentos codificados en base64.

Procesamiento por lotes y límites de simultaneidad para alto rendimiento

La integración de alto rendimiento está determinada por la simultaneidad, es decir, el número de solicitudes que generan audio en el mismo instante. Cada plan tiene un límite por familia de modelos. 

Cada plan incluye un límite de simultaneidad distinto:

  • Free: 4 solicitudes Flash simultáneas.
  • Starter: 6 solicitudes Flash simultáneas.
  • Creator: 10 solicitudes Flash simultáneas.
  • Pro: 20 solicitudes Flash simultáneas.
  • Scale y Business: 30 solicitudes Flash simultáneas; los límites de Enterprise son personalizados.

Los límites de Multilingual v2 son aproximadamente la mitad de los anteriores.

Un grupo con límite reduce este problema al restringir cuántas solicitudes se ejecutan a la vez:

// Set MAX_CONCURRENCY at or below your plan's Flash concurrency limit.
const MAX_CONCURRENCY = 8;

async function synthMany(texts: string[]): Promise<Buffer[]> {
  const results: Buffer[] = [];
  for (let i = 0; i < texts.length; i += MAX_CONCURRENCY) {
    const batch = texts.slice(i, i + MAX_CONCURRENCY);
    results.push(...(await Promise.all(batch.map(eachSingleRequest)))); // never more than MAX_CONCURRENCY in flight
  }
  return results;

Configura MAX_CONCURRENCY ligeramente por debajo del límite de tu plan, en lugar de exactamente en él. Ese margen absorbe cualquier otro tráfico que comparta la misma clave y te mantiene por debajo del umbral que devuelve un 429.

Límites de caracteres y división de textos largos

Cada modelo limita los caracteres que acepta en una sola solicitud. Cualquier integración para contenido largo debe dividir el texto y volver a unir el audio. 

Estos son los límites de caracteres por solicitud de cada modelo:

  • Flash v2.5: acepta hasta 40.000 caracteres por solicitud.
  • Flash v2: acepta hasta 30.000 caracteres por solicitud.
  • Multilingual v2: acepta hasta 10.000 caracteres por solicitud.
  • Eleven v3: acepta hasta 5.000 caracteres por solicitud.

Cualquier texto más largo debe dividirse en varias solicitudes. Intenta dividirlo en los límites de las frases para que la prosodia se mantenga entre los fragmentos.

function splitText(text: string, maxChars: number): string[] {
  const sentences = text.trim().split(/(?<=[.!?])\s+/);
  const chunks: string[] = [];
  let current = "";
  for (let sentence of sentences) {
    if (current.length + sentence.length + 1 > maxChars) {
      if (current) chunks.push(current.trim());
      // A single sentence longer than the limit is hard-split.
      while (sentence.length > maxChars) {
        chunks.push(sentence.slice(0, maxChars));
        sentence = sentence.slice(maxChars);
      }
      current = sentence;
    } else {
      current = `${current} ${sentence}`.trim();
    }
  }
  if (current) chunks.push(current.trim());
  return chunks;
}

Genera los fragmentos en orden y concatena el audio. En narraciones largas donde cada fragmento es independiente, las dos partes encajan directamente: introduce la salida de splitText en el grupo con límite anterior y deja que gestione el resto.

Caché e idempotencia

La salida de Texto a Voz es lo bastante determinista como para que volver a generar el mismo texto con la misma voz, modelo y ajustes sea un desperdicio. Guarda el resultado en caché usando como clave un hash de las entradas que afectan al audio, y esa misma clave también sirve como token de idempotencia en los reintentos.

Así puedes hacer ambas cosas.

import { createHash } from "node:crypto";

function cacheKey(text: string, voiceId: string, modelId: string,
                  outputFormat: string, settings: object): string {
  // Every parameter that changes the audio must be in the key.
  const payload = JSON.stringify({ text, voiceId, modelId, outputFormat, settings });
  return createHash("sha256").update(payload).digest("hex");
}

async function cachedSynth(text: string, voiceId: string, modelId: string,
                           outputFormat: string, settings: object): Promise<Buffer> {
  const key = cacheKey(text, voiceId, modelId, outputFormat, settings);
  const cached = await cacheGet(key);          // e.g. read from disk or S3
  if (cached) return cached;

  const audio = await elevenlabs.textToSpeech.convert(voiceId, { text, modelId, outputFormat });
  await cachePut(key, audio);                   // store the bytes under the key
  return audio;
}

La regla que hace que esto funcione es que todos los parámetros que cambian el audio deben estar en la clave, incluidos outputFormat y los ajustes de voz. Si se hace correctamente, esa misma clave también sirve como token de idempotencia. Cuando un cliente reintenta una solicitud que ya se completó correctamente, devuelves los bytes en caché en lugar de generarlos de nuevo.

Gestión de errores y límites de tasa (429)

Un cliente de producción necesita reintentos con backoff y jitter, además de un tratamiento que varíe según el código de estado, ya que algunos fallos merecen reintentarse y otros no. 

La tabla siguiente relaciona cada estado con la acción adecuada, y esta sección explica por qué un 429 es un límite flexible y no una barrera infranqueable.

Meaning
401
Authentication failed
422
Invalid request
429
Concurrency exceeded
5xx
Transient server error
Action
401
Do not retry. Check the xi-api-key header and key validity.
422
Do not retry. Fix the payload (bad voice id, unsupported format, text over limit).
429
Retry with exponential backoff and jitter.
5xx
Retry with backoff.
Character limit
401
40,000
422
30,000
429
10,000
5xx
5,000

Un 429 no es una barrera infranqueable, y conviene conocer el mecanismo. Cuando superas el límite de simultaneidad, las solicitudes primero se ponen en cola por prioridad, lo que normalmente añade unos 50 ms. Solo recibes un 429 si después de eso sigues superando la capacidad. 

La respuesta también incluye las cabeceras current-concurrent-requests y maximum-concurrent-requests, que muestran el margen disponible en tiempo real. Así puedes leerlas y reducir el ritmo antes de alcanzar el límite.

const RETRYABLE = new Set([429, 500, 502, 503, 504]);

async function synthWithRetry(text: string, voiceId: string, maxRetries = 5): Promise<Buffer> {
  let delay = 500; // ms, base for exponential backoff
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await elevenlabs.textToSpeech.convert(voiceId, {
        text, modelId: "eleven_flash_v2_5", outputFormat: "mp3_44100_128",
      });
    } catch (err: any) {
      const status = err.statusCode;
      // 401/422 and exhausted retries are not recoverable here.
      if (!RETRYABLE.has(status) || attempt === maxRetries) throw err;
      // Exponential backoff with full jitter.
      await new Promise((r) => setTimeout(r, Math.random() * delay));
      delay = Math.min(delay * 2, 8000);
    }
  }
  throw new Error("unreachable");
}

Cuando necesitas más margen en lugar de un mejor comportamiento de reintentos, mejora tu plan. Clientes Enterprise pueden solicitar límites superiores a través de su gestor de cuenta.

Comparar la latencia y el tiempo hasta el primer byte

La latencia depende de tu región, de tu entrada y de la carga actual, así que la única cifra de latencia en la que merece la pena confiar es una que hayas medido desde tu propio entorno. 

Esta sección te ofrece el tiempo hasta el primer byte (TTFB) de la ruta de streaming Flash, y está estructurada para que puedas aplicar el mismo conjunto de pruebas a otro proveedor y compararlos en condiciones idénticas.

Tómalo como una metodología, no como un resultado publicado. Una sola ejecución no garantiza nada. 

Estas son algunas consideraciones importantes al comparar la latencia de una integración de API de Texto a Voz:

  • Incluye el viaje de ida y vuelta de red: el TTFB depende de tu ubicación y del clúster más cercano del proveedor, así que ejecuta la prueba desde donde suelen ejecutarse tus servidores.
  • Descarta una ejecución de calentamiento: la primera solicitud a una conexión sin calentar es más lenta y puede sesgar tus cifras.
  • Mantén fijas las entradas: la longitud de entrada, la voz, el modelo y la carga afectan al resultado, así que mantén todos esos elementos idénticos entre proveedores.
  • Informa de una distribución: las cifras varían de una ejecución a otra, así que publica la mediana y el p95 en lugar de un único valor.

Con todo esto en cuenta, ya puedes empezar a comparar.

const TEXT = "This is a fixed benchmark sentence used for every provider.";

async function measureElevenLabs(): Promise<number> {
  const start = performance.now();
  const res = await fetch(
    "https://api.elevenlabs.io/v1/text-to-speech/JBFqnCBsd6RMkjVDRZzb/stream?output_format=mp3_44100_128",
    {
      method: "POST",
      headers: { "xi-api-key": process.env.ELEVENLABS_API_KEY!, "Content-Type": "application/json" },
      body: JSON.stringify({ text: TEXT, model_id: "eleven_flash_v2_5" }),
    },
  );
  for await (const _ of res.body!) {
    return performance.now() - start; // first chunk received
  }
  throw new Error("no audio returned");
}

Para comparar con otro proveedor, escribe una función con la misma estructura. Después ejecuta ambas con un pequeño programa que descarte una llamada de calentamiento, tome unas 20 muestras cronometradas y separadas para que no interfieran entre sí, e informe de la mediana y el p95 en milisegundos.

Una comparación justa depende de controlar las variables. 

Ejecuta ambos proveedores desde la misma máquina y red; lo ideal es un servidor en la región donde realmente despliegas, en lugar de un portátil con conexión residencial. Usa el mismo texto de entrada y mantén el audio corto para que la inferencia del modelo influya más en la cifra que la duración de la generación. Informa de la mediana y el p95 de muchas ejecuciones, porque una sola medición es ruido. 

Ten en cuenta que el TTFB en la internet pública incluye entre 20 y 200 ms de viaje de ida y vuelta de red que no tienen nada que ver con el modelo. Prestamos servicio desde clústeres en Norteamérica, Europa y el Sudeste Asiático, y dirigimos el tráfico al más cercano. Por ello, ubica tu cliente de pruebas en consecuencia; de lo contrario, estarás midiendo principalmente la distancia hasta el centro de datos.

Conclusiones clave para integrar tu API de Texto a Voz

Una integración de API de Texto a Voz en producción se reduce a unas cuantas decisiones importantes.

Si aciertas con ellas, todo lo demás encajará:

  • Elige el modelo según la tarea: usa Flash v2.5 para todo lo interactivo y un modelo de mayor fidelidad, como Multilingual v2 o Eleven v3 para generación sin conexión, donde la latencia importa menos.
  • Usa streaming siempre que un usuario esté esperando: usa streaming HTTP para texto conocido y WebSocket para agentes, de modo que el tiempo inactivo no consuma tu presupuesto de simultaneidad.
  • Limita el paralelismo al límite de tu plan: limita las solicitudes simultáneas justo por debajo del límite de tu plan y almacena en caché un hash de todos los parámetros que afectan a la salida para no facturar nunca dos veces el mismo audio.
  • Reintenta los errores 429 y 5xx con espera exponencial y jitter completo: reduce el ritmo ante errores 429 y 5xx con jitter completo, y consulta las cabeceras de simultaneidad para saber lo cerca que estás del límite.
  • Divide los textos largos en los límites de las frases: divide en los límites de las frases sin superar el límite de caracteres de cada modelo para que la prosodia se mantenga entre los fragmentos.

Si quieres profundizar aún más, consulta la guía práctica de streaming, el concepto de streaming de audio, autenticación y tokens de un solo uso para uso del lado del cliente.

Crea tu integración de Texto a Voz con ElevenAPI

Tras leer esta guía, ya tienes todos los patrones que necesitas para integrar una API de Texto a Voz en producción. Con streaming, procesamiento por lotes, caché, reintentos e incluso comparación de rendimiento, ya puedes llevarlo a la práctica. 

Empieza por obtener más información sobre la API de Texto a Voz o regístrate para hacer hoy tu primera llamada con ElevenAPI.

Preguntas frecuentes sobre la integración de la API de Texto a Voz

Artículos relacionados

Crea con el audio IA de la más alta calidad