Apresentamos o Eleven v4Conheça o Eleven v4, nosso modelo mais expressivo até agora.

Pular para o conteúdo

Limitação de taxa com IA para voz: Concorrência, filas e erros 429

Publicado
Última atualização

OuvirOuça este artigo

A maioria das equipes aborda a limitação de taxa por IA para voz da mesma forma que lida com outras APIs: limita as solicitações por minuto, tenta novamente quando o servidor impõe restrições e segue em frente. Para cargas de trabalho na ElevenLabs, esse modelo falha já no primeiro pico de tráfego, porque o limite que você realmente atinge é o de simultaneidade, não a contagem de solicitações.

Este guia explica por que a simultaneidade é a verdadeira restrição e mostra padrões do lado do cliente para manter você dentro desse limite. De pools de simultaneidade limitada e tratamento adequado de erros 429 à equidade entre múltiplos tenants e buckets de tokens e de vazamento, oferecemos sistemas práticos que você pode implementar. Associamos cada padrão a uma implementação funcional em TypeScript que você pode adaptar.

Se você cria agentes de voz, pipelines de narração ou qualquer outro sistema de produção baseado nos nossos modelos e quer escalar, este guia é para você.

Resumo

  • A limitação de taxa por IA para voz é controle de simultaneidade, não contagem de solicitações por minuto.
  • Atingir o teto da limitação de taxa não rejeita o tráfego imediatamente. Em vez disso, as solicitações entram em uma fila de prioridade que adiciona cerca de 50 ms.
  • Ultrapassar a capacidade mesmo após entrar na fila gera um erro HTTP 429.
  • WebSockets aumentam drasticamente a capacidade efetiva, pois apenas a geração ativa conta para o seu limite.
  • Sistemas com múltiplos tenants precisam de uma camada extra de equidade: buckets por tenant, enfileiramento justo ponderado, capacidade reservada e fragmentação entre chaves para isolamento. 
  • Dois cabeçalhos de resposta, current-concurrent-requests e maximum-concurrent-requests, informam sua situação em relação à limitação de taxa por IA.

Por que o limite é a simultaneidade, não as solicitações por minuto

Simultaneidade é o número de solicitações em andamento no mesmo momento. Solicitações por minuto são a vazão ao longo de uma janela de tempo. Entender essa diferença é importante porque ela muda qual recurso mantém você dentro do limite.

Ao usar um dos modelos da ElevenLabs, a carga de trabalho do servidor aumenta conforme o número de usuários simultâneos. A geração de áudio ocupa uma vaga durante toda a geração, e essa duração varia de acordo com o tamanho da entrada, o modelo e a carga.

Um limite de solicitações por minuto não informa quantas vagas estão ocupadas neste momento, que é a única métrica controlada pelo servidor.

Limites por plano e por família de modelos

Seu orçamento de simultaneidade não é um único número. Os limites de simultaneidade variam por plano e família de modelos. Por exemplo, Speech to Text tem um limite mais alto em relação a Text to Speech, pois as solicitações de transcrição geralmente têm duração menor e o sistema consegue processar mais delas ao mesmo tempo.

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

O limite é por família de modelos. Se você usa Flash para agentes e Multilingual v2 para narração, está trabalhando com dois orçamentos separados ao mesmo tempo. Os valores atuais por plano e a seção sobre simultaneidade estão documentados na página de modelos.

O que acontece quando você atinge o limite de simultaneidade?

Atingir o limite de simultaneidade não rejeita o tráfego imediatamente. O sistema reduz o desempenho de forma gradual por meio de uma fila de prioridade e só passa à rejeição completa quando você continua acima da capacidade total da limitação de taxa.

Enquanto você está abaixo do limite, as solicitações são executadas imediatamente. Ao atingi-lo, as solicitações seguintes entram em uma fila ordenada pelo nível de prioridade do seu plano. A fila normalmente adiciona cerca de 50 ms de latência, então uma breve ultrapassagem quase não é percebida pelos usuários.

Se o sistema continuar acima da capacidade após o enfileiramento, você recebe um HTTP 429. Esse é o sinal para reduzir o ritmo, em vez de tentar novamente de imediato. O nível de prioridade na tabela determina como suas solicitações enfileiradas são ordenadas em relação a outro tráfego; planos superiores esvaziam a fila mais rapidamente.

HTTP vs. WebSocket: como cada um conta para o seu limite

O transporte escolhido influencia diretamente a limitação de taxa e o orçamento. A mesma conversa recebida pode consumir quantidades significativamente diferentes do seu orçamento de simultaneidade, dependendo de usar HTTP ou WebSocket.

