Vai al contenuto

Integrazione API Text to Speech: streaming, batching, retry

Pubblicato
Ultimo aggiornamento

AscoltaAscolta questo articolo

Integrare un'API Text to Speech è semplice… dopo aver preso alcune decisioni concrete: quale modalità di trasferimento usare, come scegliere un modello e un formato di output, come usare lo streaming, come gestire volumi elevati senza superare il limite di concorrenza, come usare cache e retry per non pagare mai due volte il rendering dello stesso audio e come confrontare il time-to-first-byte con quello di un altro provider.

Per aiutarti a integrare un'API Text to Speech, abbiamo analizzato tutte queste decisioni architetturali e cosa fare in ciascun caso. Questa guida ti aiuterà a integrare l'API Text to Speech di ElevenLabs API Text to Speech e a scalare, con snippet di codice che puoi inserire direttamente in produzione per iniziare subito.

Per una panoramica completa dei concetti citati qui, consulta le nostre guide su come funziona lo streaming audio, come ottimizzare la latenza, e la panoramica dei modelli ElevenLabs

In sintesi

  • Esiste un unico endpoint dell'API Text to Speech di ElevenLabs, accessibile in tre modi: conversione batch, streaming HTTP e WebSocket stream-input.
  • Con HTTP, ogni richiesta in corso viene conteggiata nel limite di concorrenza, mentre con WebSocket conta solo la generazione attiva.
  • Limita il parallelismo appena al di sotto del limite del tuo piano e memorizza nella cache un hash di ogni parametro che influisce sull'output, così non ti verrà mai addebitato due volte lo stesso testo.
  • Riprova le richieste 429 e 5xx con backoff esponenziale e jitter completo, per ridurre il carico prima di raggiungere il limite di concorrenza.

Tre modi per integrare l'API Text to Speech

Esiste un unico endpoint Text to Speech, ma il modo in cui lo integri determina latenza, complessità e costi. 

La stessa chiamata POST /v1/text-to-speech/{voice_id} funziona in tre modalità, ognuna adatta a un caso d'uso leggermente diverso. Ecco una panoramica dei tre modi per integrare l'API Text to Speech:

  • Batch (convert) è l'integrazione più semplice: invii una richiesta e ricevi una risposta audio. È l'opzione meno complessa e ha il time-to-first-audio più elevato, perché l'intera clip viene sintetizzata prima che venga restituito qualsiasi byte.
  • Lo streaming HTTP (stream) mantiene la stessa richiesta, ma suddivide la risposta in chunk: aggiungi /stream al path, chiami il metodo stream e l'audio viene restituito come risposta chunked. Il codice è quasi identico, ma la latenza percepita è molto più bassa.
  • Il WebSocket (stream-input) mantiene una connessione persistente: invii il testo in modo incrementale e ricevi chunk audio man mano. È pensato per gli agenti interattivi e per convertire in parlato l'output di un LLM mentre produce i token, prima che la frase sia completata.

Lo streaming non fa generare l'audio più velocemente al modello: il tempo di inferenza non cambia. Ciò che cambia è quando ricevi il primo chunk: viene inviato prima che l'intera clip sia pronta, quindi l'attesa percepita dall'utente è più breve, anche se il lavoro totale è lo stesso.

Tabella decisionale: batch, streaming e WebSocket

Quando scegli tra questi tre metodi, devi considerare diversi fattori.

Come guida rapida: scegli il batch per il rendering offline, lo streaming HTTP per testo noto che un utente sta aspettando e WebSocket per gli agenti e la conversione in parlato in tempo reale da LLM. 

La tabella seguente illustra i compromessi nelle dimensioni che contano quando operi su larga scala.

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

Con HTTP, sia in batch sia in streaming, ogni richiesta in corso viene conteggiata nel limite di concorrenza del tuo piano per tutta la sua durata. Con WebSocket viene conteggiato solo il tempo in cui il modello genera attivamente l'audio; un socket aperto ma inattivo non comporta praticamente costi.

Per un agente vocale a cascata che mantiene aperta una connessione per l'intera conversazione ma genera audio solo durante i turni dell'agente, questa differenza è notevole ed è il motivo principale per usare WebSocket quando crei agenti. Il protocollo completo è documentato nella guida al WebSocket Text to Speech in tempo reale.

