Aller au contenu

Intégration de l’API Text to Speech : streaming, traitement par lots, gestion des erreurs

Publié
Dernière mise à jour

ÉcouterÉcouter cet article

Intégrer une API Text to Speech est simple… après quelques décisions concrètes : choisir le mode de transfert, un modèle et un format de sortie, mettre en place le streaming, traiter de gros volumes sans dépasser votre limite de requêtes simultanées, mettre en cache et relancer les requêtes pour ne jamais payer deux fois la génération du même audio, et comparer le délai avant réception du premier octet avec d'autres solutions vocales.

Pour vous aider à intégrer une API Text to Speech, nous détaillons chacune de ces décisions d'architecture et les actions à mener. Ce guide vous aidera à intégrer l'API ElevenLabs Text to Speech API et à passer à l'échelle, grâce à des extraits de code que vous pouvez directement déployer en production.

Pour approfondir les concepts évoqués ici, consultez nos guides sur le streaming audio, l'optimisation de la latence, ainsi que la présentation des modèles ElevenLabs

En résumé

  • L'API ElevenLabs Text to Speech propose un seul endpoint, accessible de trois manières : conversion par lot, streaming HTTP et WebSocket stream-input.
  • Avec HTTP, chaque requête en cours compte dans votre limite de requêtes simultanées ; avec WebSocket, seule la génération active est comptabilisée.
  • Limitez le parallélisme juste en dessous de la limite de votre plan et mettez en cache un hash de chaque paramètre influant sur la sortie, afin de ne jamais facturer deux fois le même texte.
  • Relancez les erreurs 429 et 5xx avec un backoff exponentiel et un jitter complet pour réduire la charge avant d'atteindre la limite de requêtes simultanées.

Trois façons d'intégrer l'API Text to Speech

Il n'existe qu'un endpoint Text to Speech, mais son intégration détermine votre latence, votre complexité et vos coûts. 

Le même appel POST /v1/text-to-speech/{voice_id} se décline sous trois formes, chacune adaptée à un besoin légèrement différent. Voici ces trois modes d'intégration de l'API Text to Speech :

  • La conversion par lot (convert) est l'intégration la plus simple : vous envoyez une requête et recevez une réponse audio. C'est l'option la moins complexe, mais celle dont le délai avant le premier audio est le plus élevé, car le clip complet est synthétisé avant le renvoi du moindre octet.
  • Le streaming HTTP (stream) conserve la même requête, mais segmente la réponse : ajoutez /stream au chemin, appelez la méthode stream et recevez l'audio sous forme de réponse segmentée. Le code est presque identique, mais la latence perçue est nettement plus faible.
  • Le WebSocket (stream-input) maintient une connexion persistante : vous envoyez le texte progressivement et recevez les segments audio au fil de l'eau. Il est conçu pour les agents interactifs et pour convertir en parole la sortie d'un LLM à mesure que les tokens sont produits, avant même la fin de la phrase.

Le streaming n'accélère pas la génération audio par le modèle ; le temps d'inférence reste identique. Il change le moment où vous recevez le premier segment : celui-ci est envoyé avant la fin du clip complet. L'attente perçue par l'utilisateur est donc plus courte, même si le travail total reste le même.

Tableau de décision : conversion par lot, streaming ou WebSocket

Plusieurs facteurs sont à prendre en compte pour choisir entre ces trois méthodes.

En bref : choisissez la conversion par lot pour le rendu hors ligne, le streaming HTTP pour un texte connu qu'un utilisateur attend, et le WebSocket pour les agents et la conversion en direct de la sortie d'un LLM en parole. 

Le tableau ci-dessous détaille les compromis selon les dimensions qui comptent à grande échelle.

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

Avec HTTP, qu'il s'agisse de conversion par lot ou de streaming, chaque requête en cours compte dans la limite de requêtes simultanées de votre plan pendant toute sa durée. Avec un WebSocket, seul le temps durant lequel le modèle génère activement de l'audio est comptabilisé ; un socket ouvert mais inactif ne coûte pratiquement rien.

Pour un agent vocal en cascade qui maintient une connexion ouverte durant toute une conversation, mais ne génère de l'audio que lorsque l'agent prend la parole, cette différence est considérable. C'est la principale raison d'utiliser les WebSockets pour créer des agents. Le protocole complet est documenté dans le guide WebSocket Text to Speech en temps réel.

Choisir un modèle et un format de sortie