Em HTTP, cada solicitação conta individualmente para o seu limite de simultaneidade durante toda a sua duração. Em um WebSocket, conta apenas o tempo em que o modelo está gerando áudio ativamente. Um WebSocket aberto, mas inativo, praticamente não conta.

Para um agente de voz, uma conversa tem longos períodos em que ninguém fala e o modelo não gera nada. Com HTTP, você manteria uma vaga ocupada pela duração da solicitação a cada turno. Com WebSocket, a vaga só é consumida durante os milissegundos de geração ativa, então uma vaga de simultaneidade é compartilhada ao longo do tempo entre muitas conversas. 

Consulte o guia de WebSocket TTS em tempo real para conhecer os detalhes do protocolo. Para tráfego interativo, WebSockets são a opção padrão certa.

Por que uma simultaneidade de ~5 pode suportar ~100 transmissões

A matemática por trás da simultaneidade parece contraintuitiva até você considerar o tempo de reprodução. A geração é muito mais rápida que a reprodução, e uma vaga só fica ocupada ativamente durante a geração de áudio. Essa diferença é justamente o que permite que um orçamento pequeno atenda a um público grande.

Uma solicitação que leva uma fração de segundo para gerar produz vários segundos de áudio que o ouvinte reproduz em seguida e, durante a reprodução, a vaga é liberada e fica disponível para outros ouvintes.

Como regra geral, um limite de simultaneidade de 5 pode suportar cerca de 100 transmissões de áudio simultâneas. O número exato depende da voz, do ritmo da fala e da quantidade de silêncio entre as falas.

Os cabeçalhos que explicam sua situação

Você não precisa deduzir sua posição em relação ao limite. Cada resposta traz dois números que permitem medir a capacidade disponível em vez de apenas estimá-la.

Observe estes dois cabeçalhos:

  • current-concurrent-requests: quantas solicitações estão em andamento agora?
  • maximum-concurrent-requests: seu limite para essa família de modelos.

Juntos, esses cabeçalhos fornecem uma visão em tempo real do seu uso atual e da capacidade disponível. Você não precisa fazer estimativas antes de atingir os limites de taxa por IA.

Estratégias do lado do cliente para limitação de taxa por IA

Há quatro recursos fundamentais que abrangem quase todos os cenários de limitação de taxa por IA:

  • Um bucket de tokens: se houver tokens disponíveis, permite que as solicitações prossigam. A capacidade é reabastecida com o tempo, permitindo lidar com picos curtos sem atingir os limites de taxa.
  • Um bucket de vazamento: tenta suavizar o tráfego de entrada em uma taxa de saída fixa, evitando que picos repentinos sobrecarreguem seus sistemas downstream.
  • Um pool de simultaneidade limitada: limita o número total de solicitações que podem estar ativas simultaneamente, para que você nunca exceda os limites de solicitações simultâneas.
  • Backoff exponencial com jitter completo: aumenta progressivamente o intervalo entre solicitações com falha para evitar que todos os clientes tentem novamente ao mesmo tempo.

As seções abaixo mostram como implementá-los, um de cada vez, começando pelo que corresponde mais diretamente ao limite de simultaneidade.

Todos os trechos abaixo pressupõem um único cliente, inicializado uma vez:

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

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

Simultaneidade limitada: o recurso que corresponde ao limite

Como o servidor mede a simultaneidade, o controle mais direto no cliente é um pool limitado de workers que restringe quantas solicitações ficam em andamento ao mesmo tempo. Defina o limite um pouco abaixo do limite do seu plano para deixar espaço para a fila de prioridade e o 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

Bucket de tokens: permita picos, limite a média

Um bucket de tokens armazena até capacity tokens e é reabastecido com refillRate tokens por segundo. Cada solicitação consome um token, portanto o bucket permite picos curtos até seu tamanho, enquanto limita a taxa no longo prazo. 

É a ferramenta certa para suavizar o momento em que uma fila de trabalho chega de repente, para que você não envie tudo de uma vez e cause um pico de simultaneidade.

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;
  }
}

Bucket de vazamento: imponha uma vazão constante

Em alguns casos, você não quer tolerar picos de forma alguma. Um bucket de vazamento admite trabalho a uma taxa fixa e constante, independentemente de quão irregular seja a entrada. É a melhor escolha quando o sistema downstream prefere uma carga suave e previsível a picos ocasionais.

Por exemplo, quando você quer permanecer intencionalmente bem dentro de um pequeno orçamento de simultaneidade compartilhado com outros serviços.

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 com jitter completo

Quando uma solicitação falha com um status que permite nova tentativa, tentar novamente de imediato piora a situação. O backoff espaça as tentativas, e o jitter completo aleatoriza cada atraso por todo o intervalo, evitando que muitos clientes tentem novamente em sincronia e recriem o mesmo pico que causou a falha.

