Salta al contenuto

Rate limiting IA per la voce: concorrenza, code e 429

Pubblicato

AscoltaAscolta questo articolo

La maggior parte dei team applica il rate limiting IA per la voce come per altre API: limita le richieste al minuto, ritenta quando il server respinge e va avanti. Su ElevenLabs, però, questo modello si rompe già al primo picco di traffico, perché il vero limite che incontri è la concorrenza, non il numero di richieste.

Questa guida spiega perché la concorrenza è il vero vincolo e mostra i pattern lato client per restare entro i limiti. Dai pool di concorrenza limitata alla gestione elegante dei 429, fino all’equità multi-tenant e ai bucket token e leaky, ti proponiamo sistemi pratici da implementare. Ogni pattern è accompagnato da un esempio funzionante in TypeScript che puoi adattare.

Se crei agenti vocali, pipeline di narrazione o altri sistemi di produzione basati sui nostri modelli e vuoi scalare, questa guida fa per te.

In breve

  • Il rate limiting IA per la voce è controllo della concorrenza, non conteggio delle richieste al minuto.
  • Raggiungere il limite di rate limiting non blocca subito il traffico. Le richieste entrano invece in una coda prioritaria che aggiunge circa 50 ms.
  • Se si supera la capacità anche dopo la coda, viene restituito un errore HTTP 429.
  • I WebSocket aumentano notevolmente la capacità effettiva, perché solo la generazione attiva conta ai fini del limite.
  • I sistemi multi-tenant richiedono un ulteriore livello di equità: bucket per tenant, code ponderate, riserva di margine e suddivisione per chiavi per garantire l’isolamento.
  • Due header di risposta, current-concurrent-requests e maximum-concurrent-requests, ti dicono in tempo reale a che punto sei con il rate limiting IA.

Perché il limite è la concorrenza e non le richieste al minuto

La concorrenza è il numero di richieste in corso nello stesso momento. Le richieste al minuto rappresentano il throughput su una finestra temporale. Capire questa differenza è importante perché cambia la leva che ti permette di restare entro i limiti.

Quando usi uno dei modelli ElevenLabs, il carico sul server cresce con il numero di utenti concorrenti. La generazione audio occupa uno slot per tutta la durata della generazione, che varia in base alla lunghezza dell’input, al modello e al carico.

Un limite alle richieste al minuto non ti dice nulla su quanti slot sono occupati in questo momento, che è l’unica cosa che il server monitora.

I limiti per piano e per famiglia di modelli

Il tuo budget di concorrenza non è un numero unico. I limiti di concorrenza variano in base al piano e alla famiglia di modelli. Ad esempio, Speech to Text ha un limite più alto rispetto a Text to Speech, perché le richieste di trascrizione sono di solito più brevi e il sistema può gestirne di più contemporaneamente.

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
Priority
Free
3
Starter
4
Creator
5
Pro
5
Scale
5
Business
5
Enterprise
6

Il limite è per famiglia di modelli. Se usi Flash per gli agenti e Multilingual v2 per la narrazione, lavori su due budget separati. I valori attuali per piano e la sezione sulla concorrenza sono documentati nella pagina dei modelli.

Cosa succede quando raggiungi il limite di concorrenza?

Raggiungere il limite di concorrenza non blocca subito il traffico. Il sistema degrada in modo graduale tramite una coda prioritaria, passando al rifiuto totale solo quando si supera la capacità massima consentita.

Finché sei sotto il limite, le richieste vengono eseguite subito. Quando lo raggiungi, le richieste successive entrano in una coda ordinata in base alla priorità del tuo piano. La coda aggiunge di solito circa 50 ms di latenza, quindi un piccolo sforamento è quasi invisibile per gli utenti.

Se il sistema è ancora oltre la capacità dopo la coda, ricevi un HTTP 429. Questo è il segnale per rallentare invece di ritentare subito. Il livello di priorità nella tabella determina l’ordine delle tue richieste in coda rispetto al resto del traffico; i piani più alti svuotano la coda prima.

HTTP vs. WebSocket: come ciascuno incide sul limite

Il trasporto che scegli influisce direttamente su rate limiting e budget. La stessa conversazione può consumare quantità molto diverse del tuo budget di concorrenza a seconda che passi su HTTP o WebSocket.