Deux choix déterminent l'audio renvoyé par votre intégration d'API TTS. D'abord, le modèle, qui définit la qualité et la vitesse. Ensuite, le format de sortie, qui détermine le conteneur, le débit binaire et la fréquence d'échantillonnage.

Faites les bons choix dès le départ pour que tout le reste, notamment la latence et la compatibilité avec la téléphonie, s'aligne naturellement.

Modèles

Nous proposons plusieurs modèles Text to Speech. Ils ne sont pas classés du meilleur au moins bon : chacun présente des compromis différents.

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

À noter : la valeur d'environ 75 ms correspond à l'inférence du modèle dans des conditions représentatives, hors latence réseau et applicative. Elle augmente avec des entrées plus longues et en cas de charge. Mesurez toujours depuis votre application, et non à partir d'une valeur de benchmark.

Les modèles Flash sont plus petits et utilisent des approximations plus poussées pour réduire le temps d'inférence. Eleven v3 et Multilingual v2 sont des modèles plus grands, qui consacrent davantage de temps à chaque caractère pour produire une sortie plus riche. Aucun réglage ne permet d'obtenir la qualité d'Eleven v3 à la vitesse de Flash, car cette qualité repose sur des calculs supplémentaires.

Pour un parcours en temps réel ou destiné à un agent, utilisez eleven_flash_v2_5 ; c'est l'option multilingue offrant la plus faible latence. Pour la narration, les livres audio ou les voix off marketing, utilisez eleven_multilingual_v2 si vous recherchez une haute fidélité stable, ou eleven_v3 si vous avez besoin d'une expressivité et d'une palette émotionnelle maximales. 

Lorsque la prononciation compte, par exemple pour des numéros de téléphone, des dates ou des montants, normalisez vous-même les nombres dans votre application avant que le texte n'atteigne l'API. Écrivez la forme orale souhaitée. 

Cette normalisation garantit une prononciation prévisible d'un modèle à l'autre et évite de dépendre de valeurs par défaut propres aux modèles, qui peuvent évoluer.

Format de sortie

Le paramètre output_format contrôle le conteneur, la fréquence d'échantillonnage et le débit binaire de l'audio renvoyé. Voici les valeurs que vous utiliserez le plus souvent :

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

Paramètres vocaux

Les paramètres suivants contrôlent le rendu de la parole générée :

  • Stability : contrôle l'équilibre entre cohérence et expressivité. Des valeurs faibles produisent une parole plus variée et expressive ; des valeurs élevées offrent un rendu plus régulier et prévisible.
  • SimilarityBoost : contrôle la fidélité de la sortie à la voix de référence.
  • Style : amplifie le style d'expression naturel de la voix lorsqu'il est augmenté.
  • useSpeakerBoost : renforce la ressemblance avec le locuteur d'origine, au prix d'une légère augmentation de la latence.
  • Speed : ajuste le débit autour de la valeur par défaut de 1.0.

Parmi ces paramètres, Stability a généralement l'impact le plus important sur la qualité perçue. Des valeurs faibles produisent un rendu plus expressif mais moins constant, tandis que des valeurs élevées privilégient la cohérence et la prévisibilité.

Pour choisir une voix, l'association offrant la plus faible latence est Flash avec un Clonage de Voix Instant ou une voix par défaut ; les clonages de voix professionnels offrent un excellent rendu, mais ajoutent une surcharge par génération à prendre en compte.

Dans ce guide, l'identifiant de voix utilisé comme exemple est JBFqnCBsd6RMkjVDRZzb (George).

Intégration du streaming (HTTP et WebSocket)

Cette section aborde les aspects pratiques de l'intégration d'une API Text to Speech : installation du SDK, ouverture d'un flux et traitement de l'audio à mesure de son arrivée. Le parcours HTTP couvre la plupart des usages de lecture sur le web et dans les applications, tandis que WebSocket répond aux besoins des agents et des sorties de LLM en direct.

Ces deux approches supposent que vous avez initialisé le client ElevenLabs ci-dessous.

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

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

Le parcours de streaming ouvre un flux et traite les segments à mesure de leur arrivée. voiceId est le premier argument positionnel, suivi d'un objet d'options avec des clés en camelCase (modelId, outputFormat, voiceSettings) :

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

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

