Presentamos Eleven v4Conoce Eleven v4, nuestro modelo más expresivo hasta la fecha.

Ir al contenido

Limitación de velocidad con IA para voz: concurrencia, colas y errores 429

Publicado
Última actualización

EscucharEscucha este artículo

La mayoría de equipos aborda la limitación de tasa de IA para voz igual que con otras API: limitan las solicitudes por minuto, reintentan cuando el servidor pone límites y siguen adelante. En las cargas de trabajo de ElevenLabs, ese enfoque falla con el primer pico de tráfico, porque el límite que realmente alcanzas es la concurrencia, no el número de solicitudes.

Esta guía explica por qué la concurrencia es la restricción real y recorre los patrones del lado del cliente que te ayudan a mantenerte dentro del límite. Desde grupos de concurrencia limitada y una gestión adecuada de errores 429 hasta equidad entre tenants y cubos de tokens y con fuga, te ofrecemos sistemas prácticos que puedes implementar. Hemos acompañado cada patrón de una implementación funcional en TypeScript que puedes adaptar.

Si ուզում crear agentes de voz, pipelines de narración o cualquier otro sistema de producción basado en nuestros modelos y quieres escalar, esta guía es para ti.

Resumen

  • La limitación de tasa de IA para voz consiste en controlar la concurrencia, no en contar solicitudes por minuto.
  • Alcanzar el límite de tasa no rechaza el tráfico de inmediato. En su lugar, las solicitudes entran en una cola prioritaria que añade unos 50 ms.
  • Superar la capacidad incluso después de entrar en cola generará un error HTTP 429.
  • Los WebSockets aumentan notablemente la capacidad efectiva, ya que solo la generación activa cuenta para tu límite.
  • Los sistemas multitenant necesitan una capa adicional de equidad: cubos por tenant, cola justa ponderada, margen de capacidad reservado y fragmentación entre claves para garantizar el aislamiento. 
  • Dos cabeceras de respuesta, current-concurrent-requests y maximum-concurrent-requests, te permiten saber cuál es tu situación respecto a la limitación de tasa de IA.

Por qué el límite es la concurrencia, no las solicitudes por minuto

La concurrencia es el número de solicitudes en curso al mismo tiempo. Las solicitudes por minuto son el rendimiento durante un intervalo. Entender esta diferencia es importante porque cambia qué mecanismo te permite mantenerte dentro de tu límite.

Al utilizar uno de los modelos de ElevenLabs, la carga de trabajo del servidor aumenta con el número de usuarios simultáneos. La generación de audio ocupa una ranura durante toda la generación, y esa duración varía según la longitud de la entrada, el modelo y la carga.

Un límite de solicitudes por minuto no te dice cuántas ranuras están ocupadas ahora mismo, que es lo único que mide el servidor.

Límites por plan y familia de modelos

Tu presupuesto de concurrencia no es un único número. Los límites de concurrencia difieren según el plan y la familia de modelos. Por ejemplo, Voz a Texto tiene un límite más alto que Texto a Voz, porque las solicitudes de transcripción suelen durar menos y el sistema puede procesar más a la vez.

Multilingual v2
Free
2
Starter
3
Creator
5
Pro
10
Scale
15
Business
15
Enterprise
Elevated
Flash
Free
4
Starter
6
Creator
10
Pro
20
Scale
30
Business
30
Enterprise
Elevated
STT
Free
8
Starter
12
Creator
20
Pro
40
Scale
60
Business
60
Enterprise
Elevated
Realtime STT
Free
6
Starter
9
Creator
15
Pro
30
Scale
45
Business
45
Enterprise
Elevated
Queue weight
Free
3
Starter
4
Creator
5
Pro
5
Scale
5
Business
5
Enterprise
6

El límite es por familia de modelos. Si utilizas Flash para agentes y Multilingual v2 para narración, trabajas con dos presupuestos independientes a la vez. Las cifras actuales por plan y la sección sobre concurrencia están documentadas en la página de modelos.

¿Qué ocurre cuando alcanzas el límite de concurrencia?