Scelta di un modello e del formato di output

Due scelte determinano l'audio restituito dall'integrazione della tua API TTS. La prima è il modello, che definisce qualità e velocità. La seconda è il formato di output, che definisce container, bitrate e frequenza di campionamento.

Fare le scelte giuste fin dall'inizio ti permette di gestire al meglio tutti gli aspetti successivi, come la latenza e la compatibilità con la telefonia.

Modelli

Offriamo diversi modelli Text to Speech. Non sono classificati dal migliore al peggiore: ciascuno presenta compromessi diversi.

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

Nota: il valore di ~75 ms riguarda l'inferenza del modello in condizioni rappresentative, senza includere la latenza di rete e dell'applicazione. Aumenta con input più lunghi e sotto carico. Misura sempre dalla tua applicazione, non basarti su un valore di benchmark.

I modelli Flash sono più piccoli e usano approssimazioni più aggressive per ridurre il tempo di inferenza. Eleven v3 e Multilingual v2 sono modelli più grandi, che dedicano più tempo a ogni carattere per produrre un output più ricco. Non esiste un'impostazione che offra la qualità di Eleven v3 alla velocità di Flash, perché quella qualità richiede calcolo aggiuntivo.

Per un flusso in tempo reale o per gli agenti, usa eleven_flash_v2_5: è l'opzione multilingue con la latenza più bassa. Per la narrazione, gli audiolibri o le voci fuori campo per il marketing, usa eleven_multilingual_v2 se vuoi un'alta fedeltà stabile, oppure eleven_v3 se ti servono massima espressività e un'ampia gamma emotiva. 

Quando la pronuncia è importante, ad esempio per numeri di telefono, date o valute, normalizza tu stesso i numeri nella tua applicazione prima che il testo raggiunga l'API. Scrivi per esteso la forma parlata desiderata. 

Normalizzarli autonomamente mantiene la pronuncia prevedibile tra i modelli ed evita di dipendere da valori predefiniti specifici del modello che potrebbero cambiare.

Formato di output

Il parametro output_format controlla il container, la frequenza di campionamento e il bitrate dell'audio restituito. I valori che userai più spesso:

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

Impostazioni vocali

Le seguenti impostazioni controllano la resa del parlato generato:

  • Stability: controlla il compromesso tra coerenza ed espressività. Valori più bassi producono un parlato più vario ed espressivo, mentre valori più alti rendono la resa più stabile e prevedibile.
  • SimilarityBoost: controlla quanto l'output segue da vicino la voce di riferimento.
  • Style: accentua lo stile di parlato naturale della voce quando viene aumentato.
  • useSpeakerBoost: aumenta la somiglianza con il parlante originale, con un lieve impatto sulla latenza.
  • Speed: regola il ritmo della resa rispetto al valore predefinito di 1.0.

Tra queste impostazioni, Stability ha in genere l'impatto maggiore sulla qualità percepita. Valori più bassi creano un output più espressivo ma meno coerente, mentre valori più alti privilegiano coerenza e prevedibilità.

Quando scegli una voce, la combinazione con la latenza più bassa è Flash abbinato a un Clone vocale istantaneo o a una voce predefinita; i cloni vocali professionali hanno un suono eccellente, ma aggiungono un overhead per generazione di cui dovresti tenere conto.

In questa guida, l'ID della voce di esempio è JBFqnCBsd6RMkjVDRZzb (George).

Integrazione dello streaming (HTTP e WebSocket)

In questa sezione affrontiamo gli aspetti pratici dell'integrazione dell'API Text to Speech. Vediamo come installare l'SDK, aprire uno stream e consumare l'audio man mano che arriva. Il path HTTP copre la maggior parte della riproduzione sul web e nelle app, mentre il path WebSocket copre gli agenti e l'output LLM in tempo reale.

Entrambi questi approcci presuppongono che il client ElevenLabs sia inizializzato come mostrato di seguito.

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

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

Il path di streaming apre uno stream e consuma i chunk man mano che arrivano. voiceId è il primo argomento posizionale, seguito da un oggetto di opzioni con chiavi 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
}