Pour la variante WebSocket, connectez-vous à wss://api.elevenlabs.io/v1/text-to-speech/{voice_id}/stream-input, envoyez un premier message contenant vos paramètres vocaux et un espace initial, puis envoyez des messages texte à mesure qu'ils deviennent disponibles et lisez les trames JSON dont le champ audio contient des segments encodés en base64.

Traitement par lot et limites de requêtes simultanées pour un débit élevé

Une intégration à haut débit est régie par les requêtes simultanées, c'est-à-dire le nombre de requêtes générant de l'audio au même instant. Chaque plan impose une limite par famille de modèles. 

Chaque plan comprend sa propre limite de requêtes simultanées :

  • Free : 4 requêtes Flash simultanées.
  • Starter : 6 requêtes Flash simultanées.
  • Creator : 10 requêtes Flash simultanées.
  • Pro : 20 requêtes Flash simultanées.
  • Scale et Business : 30 requêtes Flash simultanées ; les limites Enterprise sont personnalisées.

Les limites de Multilingual v2 représentent environ la moitié de celles indiquées ci-dessus.

Un pool limité atténue ce problème en plafonnant le nombre de requêtes exécutées simultanément :

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

Définissez MAX_CONCURRENCY légèrement en dessous de la limite de votre plan plutôt qu'exactement à cette limite. Cette marge absorbe tout autre trafic utilisant la même clé et vous maintient sous le seuil de renvoi d'une erreur 429.

Limites de caractères et découpage des textes longs

Chaque modèle plafonne le nombre de caractères acceptés dans une requête unique. Toute intégration de contenu long doit découper le texte puis assembler l'audio. 

Voici les limites de caractères par requête pour chaque modèle :

  • Flash v2.5 : accepte jusqu'à 40 000 caractères par requête.
  • Flash v2 : accepte jusqu'à 30 000 caractères par requête.
  • Multilingual v2 : accepte jusqu'à 10 000 caractères par requête.
  • Eleven v3 : accepte jusqu'à 5 000 caractères par requête.

Tout texte plus long doit être réparti sur plusieurs requêtes. Privilégiez les limites de phrases afin de préserver la prosodie à la jonction entre les segments.

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

Générez les segments dans l'ordre et concaténez l'audio. Pour une narration longue dont chaque segment est indépendant, les deux éléments s'assemblent directement : transmettez la sortie de splitText au pool limité ci-dessus et laissez-le gérer le reste.

Mise en cache et idempotence

La sortie Text to Speech est suffisamment déterministe pour que régénérer le même texte avec la même voix, le même modèle et les mêmes paramètres soit inutile. Mettez en cache le résultat à l'aide d'un hash des entrées qui influent sur l'audio ; cette même clé sert aussi de jeton d'idempotence lors des nouvelles tentatives.

Voici comment procéder.

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 règle essentielle est que chaque paramètre qui modifie l'audio doit figurer dans la clé, y compris outputFormat et les paramètres vocaux. Si elle est correctement construite, cette même clé sert de jeton d'idempotence. Lorsqu'un client relance une requête déjà aboutie, renvoyez les octets mis en cache au lieu de générer à nouveau l'audio.

Gestion des erreurs et limites de débit (429)

Un client de production doit relancer les requêtes avec backoff et jitter, et appliquer un traitement adapté à chaque code de statut, car certains échecs méritent une nouvelle tentative et d'autres non. 

Le tableau ci-dessous associe chaque statut à l'action appropriée et explique pourquoi une erreur 429 constitue une limite souple, plutôt qu'un mur infranchissable.

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

Une erreur 429 n'est pas un mur infranchissable ; il est utile d'en comprendre le mécanisme. Lorsque vous dépassez la limite de requêtes simultanées, les requêtes sont d'abord mises en file d'attente selon leur priorité, ce qui ajoute généralement environ 50 ms. Vous ne recevez une erreur 429 que si vous restez au-delà de la capacité après cela. 

La réponse contient également les en-têtes current-concurrent-requests et maximum-concurrent-requests, qui indiquent votre marge disponible en temps réel. Vous pouvez les lire et réduire la charge avant d'atteindre la 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");
}

Si vous avez besoin de davantage de marge plutôt que d'un meilleur comportement de relance, passez à un plan supérieur. Les clients Enterprise peuvent demander des limites plus élevées auprès de leur gestionnaire de compte.

Mesurer la latence et le délai avant le premier octet

La latence dépend de votre région, de votre entrée et de la charge actuelle. Le seul chiffre de latence fiable est donc celui que vous mesurez dans votre propre environnement. 