Alcanzar el límite de concurrencia no rechaza el tráfico inmediatamente. El sistema se degrada de forma gradual mediante una cola prioritaria, y solo pasa al rechazo completo si sigues superando la capacidad total del límite de tasa.

Mientras estés por debajo de tu límite, las solicitudes se ejecutan de inmediato. Cuando lo alcanzas, las solicitudes posteriores entran en una cola ordenada por el nivel de prioridad de tu plan. La cola suele añadir unos 50 ms de latencia, por lo que una superación breve apenas resulta visible para usuarios.

Si el sistema sigue sin capacidad tras entrar en cola, recibes un HTTP 429. Es la señal para reducir el ritmo en lugar de reintentar de inmediato. El nivel de prioridad de la tabla determina cómo se ordenan tus solicitudes en cola respecto a otro tráfico; los planes superiores vacían la cola antes.

HTTP frente a WebSocket: cómo cuenta cada uno para tu límite

El transporte que elijas influye directamente en la limitación de tasa y el presupuesto. Una misma conversación entrante puede consumir cantidades muy distintas de tu presupuesto de concurrencia según se ejecute mediante HTTP o WebSocket.

Con HTTP, cada solicitud cuenta individualmente para tu límite de concurrencia durante toda su duración. Con un WebSocket, solo cuenta el tiempo en que el modelo genera audio activamente. Un WebSocket abierto pero inactivo prácticamente no cuenta.

En un agente de voz, una conversación tiene largos intervalos en los que nadie habla y el modelo no genera nada. Con HTTP, ocuparías una ranura durante la duración de la solicitud en cada turno. Con un WebSocket, la ranura se consume solo durante los milisegundos de generación activa, de modo que una ranura de concurrencia se comparte en el tiempo entre muchas conversaciones. 

Consulta la guía de WebSocket de TTS en tiempo real para conocer los detalles del protocolo. Para tráfico interactivo, WebSockets es la opción predeterminada adecuada.

Por qué una concurrencia de ~5 puede admitir ~100 emisiones

Las matemáticas de la concurrencia no son intuitivas hasta que tienes en cuenta el tiempo de reproducción. La generación es mucho más rápida que la reproducción, y una ranura solo está ocupada activamente mientras se genera audio. Esa diferencia es precisamente lo que permite que un presupuesto pequeño atienda a una audiencia amplia.

Una solicitud que tarda una fracción de segundo en generar produce varios segundos de audio que el oyente reproduce después y, durante la reproducción, la ranura se libera y queda disponible para otros oyentes.

Como regla general, un límite de concurrencia de 5 puede admitir aproximadamente 100 emisiones de audio simultáneas. La cifra exacta depende de la voz, el ritmo del habla y cuánto silencio haya entre intervenciones.

Las cabeceras que explican cuál es tu situación

No necesitas deducir tu posición respecto a tu límite. Cada respuesta incluye dos valores que puedes utilizar para medir el margen de capacidad, en lugar de limitarte a estimarlo.

Busca estas dos cabeceras:

  • current-concurrent-requests: ¿cuántas solicitudes están en curso ahora mismo?
  • maximum-concurrent-requests: tu límite para esa familia de modelos.

Juntas, estas cabeceras ofrecen una visión en tiempo real de tu uso actual y la capacidad disponible. No deberías tener que hacer estimaciones antes de encontrarte con límites de tasa de IA.

Estrategias del lado del cliente para la limitación de tasa de IA

Hay cuatro elementos básicos que cubren casi todos los casos de limitación de tasa de IA:

  • Un cubo de tokens: si hay tokens disponibles, permite que las solicitudes avancen. La capacidad se repone con el tiempo, lo que permite gestionar ráfagas breves sin alcanzar límites de tasa.
  • Un cubo con fuga: intenta suavizar el tráfico entrante hasta una tasa de salida fija, lo que evita que picos repentinos saturen tus sistemas posteriores.
  • Un grupo de concurrencia limitada: limita el número total de solicitudes que pueden estar activas simultáneamente, por lo que nunca superas los límites de solicitudes simultáneas.
  • Backoff exponencial con jitter completo: aumenta progresivamente el tiempo entre solicitudes fallidas para evitar que todos los clientes reintenten a la vez.