Per la variante WebSocket, connettiti a wss://api.elevenlabs.io/v1/text-to-speech/{voice_id}/stream-input, invia un primo messaggio contenente le impostazioni vocali e uno spazio iniziale, quindi invia messaggi di testo non appena sono disponibili e leggi i frame JSON restituiti, il cui campo audio contiene chunk codificati in base64.

Batch e limiti di concorrenza per un throughput elevato

L'integrazione ad alto throughput è regolata dalla concorrenza, ovvero dal numero di richieste che generano audio nello stesso istante. Ogni piano prevede un limite per famiglia di modelli. 

Ogni piano include un limite di concorrenza specifico:

  • Free: 4 richieste Flash simultanee.
  • Starter: 6 richieste Flash simultanee.
  • Creator: 10 richieste Flash simultanee.
  • Pro: 20 richieste Flash simultanee.
  • Scale e Business: 30 richieste Flash simultanee; i limiti Enterprise sono personalizzati.

I limiti di Multilingual v2 sono circa la metà di quelli indicati sopra.

Un pool limitato risolve il problema limitando il numero di richieste eseguite contemporaneamente:

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

Imposta MAX_CONCURRENCY leggermente al di sotto del limite del tuo piano, anziché esattamente al suo livello. Questo margine assorbe altro traffico che usa la stessa chiave e ti mantiene sotto la soglia che restituisce un 429.

Limiti di caratteri e suddivisione di testi lunghi

Ogni modello impone un limite ai caratteri accettati in una singola richiesta. Qualsiasi integrazione per contenuti lunghi deve suddividere il testo e ricomporre l'audio. 

Ecco i limiti di caratteri per richiesta di ciascun modello:

  • Flash v2.5: accetta fino a 40.000 caratteri per richiesta.
  • Flash v2: accetta fino a 30.000 caratteri per richiesta.
  • Multilingual v2: accetta fino a 10.000 caratteri per richiesta.
  • Eleven v3: accetta fino a 5.000 caratteri per richiesta.

Qualsiasi testo più lungo deve essere suddiviso in più richieste. Cerca di dividere in corrispondenza dei confini delle frasi, così la prosodia resta naturale tra un chunk e l'altro.

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 i chunk nell'ordine corretto e concatena l'audio. Per le narrazioni lunghe, in cui ogni chunk è indipendente, le due parti si combinano direttamente: passa l'output di splitText al pool limitato mostrato sopra e lascia che gestisca il resto.

Caching e idempotenza

L'output Text to Speech è sufficientemente deterministico da rendere superfluo generare di nuovo lo stesso testo con la stessa voce, modello e impostazioni. Memorizza il risultato nella cache usando come chiave un hash degli input che influenzano l'audio; la stessa chiave può anche fungere da token di idempotenza nei retry.

Ecco come fare entrambe le cose.

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 regola che rende possibile questo approccio è che ogni parametro che modifica l'audio deve essere incluso nella chiave, comprese outputFormat e le impostazioni vocali. Se fatto correttamente, la stessa chiave funge anche da token di idempotenza. Quando un client riprova una richiesta già riuscita, restituisci i byte in cache anziché generare di nuovo l'audio.

Gestione degli errori e rate limit (429)

Un client di produzione necessita di retry con backoff e jitter, oltre a una gestione diversa in base allo status code, perché alcuni errori meritano un nuovo tentativo e altri no. 

La tabella seguente associa ogni status all'azione corretta e la sezione spiega perché un 429 è un limite flessibile, non una barriera rigida.

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 non è una barriera rigida, ed è utile conoscerne il meccanismo. Quando superi il limite di concorrenza, le richieste vengono prima messe in coda in base alla priorità, aggiungendo in genere circa 50 ms. Ricevi un 429 soltanto se anche dopo questo passaggio sei ancora oltre la capacità. 

La risposta include anche gli header current-concurrent-requests e maximum-concurrent-requests, che mostrano il margine disponibile in tempo reale: puoi leggerli e ridurre il carico prima di raggiungere il limite.

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

Quando hai bisogno di più margine anziché di retry migliori, passa a un piano superiore. I clienti Enterprise possono richiedere limiti più elevati tramite il proprio account manager.

Benchmark di latenza e time-to-first-byte

La latenza dipende dalla tua regione, dal tuo input e dal carico corrente: per questo, l'unico dato di latenza affidabile è quello misurato nel tuo ambiente. 