Cette section couvre le délai avant le premier octet (TTFB) pour l'endpoint de streaming Flash. Sa structure vous permet d'appliquer le même protocole de test à d'autres solutions vocales et de les comparer dans des conditions identiques.

Considérez cette approche comme une méthodologie, non comme un résultat publié. Une seule exécution ne garantit rien. 

Voici quelques points importants pour mesurer la latence d'une intégration d'API Text to Speech :

  • Incluez l'aller-retour réseau : le TTFB dépend de votre localisation et du cluster le plus proche du fournisseur ; exécutez donc le test depuis l'emplacement habituel de vos serveurs.
  • Écartez une exécution de préchauffage : la première requête sur une connexion inactive est plus lente et peut fausser vos résultats.
  • Gardez les entrées fixes : la longueur de l'entrée, la voix, le modèle et la charge influencent tous le résultat ; conservez-les donc à l'identique entre les solutions.
  • Présentez une distribution : les résultats varient d'une exécution à l'autre ; publiez la médiane et le p95 plutôt qu'une valeur unique.

Vous êtes maintenant prêt à mesurer les performances.

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

Pour comparer avec une autre solution vocale, écrivez une fonction de même structure. Exécutez ensuite les deux avec un petit programme qui écarte un appel de préchauffage, effectue environ 20 mesures espacées pour éviter qu'elles n'entrent en concurrence, et indique la médiane ainsi que le p95 en millisecondes.

Une comparaison équitable repose sur le contrôle des variables. 

Exécutez les deux solutions depuis la même machine et le même réseau, idéalement un serveur situé dans la région où vous déployez réellement, plutôt qu'un ordinateur portable connecté à un réseau résidentiel. Utilisez le même texte d'entrée et conservez un audio court, afin que l'inférence du modèle pèse davantage que la durée de génération. Indiquez la médiane et le p95 sur un grand nombre d'exécutions : une mesure unique n'est que du bruit. 

Gardez à l'esprit que le TTFB sur Internet public inclut 20 à 200 ms d'aller-retour réseau, sans lien avec le modèle. Nous opérons depuis des clusters en Amérique du Nord, en Europe et en Asie du Sud-Est, et acheminons les requêtes vers le plus proche. Placez donc votre client de test à proximité ; autrement, vous mesurez surtout la distance jusqu'à notre infrastructure.

Points clés pour votre intégration d'API Text to Speech

Une intégration d'API Text to Speech en production repose sur quelques décisions déterminantes.

Si vous les prenez correctement, tout le reste s'aligne :

  • Choisissez le modèle selon l'usage : utilisez Flash v2.5 pour tout usage interactif et un modèle à plus haute fidélité, comme Multilingual v2 ou Eleven v3 pour le rendu hors ligne, lorsque la latence importe moins.
  • Utilisez le streaming lorsqu'un utilisateur attend : choisissez le streaming HTTP pour les textes connus et un WebSocket pour les agents, afin que le temps d'inactivité ne grève pas votre budget de requêtes simultanées.
  • Limitez votre parallélisme à la limite de votre plan : plafonnez les requêtes simultanées juste en dessous de cette limite et mettez en cache un hash de chaque paramètre influant sur la sortie, afin de ne jamais facturer deux fois le même audio.
  • Relancez les erreurs 429 et 5xx avec un backoff exponentiel et un jitter complet : réduisez la charge face aux erreurs 429 et 5xx avec un jitter complet, et surveillez les en-têtes de requêtes simultanées pour savoir à quel point vous approchez de la limite.
  • Découpez les textes longs aux limites de phrases : effectuez le découpage aux limites de phrases, dans la limite de caractères de chaque modèle, pour préserver la prosodie à la jonction.

Pour aller plus loin, consultez le guide pratique du streaming, concept de streaming audio, l'authentification, ainsi que les jetons à usage unique pour une utilisation côté client.

Créez votre intégration Text to Speech avec ElevenAPI

Après avoir lu ce guide, vous disposez de tous les modèles nécessaires à une intégration d'API Text to Speech en production. Streaming, traitement par lot, mise en cache, relances et même benchmark : vous êtes prêt à les mettre en œuvre. 

Commencez par en savoir plus sur l'API Text to Speech ou créez un compte pour effectuer dès aujourd'hui votre premier appel avec ElevenAPI.

FAQ sur l'intégration de l'API Text to Speech

Articles similaires

Créez avec l'audio IA de la plus haute qualité