Las secciones siguientes muestran cómo implementarlos uno a uno, empezando por el que se corresponde más directamente con el límite de concurrencia.

Todos los fragmentos siguientes asumen un único cliente, inicializado una vez:

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

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

Concurrencia limitada: el elemento básico que se ajusta al límite

Como el servidor mide la concurrencia, el control de cliente más directo es un grupo limitado de workers que restringe cuántas solicitudes tienes en curso a la vez. Establece el límite un poco por debajo del de tu plan para dejar margen para la cola prioritaria y el jitter.

async function pool<T, R>(
  items: T[],
  maxInFlight: number,
  worker: (item: T) => Promise<R>,
): Promise<R[]> {
  const results: R[] = new Array(items.length);
  let next = 0;

  async function run(): Promise<void> {
    while (next < items.length) {
      const i = next++;
      results[i] = await worker(items[i]); // never more than maxInFlight of these run at once
    }
  }

  await Promise.all(
    Array.from({ length: Math.min(maxInFlight, items.length) }, run),
  );
  return results;
}

async function synthesize(text: string): Promise<Buffer> {
  const stream = await elevenlabs.textToSpeech.stream("JBFqnCBsd6RMkjVDRZzb", {
    text,
    modelId: "eleven_flash_v2_5",
    outputFormat: "mp3_44100_128",
  });
  const chunks: Buffer[] = [];
  for await (const chunk of stream) chunks.push(Buffer.from(chunk));
  return Buffer.concat(chunks);
}

// Plan Flash limit is, say, 10. Stay under it.
const texts = Array.from({ length: 50 }, (_, i) => `Sentence number ${i}.`);
const audio = await pool(texts, 8, synthesize); // never more than 8 in flight

Cubo de tokens: permite ráfagas y limita la media

Un cubo de tokens almacena hasta capacity tokens y se repone a refillRate tokens por segundo. Cada solicitud consume un token, por lo que el cubo permite ráfagas breves hasta su tamaño y limita la tasa a largo plazo. 

Es la herramienta adecuada para suavizar el momento en que llega de repente una cola de trabajo, de modo que no lances todo a la vez y dispares la concurrencia.

class TokenBucket {
  private tokens: number;
  private updated = performance.now();

  constructor(private capacity: number, private refillPerSec: number) {
    this.tokens = capacity;
  }

  private refill(): void {
    const now = performance.now();
    const elapsed = (now - this.updated) / 1000;
    this.tokens = Math.min(this.capacity, this.tokens + elapsed * this.refillPerSec);
    this.updated = now;
  }

  tryAcquire(cost = 1): boolean {
    this.refill();
    if (this.tokens >= cost) {
      this.tokens -= cost;
      return true;
    }
    return false;
  }

  timeUntil(cost = 1): number {
    this.refill();
    return this.tokens >= cost ? 0 : ((cost - this.tokens) / this.refillPerSec) * 1000;
  }
}

Cubo con fuga: impón un drenaje constante

En algunos casos no quieres tolerar ráfagas en absoluto. Un cubo con fuga admite trabajo a una tasa fija y constante, independientemente de lo irregular que sea la entrada. Es la mejor opción cuando el sistema posterior prefiere una carga uniforme y predecible a picos ocasionales.

Por ejemplo, cuando deliberadamente te mantienes muy por debajo de un presupuesto de concurrencia pequeño compartido con otros servicios.

class LeakyBucket {
  private next = performance.now();
  constructor(private intervalMs: number) {} // admit at most one item per intervalMs

  async acquire(): Promise<void> {
    const now = performance.now();
    const wait = Math.max(0, this.next - now);
    this.next = Math.max(now, this.next) + this.intervalMs;
    if (wait > 0) await new Promise((r) => setTimeout(r, wait));
  }
}

Backoff exponencial con jitter completo

Cuando una solicitud falla con un estado que permite reintento, reintentarlo de inmediato empeora la situación. El backoff separa los reintentos y el jitter completo aleatoriza cada retraso en todo el intervalo, evitando que muchos clientes reintenten al mismo ritmo y reproduzcan el pico que causó el fallo.