Su HTTP, ogni richiesta conta singolarmente per tutta la sua durata. Su WebSocket, conta solo il tempo in cui il modello genera attivamente audio. Un WebSocket aperto ma inattivo di solito non viene conteggiato.

Per un agente vocale, una conversazione ha lunghi momenti in cui nessuno parla e il modello non genera nulla. Con HTTP occuperesti uno slot per tutta la durata della richiesta a ogni turno. Con WebSocket lo slot viene usato solo durante i millisecondi di generazione attiva, quindi uno slot viene condiviso tra molte conversazioni.

Consulta la guida WebSocket TTS in tempo reale per i dettagli sul protocollo. Per traffico interattivo, i WebSocket sono la scelta migliore.

Perché ~5 di concorrenza possono gestire ~100 trasmissioni

La matematica della concorrenza è controintuitiva finché non consideri il tempo di riproduzione. La generazione è molto più veloce della riproduzione e uno slot è occupato solo mentre si genera audio. Questo è proprio ciò che permette a un piccolo budget di servire un pubblico ampio.

Una richiesta che impiega una frazione di secondo a generare produce diversi secondi di audio che l’ascoltatore poi riproduce, e durante la riproduzione lo slot si libera e diventa disponibile per altri.

Come regola generale, un limite di concorrenza di 5 può supportare circa 100 trasmissioni audio simultanee. Il numero esatto dipende dalla voce, dalla cadenza e da quanta pausa c’è tra le frasi.

Gli header che spiegano a che punto sei

Non serve dedurre a occhio la tua posizione rispetto al limite. Ogni risposta contiene due numeri che puoi usare per misurare il margine invece di stimare.

Controlla questi due header:

  • richieste simultanee attuali: quante richieste sono in corso in questo momento?
  • numero massimo di richieste simultanee: il tuo limite per quella famiglia di modelli.

Insieme, questi header ti danno una panoramica in tempo reale del tuo utilizzo attuale e della capacità disponibile. Non dovresti dover andare a tentativi prima di incontrare i limiti IA.

Strategie lato client per il rate limiting IA

Ci sono quattro primitive che coprono quasi tutti gli scenari di rate limiting IA:

  • Token bucket: se ci sono token disponibili, permette alle richieste di procedere. La capacità si ricarica nel tempo, così puoi gestire brevi picchi senza superare i limiti.
  • Leaky bucket: cerca di rendere il traffico in ingresso più regolare, con una velocità di uscita fissa, evitando che i picchi improvvisi sovraccarichino i sistemi a valle.
  • Pool di concorrenza limitata: limita il numero totale di richieste attive contemporaneamente, così non superi mai i limiti di concorrenza.
  • Backoff esponenziale con jitter completo: aumenta progressivamente il tempo tra i tentativi falliti per evitare che tutti i client ritentino insieme.

Le sezioni qui sotto mostrano come costruirle una alla volta, partendo da quella che corrisponde più direttamente al limite di concorrenza.

Tutti gli esempi qui sotto assumono un singolo client, inizializzato una sola volta:

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

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

Concorrenza limitata: la primitiva che corrisponde al limite

Poiché il server misura la concorrenza, il controllo più diretto lato client è un pool di worker limitato che imposta un tetto alle richieste in corso. Imposta il limite leggermente sotto quello del tuo piano per lasciare margine alla coda prioritaria e al 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

Token bucket: consenti i picchi, limita la media

Un token bucket contiene fino a capacity token e si ricarica a refillRate token al secondo. Ogni richiesta consuma un token, quindi il bucket permette brevi picchi fino alla sua dimensione, ma limita la velocità media nel tempo.

È lo strumento giusto per gestire l’arrivo improvviso di un gruppo di lavori, così non lanci tutto insieme e non fai impennare la concorrenza.

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

Leaky bucket: mantieni un flusso costante

In alcuni casi non vuoi tollerare picchi. Un leaky bucket accetta lavoro a una velocità fissa e costante, indipendentemente da quanto sia irregolare l’input. È la scelta migliore quando il sistema a valle preferisce un carico regolare e prevedibile rispetto a picchi occasionali.

Ad esempio, quando vuoi restare ben dentro un piccolo budget di concorrenza condiviso con altri servizi.

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 esponenziale con jitter completo