Questa sezione ti fornisce un test per misurare il time-to-first-byte (TTFB) dell'endpoint di streaming Flash, strutturato in modo da poter usare lo stesso harness con un altro provider e confrontarli in condizioni identiche.

Consideralo una metodologia, non un risultato pubblicato. Una singola esecuzione non garantisce nulla. 

Ecco alcuni aspetti importanti da considerare quando misuri la latenza di un'integrazione API Text to Speech:

  • Includi il round-trip di rete: il TTFB dipende dalla tua posizione geografica e dal cluster più vicino del provider, quindi esegui il test dalla posizione in cui vengono normalmente eseguiti i tuoi server.
  • Escludi un'esecuzione di warmup: la prima richiesta su una connessione fredda è più lenta e può alterare i risultati.
  • Mantieni fissi gli input: la lunghezza dell'input, la voce, il modello e il carico influiscono tutti sul risultato, quindi mantienili identici tra i vari provider.
  • Riporta una distribuzione: i valori variano da un'esecuzione all'altra, quindi pubblica la mediana e il p95 anziché un singolo valore.

Tenendo conto di questi aspetti, sei pronto per il benchmark.

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

Per confrontare un altro provider, scrivi una funzione con la stessa struttura. Quindi esegui entrambe con un piccolo runner che scarta una chiamata di warmup, raccoglie circa 20 campioni temporizzati e distanziati per evitare collisioni reciproche e riporta la mediana e il p95 in millisecondi.

Un confronto equo dipende dal controllo delle variabili. 

Esegui entrambi i provider dalla stessa macchina e rete, idealmente da un server nella regione in cui effettivamente effettui il deployment anziché da un laptop con una connessione residenziale. Usa lo stesso testo di input e mantieni l'audio breve, così l'inferenza del modello incide più della durata della generazione. Riporta la mediana e il p95 su molte esecuzioni, perché una singola misurazione è rumore. 

Ricorda che il TTFB su Internet pubblico include 20-200 ms di round-trip di rete che non dipendono dal modello. Forniamo i modelli da cluster in Nord America, Europa e Sud-est asiatico, instradando le richieste verso il cluster più vicino: posiziona quindi il client di test di conseguenza, altrimenti misurerai soprattutto la distanza dal data center.

Punti chiave per l'integrazione della tua API Text to Speech

Un'integrazione API Text to Speech per la produzione si riduce a una serie di decisioni importanti.

Se le fai nel modo giusto, tutto il resto andrà di conseguenza:

  • Scegli il modello in base al caso d'uso: usa Flash v2.5 per tutto ciò che è interattivo e un modello a fedeltà più elevata come Multilingual v2 o Eleven v3 per il rendering offline, dove la latenza è meno importante.
  • Usa lo streaming quando un utente è in attesa: usa lo streaming HTTP per testo noto e WebSocket per gli agenti, così il tempo di inattività non incide sul budget di concorrenza.
  • Limita il parallelismo al limite del tuo piano: limita le richieste simultanee appena sotto il limite del piano e usa nella cache un hash di ogni parametro che influisce sull'output, così lo stesso audio non viene mai addebitato due volte.
  • Riprova le richieste 429 e 5xx con backoff esponenziale e jitter completo: riduci il carico in caso di 429 e 5xx con jitter completo e monitora gli header di concorrenza per capire quanto sei vicino al limite.
  • Suddividi i testi lunghi ai confini delle frasi: dividi ai confini delle frasi entro il limite di caratteri di ciascun modello, così la prosodia si conserva tra un chunk e l'altro.

Se vuoi approfondire ulteriormente, dai un'occhiata alla guida pratica allo streaming, al concetto di streaming audio, all'autenticazione e ai token monouso per l'uso lato client.

Crea la tua integrazione Text to Speech con ElevenAPI

Dopo aver letto questa guida, hai tutti i pattern necessari per un'integrazione API Text to Speech pronta per la produzione. Streaming, batch, caching, retry e perfino benchmarking: ora sei pronto a metterli in pratica. 

Inizia scoprendo di più sull'API Text to Speech oppure registrati per effettuare oggi la tua prima chiamata con ElevenAPI.

Domande frequenti sull'integrazione dell'API Text to Speech

Articoli simili

Crea con l'audio IA della massima qualità