El fragmento siguiente hace referencia a RetryableError, una pequeña clase que incluye el estado fallido y cualquier valor de Retry-After. Se define en la sección sobre gestión adecuada de 429 más abajo.

async function withBackoff<T>(
  call: () => Promise<T>,
  opts: { maxAttempts?: number; baseMs?: number; capMs?: number } = {},
): Promise<T> {
  const { maxAttempts = 5, baseMs = 500, capMs = 20_000 } = opts;
  let attempt = 0;
  for (;;) {
    try {
      return await call();
    } catch (e) {
      if (!(e instanceof RetryableError) || ++attempt >= maxAttempts) throw e;
      // honor Retry-After if present; otherwise capped exponential growth with full jitter
      const delay =
        e.retryAfterMs ?? Math.random() * Math.min(capMs, baseMs * 2 ** attempt);
      await new Promise((r) => setTimeout(r, delay));
    }
  }
}

Gestión adecuada de 429: qué hacer cuando alcanzas el límite

Un 429 significa que superaste la capacidad incluso después de la cola prioritaria, por lo que la respuesta correcta es reducir el ritmo en lugar de insistir con los reintentos. Hay cuatro formas de gestionarlo. Gestionarlo bien se reduce a cuatro estrategias:

  • Detección
  • Respetar Retry-After
  • Mostrar la contrapresión
  • Evitar tormentas de reintentos con un cortacircuitos

Veámoslas con más detalle.

La primera es la detección. Trata HTTP 429 (y los 500, 502, 503 y 504 transitorios) como reintentables, y 400, 401, 403 y 422 como no reintentables; reintentar una solicitud malformada o no autorizada nunca funciona y solo desperdicia una ranura.

La segunda es respetar Retry-After. Si la respuesta incluye esa cabecera, respétala exactamente en lugar de calcular tu propio retraso. El servidor te indica cuándo espera tener capacidad y lo sabe mejor que tu fórmula exponencial. Recurre al backoff con jitter solo cuando no exista la cabecera.

class RetryableError extends Error {
  constructor(public status: number, public retryAfterMs?: number) {
    super(`retryable ${status}`);
  }
}

function classify(resp: Response): void {
  if ([429, 500, 502, 503, 504].includes(resp.status)) {
    const ra = resp.headers.get("retry-after");
    throw new RetryableError(resp.status, ra ? Number(ra) * 1000 : undefined);
  }
  if (!resp.ok) throw new Error(`non-retryable ${resp.status}`);
}

El tercer aspecto es mostrar la contrapresión. No permitas que los reintentos se acumulen de forma invisible. Si la profundidad de tu cola o el margen de capacidad medido indica que no puedes atender pronto una nueva solicitud, recházala en el borde con una señal clara para quien llama, en vez de aceptar trabajo que no puedes realizar.

El cuarto es evitar tormentas de reintentos con un cortacircuitos. Si los fallos superan un umbral, abre el circuito y falla rápido durante un periodo de enfriamiento, en lugar de enviar solicitudes que esperas que fallen. Tras ese periodo, envía algunas solicitudes de prueba; si tienen éxito, cierra el circuito.

class CircuitBreaker {
  private failures = 0;
  private openedAt: number | null = null;
  constructor(private threshold = 5, private cooldownMs = 10_000) {}

  allow(): boolean {
    if (this.openedAt === null) return true;
    if (performance.now() - this.openedAt >= this.cooldownMs) {
      this.openedAt = null; // half-open: allow a probe
      this.failures = 0;
      return true;
    }
    return false;
  }

  record(ok: boolean): void {
    if (ok) {
      this.failures = 0;
      this.openedAt = null;
    } else if (++this.failures >= this.threshold) {
      this.openedAt = performance.now();
    }
  }
}

Patrones de cuotas multitenant para la limitación de tasa de IA

Hasta ahora, todo asume una única aplicación con un único presupuesto. Cuando creas un SaaS sobre ElevenLabs, el problema cambia: tu presupuesto de concurrencia se comparte entre todos tus clientes, y un tenant que ejecuta un trabajo por lotes no debería privar a todos los demás del tráfico en directo. Necesitas una capa de equidad entre tus tenants y el único límite ascendente.