Quando una richiesta fallisce con uno status ritentabile, ritentare subito peggiora la situazione. Il backoff distanzia i tentativi, e il jitter completo randomizza ogni attesa su tutto l’intervallo, evitando che molti client ritentino insieme e ricreino il picco che ha causato il problema.

L’esempio qui sotto fa riferimento a RetryableError, una piccola classe che contiene lo status fallito e l’eventuale valore Retry-After. È definita nella sezione sulla gestione elegante dei 429 qui sotto.

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

Gestione elegante dei 429: cosa fare quando raggiungi il limite

Un 429 significa che hai superato la capacità anche dopo la coda prioritaria, quindi la risposta corretta è rallentare invece di ritentare più aggressivamente. Ci sono quattro modi per gestirlo. Gestirlo bene si riduce a quattro strategie:

  • Rilevamento
  • Rispetto di Retry-After
  • Segnalazione della pressione
  • Evitare tempeste di retry con un circuit breaker

Vediamole più nel dettaglio.

La prima è il rilevamento. Considera HTTP 429 (e i temporanei 500, 502, 503 e 504) come ritentabili, mentre 400, 401, 403 e 422 come non ritentabili; ritentare una richiesta malformata o non autorizzata non funziona e spreca solo uno slot.

La seconda è rispettare Retry-After. Se la risposta contiene quell’header, rispettalo esattamente invece di calcolare un’attesa tua. Il server ti dice quando prevede di avere capacità, e lo sa meglio della tua formula esponenziale. Usa il backoff con jitter solo se l’header manca.

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

La terza è segnalare la pressione. Non lasciare che i retry si accumulino senza controllo. Se la profondità della coda o il margine residuo indicano che non puoi servire una nuova richiesta a breve, rifiutala subito con un segnale chiaro invece di accettare lavoro che non puoi gestire.

La quarta è evitare tempeste di retry con un circuit breaker. Se i fallimenti superano una soglia, apri il circuito e fallisci subito per una finestra di raffreddamento invece di inviare richieste che sai già che falliranno. Dopo la finestra, invia qualche richiesta di prova; se vanno a buon fine, richiudi il 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();
    }
  }
}

Pattern di quota multi-tenant per il rate limiting IA

Finora abbiamo considerato una singola applicazione su un solo budget. Se costruisci un SaaS su ElevenLabs, il problema cambia: il tuo budget di concorrenza è condiviso tra tutti i tuoi clienti e un tenant che lancia un batch non deve bloccare il traffico live degli altri. Serve un livello di equità tra i tenant e il limite unico a monte.

La base sono i token bucket per tenant. Dai a ogni tenant un bucket dimensionato in base ai suoi diritti e accetta una richiesta solo se sia il bucket del tenant sia il limiter globale lo consentono.

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

I bucket tengono ogni tenant sotto controllo, ma non decidono chi ha la precedenza quando i tenant competono per il limiter globale. Per questo usa code ponderate.

Non servire in ordine di arrivo, perché un picco di un tenant potrebbe monopolizzare gli slot. Mantieni una coda per tenant e distribuisci in proporzione al peso di ciascuno, così un tenant pagante ottiene una quota maggiore rispetto a uno gratuito.

Oltre all’equità, riserva sempre un margine. Non lasciare che il traffico normale consumi il 100% del limite di concorrenza. Tieni da parte una frazione, ad esempio il 15-20%, come buffer per richieste interattive sensibili alla latenza e per la coda prioritaria.

Quando l’equità su un solo budget non basta più, suddividi per workspace o chiavi. Un unico budget di concorrenza prima o poi diventa il collo di bottiglia, anche se lo dividi in modo equo.

A quel punto, separa i carichi di lavoro su workspace o API key distinti, ognuno con il proprio budget: ad esempio, una chiave per il traffico agenti in tempo reale e un’altra per la narrazione in background, così l’accumulo di narrazione non tocca la capacità degli agenti.

I workspace ti permettono anche di applicare restrizioni di ambito, quote di credito e controlli per chiave, descritti nella documentazione sull’autenticazione.

Monitorare l’utilizzo della concorrenza

Nulla di tutto questo è regolabile senza misurazione; non puoi gestire il margine che non misuri. Registra current-concurrent-requests e maximum-concurrent-requests su ogni risposta, etichettati per famiglia di modelli, ed emetti il rapporto di utilizzo come gauge.

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