O trecho abaixo faz referência a RetryableError, uma classe pequena que armazena o status da falha e qualquer valor de Retry-After. Ela é definida na seção sobre tratamento adequado de 429 abaixo.

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));
    }
  }
}

Tratamento adequado de 429: o que fazer ao atingir o teto

Um 429 significa que você estava acima da capacidade mesmo após a fila de prioridade, então a resposta correta é reduzir o ritmo, em vez de insistir nas tentativas. Há quatro formas de lidar com isso. Um bom tratamento se resume a quatro estratégias:

  • Detecção
  • Respeitar Retry-After
  • Sinalizar contrapressão
  • Evitar tempestades de tentativas com um circuit breaker

Vamos detalhar cada uma delas.

A primeira é a detecção. Trate HTTP 429 (e os erros transitórios 500, 502, 503 e 504) como passíveis de nova tentativa e trate 400, 401, 403 e 422 como não passíveis de nova tentativa; tentar novamente uma solicitação malformada ou não autorizada nunca terá sucesso e só desperdiça uma vaga.

A segunda é respeitar Retry-After. Se a resposta trouxer esse cabeçalho, siga-o exatamente, em vez de calcular seu próprio atraso. O servidor informa quando espera ter capacidade e sabe disso melhor do que sua fórmula exponencial. Recorra ao backoff com jitter apenas quando o cabeçalho estiver ausente.

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}`);
}

O terceiro aspecto é sinalizar contrapressão. Não deixe as tentativas se acumularem de forma invisível. Se a profundidade da sua fila ou a capacidade disponível medida indicar que você não poderá atender uma nova solicitação em breve, rejeite-a na entrada com um sinal claro para quem chamou, em vez de aceitar um trabalho que não pode realizar.

O quarto é evitar tempestades de tentativas com um circuit breaker. Se as falhas ultrapassarem um limite, abra o circuito e falhe rapidamente durante uma janela de resfriamento, em vez de enviar solicitações que você espera que falhem. Depois da janela, envie algumas solicitações de teste; se tiverem sucesso, feche o 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();
    }
  }
}

Padrões de cotas multi-tenant para limitação de taxa por IA

Tudo até aqui pressupõe um único aplicativo com um único orçamento. Ao criar um SaaS sobre a ElevenLabs, o problema muda: seu orçamento de simultaneidade é compartilhado entre todos os seus clientes, e um tenant executando um trabalho em lote não deve privar todo o tráfego ao vivo dos outros tenants. Você precisa de uma camada de equidade entre seus tenants e o único limite upstream.

A base são buckets de tokens por tenant. Dê a cada tenant seu próprio bucket dimensionado de acordo com seu direito de uso e aceite uma solicitação somente quando tanto o bucket do tenant quanto um limitador global permitirem.

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();
  }
}

Os buckets mantêm cada tenant dentro dos limites, mas não decidem quem vence quando os tenants disputam o limitador global. Para isso, use enfileiramento justo ponderado. 

Não atenda por ordem de chegada, pois isso permite que um pico de um tenant monopolize as vagas. Mantenha uma fila por tenant e faça a distribuição proporcionalmente ao peso de cada um, para que um tenant pagante receba uma parcela maior da capacidade disputada do que um tenant gratuito.

Além da equidade, reserve capacidade. Nunca deixe o tráfego normal consumir 100% do limite de simultaneidade. Reserve uma parcela, por exemplo, 15% a 20%, como buffer para solicitações interativas sensíveis à latência e para a fila de prioridade.

Quando a equidade dentro de um único orçamento deixa de ser suficiente, fragmente entre workspaces ou chaves. Um único orçamento de simultaneidade acabará se tornando o gargalo, por mais justamente que você o divida. 

Nesse ponto, separe as cargas de trabalho em workspaces ou chaves de API distintos, com seus próprios orçamentos: por exemplo, uma chave para tráfego de agentes em tempo real e outra para narração em segundo plano, para que uma fila acumulada de narração não afete a capacidade dos agentes.

Os workspaces também permitem aplicar restrição de escopo, cotas de créditos e controles por chave, descritos na documentação de autenticação.

Monitoramento da utilização de simultaneidade

Nada disso pode ser ajustado sem medição; você não consegue gerenciar uma capacidade disponível que não mede. Registre current-concurrent-requests e maximum-concurrent-requests em cada resposta, identificados por família de modelos, e emita a taxa de utilização como uma 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);
  }
}

Quatro sinais para acompanhar:

  • Utilização (atual / máximo).
  • Taxa de 429 como fração do total de solicitações.
  • Profundidade de tentativas, o número de tentativas por solicitação lógica.
  • Tempo até o primeiro áudio, medido no seu aplicativo, não pelos dados de inferência do modelo. Consulte como entender a latência para saber o que o TTFA inclui.

Um sistema saudável mantém a utilização confortavelmente abaixo da saturação e vê erros 429 apenas em picos ocasionais. Monitorar esses sinais dá visibilidade sobre a pressão da limitação de taxa muito antes de ela se tornar um problema de indisponibilidade.

Quando escalar além da limitação de taxa do lado do cliente

Os padrões do lado do cliente podem fazer muito, mas a demanda estável acabará superando suas capacidades. Quando isso acontecer, será hora de fazer mudanças que ajudam tanto no custo quanto no esforço. 

Cada uma das etapas a seguir oferece capacidade adicional.

Comece trocando HTTP por WebSockets no tráfego interativo. Se seus agentes ou casos de uso ao vivo usam HTTP, migrar para WebSocket muda a contagem para que apenas a geração ativa seja considerada. Para cargas de trabalho conversacionais, isso muitas vezes multiplica a capacidade efetiva sem mudar seu plano, porque o tempo ocioso da conversa deixa de consumir vagas.

Se seus picos forem irregulares, mas sua carga média couber no orçamento, um bucket de tokens ou de vazamento junto com um pool limitado transforma os picos na média.

Em seguida, escolha o modelo certo. Uma geração mais rápida mantém cada vaga ocupada por menos tempo, aumentando o número de transmissões que um limite fixo de simultaneidade pode sustentar. Eleven Flash v2.5 é a opção de menor latência para trabalho em tempo real; combiná-lo com uma Clonagem Instantânea de Voz ou uma voz padrão evita a sobrecarga por geração das Clonagens Profissionais de Voz.

Só depois disso você deve fazer upgrade do plano. Quando sua demanda estável realmente ultrapassar o orçamento, mesmo com um cliente bem-comportado, um plano superior aumenta tanto o limite de simultaneidade por modelo quanto a prioridade da sua fila. Compare os níveis na página de preços da API.

Se precisar de limites além dos publicados, os planos Enterprise oferecem limites de simultaneidade elevados e personalizados, além da maior prioridade na fila. Há controles adicionais disponíveis para casos de uso elegíveis, como lista de permissões de IP (em prévia para Enterprise) e modos sem retenção. Entre em contato com seu gerente de conta para aumentar os limites.

Recapitulando o que considerar sobre limitação de taxa por IA 

O erro fundamental é tratar a limitação de taxa de IA para voz como uma contagem de solicitações. Tudo aqui se trata de controle de simultaneidade. O número que determina seu sucesso é quantas solicitações estão gerando áudio no mesmo instante e por quanto tempo cada uma mantém sua vaga ocupada.

Estruture o cliente com base nisso. 

Limite as solicitações em andamento com um pool limitado, controle a admissão com um bucket de tokens ou de vazamento, tente novamente com backoff exponencial limitado e jitter completo, respeite Retry-After e interrompa o circuito antes que se forme uma tempestade de tentativas. 

Para sistemas com múltiplos tenants, adicione buckets por tenant, equidade ponderada, capacidade reservada e fragmentação para isolamento. Acompanhe os cabeçalhos current-concurrent-requests e maximum-concurrent-requests e gere alertas com base na tendência de utilização, não nas falhas. 

Quando você realmente precisar de mais capacidade, siga a lista na ordem: primeiro WebSockets e melhor comportamento do cliente, depois o modelo certo, em seguida um upgrade de plano e, por fim, limites Enterprise.

Crie aplicativos de voz com a ElevenAPI

A limitação de taxa por IA em nível de produção começa com o transporte certo, o modelo certo e cabeçalhos que mostram exatamente sua situação.

A ElevenAPI oferece modelos de baixa latência como o Eleven Flash v2.5, streaming WebSocket em tempo real, Speech to Text e APIs de Text to Speech, além de cabeçalhos de simultaneidade por resposta que permitem criar agentes de voz que escalam dentro dos seus limites. 

Combinadas às estratégias de limitação de taxa por IA deste artigo, elas permitem oferecer experiências de voz responsivas, mantendo um desempenho previsível, mesmo sob carga. 

Explore a ElevenAPI para ver toda a linha de modelos em ação ou crie uma conta para começar a desenvolver com a ElevenLabs hoje.

Perguntas frequentes sobre limitação de taxa por IA

Escrito por

Tadas Petra é engenheiro de Relações com Desenvolvedores e ajuda desenvolvedores a criar com a suíte de produtos da ElevenLabs. Ele também é fundador da Hungrimind, uma plataforma de educação para desenvolvedores. Antes disso, foi Líder de Advocacia para Desenvolvedores na Agora e instrutor na Zero To Mastery.

Artigos relacionados

Crie com o áudio de IA da mais alta qualidade