La base son los cubos de tokens por tenant. Da a cada tenant su propio cubo, dimensionado según lo que le corresponda, y admite una solicitud solo cuando lo permitan tanto el cubo del tenant como un limitador global.

class MultiTenantAdmission {
  private tenantBuckets = new Map<string, TokenBucket>();
  constructor(private globalMaxInFlight: number) {}

  private bucket(tenant: string): TokenBucket {
    let b = this.tenantBuckets.get(tenant);
    if (!b) {
      // Each tenant: burst of 5, sustained 2 starts/sec. Tune per tier.
      b = new TokenBucket(5, 2);
      this.tenantBuckets.set(tenant, b);
    }
    return b;
  }

  async run<R>(tenant: string, work: () => Promise<R>): Promise<R> {
    const b = this.bucket(tenant);
    if (!b.tryAcquire()) {
      throw new RetryableError(429, b.timeUntil());
    }
    // ... then admit through the global limiter (e.g. the bounded pool above)
    return work();
  }
}

Los cubos evitan que un solo tenant abuse, pero no deciden quién gana cuando los tenants compiten por el limitador global. Para ello, utiliza una cola justa ponderada. 

No atiendas por orden de llegada, pues una ráfaga de un tenant puede monopolizar las ranuras. Mantén una cola por tenant y distribuye en proporción al peso de cada uno, de modo que un tenant de pago reciba una parte mayor de la capacidad disputada que uno gratuito.

Además de la equidad, reserva margen de capacidad. Nunca permitas que el tráfico normal consuma el 100 % del límite de concurrencia. Reserva una fracción, por ejemplo el 15-20 %, como búfer para solicitudes interactivas sensibles a la latencia y para la cola prioritaria.

Cuando la equidad dentro de un único presupuesto deja de ser suficiente, fragmenta entre espacios de trabajo o claves. Un único presupuesto de concurrencia acabará siendo el cuello de botella, por muy equitativamente que lo repartas. 

En ese momento, separa las cargas de trabajo en espacios de trabajo o claves de API distintos con sus propios presupuestos: por ejemplo, una clave para tráfico de agentes en tiempo real y otra para narración en segundo plano, para que una acumulación de narración no afecte a la capacidad de los agentes.

Los espacios de trabajo también te permiten aplicar restricciones de ámbito, cuotas de créditos y controles por clave, descritos en la documentación de autenticación.

Supervisar el uso de concurrencia

Nada de esto se puede ajustar sin medir; no puedes gestionar un margen de capacidad que no mides. Registra current-concurrent-requests y maximum-concurrent-requests en cada respuesta, etiquetados por familia de modelos, y emite la ratio de utilización como una métrica.

function recordHeadroom(resp: Response, metrics: Metrics): void {
  const cur = Number(resp.headers.get("current-concurrent-requests"));
  const max = Number(resp.headers.get("maximum-concurrent-requests"));
  if (Number.isFinite(cur) && Number.isFinite(max)) {
    metrics.gauge("el.concurrency.current", cur);
    metrics.gauge("el.concurrency.max", max);
    if (max > 0) metrics.gauge("el.concurrency.utilization", cur / max);
  }
}

Cuatro señales que seguir:

  • Utilización (actual / máximo).
  • Tasa de 429 como proporción del total de solicitudes.
  • Profundidad de reintento: el número de intentos por solicitud lógica.
  • Tiempo hasta el primer audio, medido desde tu aplicación, no a partir de las cifras de inferencia del modelo. Consulta la explicación sobre latencia para saber qué incluye TTFA.

Un sistema sano mantendrá la utilización cómodamente por debajo de la saturación y solo verá 429 en ráfagas ocasionales. Supervisar estas señales te da visibilidad de la presión del límite de tasa mucho antes de que se convierta en un problema de disponibilidad.

Cuándo escalar más allá de la limitación de tasa del lado del cliente

Los patrones del lado del cliente pueden hacer mucho trabajo, pero la demanda sostenida acabará superándolos. Cuando ocurra, es hora de hacer cambios que ayuden tanto con el coste como con el esfuerzo. 