Quattro segnali da monitorare:

  • Utilizzo (current / maximum).
  • Tasso di 429 rispetto al totale delle richieste.
  • Profondità dei retry, cioè il numero di tentativi per ogni richiesta logica.
  • Time-to-first-audio, misurato dalla tua applicazione, non dai dati di inferenza del modello. Consulta la sezione sulla latenza per cosa include il TTFA.

Un sistema sano mantiene l’utilizzo ben sotto la saturazione e vede i 429 solo in rari picchi. Monitorare questi segnali ti dà visibilità sulla pressione del rate limiting, molto prima che diventi un problema di interruzione.

Quando scalare oltre il rate limiting lato client

I pattern lato client possono fare molto, ma la domanda costante prima o poi li supera. Quando succede, è il momento di cambiare per migliorare sia i costi che l’efficienza.

Ognuno dei seguenti passaggi ti dà capacità extra.

Inizia passando da HTTP a WebSocket per il traffico interattivo. Se i tuoi agenti o i casi d’uso live funzionano su HTTP, passare a WebSocket cambia il conteggio: solo la generazione attiva conta. Per i carichi conversazionali spesso questo moltiplica la capacità effettiva senza cambiare piano, perché il tempo inattivo non consuma slot.

Se i tuoi picchi sono improvvisi ma il carico medio rientra nel budget, un token o leaky bucket più un pool limitato appiattiscono i picchi sulla media.

Poi scegli il modello giusto. Una generazione più veloce occupa ogni slot per meno tempo, aumentando il numero di trasmissioni che un limite fisso può sostenere. Eleven Flash v2.5 è l’opzione a latenza più bassa per il lavoro in tempo reale; abbinalo a un Clonazione vocale istantanea o a una voce predefinita per evitare il sovraccarico delle Professional Voice Clones.

Solo dopo valuta l’upgrade del piano. Se la domanda costante supera davvero il budget anche dopo aver ottimizzato il client, un piano superiore aumenta sia il limite di concorrenza per modello sia la priorità in coda. Confronta i livelli nella pagina prezzi API.

Se hai bisogno di limiti superiori a quelli pubblicati, i piani Enterprise offrono limiti di concorrenza più alti e personalizzati e la massima priorità in coda. Sono disponibili controlli aggiuntivi per casi d’uso idonei, come la whitelist IP (in anteprima Enterprise) e modalità senza conservazione dati. Contatta il tuo account manager per aumentare i limiti.

Ricapitolando cosa ricordare sul rate limiting IA

L’errore principale è trattare il rate limiting IA per la voce come conteggio delle richieste. Qui conta il controllo della concorrenza. Il numero che decide se ce la fai è quante richieste stanno generando audio nello stesso istante e per quanto tempo ciascuna occupa uno slot.

Costruisci il client su questa base.

Limita le richieste in corso con un pool, regola l’ammissione con un token o leaky bucket, ritenta con backoff esponenziale e jitter, rispetta Retry-After e interrompi il circuito prima che si crei una tempesta di retry.

Per i sistemi multi-tenant, aggiungi bucket per tenant, equità ponderata, margine riservato e suddivisione per isolamento. Monitora gli header current-concurrent-requests e maximum-concurrent-requests e segnala il trend di utilizzo, non solo i fallimenti.

Quando hai davvero bisogno di più capacità, segui l’ordine: prima WebSocket e client ottimizzato, poi il modello giusto, poi upgrade del piano e infine limiti Enterprise.

Crea applicazioni vocali con ElevenAPI

Un rate limiting IA di livello produzione parte dal trasporto giusto, dal modello giusto e dagli header che ti dicono esattamente a che punto sei.

ElevenAPI offre modelli a bassa latenza come Eleven Flash v2.5, streaming WebSocket in tempo reale, Speech to Text e API Text to Speech, e header di concorrenza per risposta che ti permettono di creare agenti vocali che scalano entro i tuoi limiti.

Insieme alle strategie di rate limiting IA di questo articolo, offri esperienze vocali reattive mantenendo prestazioni prevedibili (anche sotto carico).

Esplora ElevenAPI per vedere tutti i modelli in azione, oppure crea un account per iniziare a costruire con ElevenLabs.

FAQ sul rate limiting IA

Articoli simili

Crea con l'audio IA della massima qualità