Cada uno de los siguientes pasos te proporcionará capacidad adicional.

Empieza por cambiar de HTTP a WebSockets para el tráfico interactivo. Si tus agentes o casos de uso en directo se ejecutan mediante HTTP, pasar a WebSocket cambia el cálculo para que solo cuente la generación activa. En cargas de trabajo conversacionales, esto suele multiplicar la capacidad efectiva sin cambiar de plan, porque el tiempo de conversación inactivo deja de consumir ranuras.

Si tus ráfagas son irregulares pero tu carga media cabe en el presupuesto, un cubo de tokens o con fuga junto con un grupo limitado convierte los picos en la media.

Después, elige el modelo adecuado. Una generación más rápida mantiene cada ranura ocupada durante menos tiempo, lo que aumenta el número de emisiones que puede sostener un límite de concurrencia fijo. Eleven Flash v2.5 es la opción de menor latencia para trabajo en tiempo real; combinarlo con un Clon de Voz Instantáneo o una voz predeterminada evita la sobrecarga por generación de los Clones de Voz Profesionales.

Solo después deberías mejorar el plan. Cuando tu demanda sostenida supere realmente el presupuesto después de que el cliente se comporte correctamente, un plan superior aumenta tanto el límite de concurrencia por modelo como la prioridad de tu cola. Compara los niveles en la página de precios de la API.

Si necesitas límites superiores a los publicados, los planes Enterprise ofrecen límites de concurrencia elevados y personalizados, así como la máxima prioridad en cola. Hay controles adicionales disponibles para casos de uso que cumplan los requisitos, como listas blancas de IP (en vista previa de Enterprise) y modos de retención cero. Ponte en contacto con tu gestor de cuenta para aumentar los límites.

Resumen de lo que debes tener en cuenta sobre la limitación de tasa de IA 

El error principal es tratar la limitación de tasa de IA para voz como un recuento de solicitudes. Todo aquí trata sobre controlar la concurrencia. El número que determina si tienes éxito es cuántas solicitudes generan audio en el mismo instante y cuánto tiempo ocupa cada una su ranura.

Diseña el cliente en torno a ese hecho. 

Limita las solicitudes en curso con un grupo limitado, regula la admisión con un cubo de tokens o con fuga, reintenta con backoff exponencial limitado y jitter completo, respeta Retry-After y abre el circuito antes de que se forme una tormenta de reintentos. 

Para sistemas multitenant, añade cubos por tenant, equidad ponderada, margen de capacidad reservado y fragmentación para aislamiento. Vigila las cabeceras current-concurrent-requests y maximum-concurrent-requests, y alerta según la tendencia de utilización, no según los fallos. 

Cuando realmente necesites más capacidad, sigue la lista en orden: primero WebSockets y un mejor comportamiento del cliente, después el modelo adecuado, luego una mejora de plan y, por último, los límites Enterprise.

Crea aplicaciones de voz con ElevenAPI

La limitación de tasa de IA lista para producción empieza con el transporte adecuado, el modelo adecuado y cabeceras que te indican exactamente cuál es tu situación.

ElevenAPI ofrece modelos de baja latencia como Eleven Flash v2.5, streaming por WebSocket en tiempo real, Voz a Texto y API de Texto a Voz, además de cabeceras de concurrencia por respuesta que te permiten crear agentes de voz que escalan dentro de tus límites. 

Combinadas con las estrategias de limitación de tasa de IA de este artículo, te permitirán ofrecer experiencias de voz ágiles y mantener un rendimiento predecible, incluso bajo carga. 

Explora ElevenAPI para ver toda la gama de modelos en acción o crea una cuenta para empezar a crear con ElevenLabs hoy mismo.

Preguntas frecuentes sobre la limitación de tasa de IA

Escrito por

Tadas Petra es Developer Relations Engineer y ayuda a desarrolladores a crear con la suite de productos de ElevenLabs. Además, es fundador de Hungrimind, una plataforma de formación para desarrolladores. Antes fue Lead Developer Advocate en Agora e instructor en Zero To Mastery.

Artículos relacionados

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