AWS fait parler les agents IA vocaux dès la première phrase avec Kokoro-82M

AWS décrit une architecture où l'agent IA vocal n'attend plus que le modèle ait fini d'écrire toute sa réponse. Chaque phrase terminée part aussitôt vers Kokoro-82M, déployé sur SageMaker AI, et la voix démarre dès la première phrase. Auparavant, le client restait silencieux environ cinq secondes, alors qu'une réponse courte est générée en 600 ms. Le modèle ne pèse que 82 millions de paramètres et tourne sur un seul GPU ml.g5.xlarge. Une seule session WebSocket bidirectionnelle relie le client et le conteneur : l'audio et les commandes JSON circulent sur le même canal, sans base64 ni reconnexions inutiles. • Le base64 dans SSE gonfle l'audio d'environ 33 % : MP3 48k — 308 304 octets, 411 072 après encodage • L'erreur KeyError('text') survient parce que SageMaker n'envoie que des trames binaires • L'agent Strands + Bedrock AgentCore en mode bidi fait chevaucher la génération et la synthèse

Traité par IA depuis AWS Machine Learning Blog ; édité par Hamidun News
AWS fait parler les agents IA vocaux dès la première phrase avec Kokoro-82M
Source : AWS Machine Learning Blog. Collage: Hamidun News.
Écouter l'article
Fiche synthèse et chiffres clés
ParamètreDonnées
Source primaireAWS Machine Learning Blog ↗
Rédacteur en chef
Chiffre clé #1Le base64 dans SSE gonfle l'audio d'environ 33 % : MP3 48k — 308 304 octets, 411 072 après encodage
Chiffre clé #2L'erreur KeyError('text') survient parce que SageMaker n'envoie que des trames binaires
Chiffre clé #3L'agent Strands + Bedrock AgentCore en mode bidi fait chevaucher la génération et la synthèse

Un utilisateur pose une question à un agent vocal, puis attend. La réponse est déjà en partie générée, mais aucun son ne sort : les intégrations text-to-speech attendent généralement la fin de tout le texte. Le modèle produit une phrase en 600 ms, mais une réponse longue complète prend environ cinq secondes. Pendant tout ce temps, le client n'entend que du silence. Dans un article de blog, AWS montre comment supprimer ce trou : envoyer chaque phrase au synthétiseur Kokoro-82M sur Amazon SageMaker AI dès que l'agent l'a terminée.

Un petit modèle, un seul GPU et deux routes

Kokoro-82M compte 82 millions de paramètres, ce qui suffit pour le faire tourner sur une seule instance GPU de milieu de gamme. Le modèle s'exécute dans un conteneur personnalisé, sur un endpoint temps réel (real-time) SageMaker AI, avec une instance de type ml.g5.xlarge. Le conteneur expose deux routes : POST /invocations pour une réponse en streaming à une entrée complète unique, et WS /invocations-bidirectional-stream, où une même connexion porte de nombreux messages dans les deux sens. Le conteneur doit déclarer le label Docker com.amazonaws.sagemaker.capabilities.bidirectional-streaming=true.

Le déploiement se résume à deux commandes basées sur AWS CDK : `uv run speech-infra deploy kokoro-82m` crée l'endpoint (rôles IAM, cache des poids dans S3, image du conteneur, modèle et configuration), tandis que `uv run agent-infra deploy --bedrock-model-id <id>` déploie l'agent Strands dans l'environnement Amazon Bedrock AgentCore. L'ordre compte : l'agent s'adresse à un endpoint qui doit déjà exister. Python 3.12+ est requis, car le client HTTP/2 n'est fourni en wheels précompilées que pour ces versions. Les auteurs lancent aussi un avertissement : l'exemple fige une ancienne version de PyTorch affectée par la vulnérabilité connue PYSEC-2026-2286 (corrigée dans torch 2.10.0), qu'il faut mettre à jour avant tout usage au-delà du proof-of-value.

Trois pièges du streaming bidirectionnel

Premier piège : le type de trame WebSocket. SageMaker livre chaque message du client sous forme de trame binaire. Un gestionnaire Starlette classique appelle receive_text(), lit message["text"] et plante avec une KeyError. Si le gestionnaire d'exceptions global renvoie str(e) au client, celui-ci voit l'erreur « 'text' », qui ressemble à un champ manquant et non à un problème de framing. L'endpoint, lui, affiche honnêtement InService : il accepte les connexions et répond à chaque message, mais par une erreur. La solution consiste à accepter les deux types de trames et à décoder les bytes en UTF-8 lorsque le champ text est absent.

Deuxième piège : la fermeture prématurée de la session. Après l'envoi de l'audio et de la trame de contrôle synthesis_complete, le conteneur doit revenir à receive() et attendre le message suivant. La connexion ne doit se fermer que sur un {"type": "close"} explicite. Sinon, chaque phrase exigerait une nouvelle connexion, et le gain de latence disparaîtrait.

Troisième piège : le découpage du flux en phrases. La classe IncrementalSentenceChunker reçoit le texte par morceaux via feed(), isole les phrases terminées au moyen d'une expression régulière et retient la fin de texte. En fin de flux, flush() récupère le reste, y compris sans point final. Les auteurs précisent d'emblée qu'il ne s'agit pas de synthèse parallèle. Le gestionnaire est strictement séquentiel : recevoir le message, synthétiser, envoyer l'audio, envoyer synthesis_complete. Le gain tient uniquement au fait que la synthèse de la première phrase démarre dès qu'elle apparaît. Sur un endpoint en production, trois phrases ont été testées dans une même session :

  • « Hello there. » — 73 200 octets d'audio, 1,525 s
  • « This is a post deployment check. » — 108 000 octets, 2,250 s
  • « It has three sentences. » — 98 400 octets, 2,050 s

SSE avec base64 contre multiplexage binaire

Pour livrer l'audio au navigateur, AWS a comparé deux approches. La première est le SSE classique : l'audio est encodé en base64 et enveloppé dans un événement JSON. Sur 24 clips, AWS a mesuré le coût de cette solution (la qualité mesurée par Amazon Transcribe ne dépendait pas de l'encodage) :

  • PCM brut : 2 396 400 octets, 3 195 200 en base64, débit de 384 kbit/s
  • MP3 48k : 308 304 octets, 411 072 en base64, 49,5 kbit/s
  • AAC 32k fMP4 : 258 039 octets, 344 084 en base64, 41,6 kbit/s

Le base64 ajoute presque exactement les 33 % attendus. Un problème moins évident s'y ajoute : les frontières des événements SSE (ligne vide) ne coïncident pas avec celles des trames MP3, ni avec la façon dont le transport découpe les données. Dans une mesure, quatre trames MP3 logiques sont arrivées en six chunks, dont deux s'interrompaient au milieu d'une trame. Un consommateur qui traite chaque chunk comme une unité complète plante sur un trafic pourtant sain. Un tampon règle le problème : on accumule les octets et on ne traite une trame qu'après avoir trouvé le séparateur `\n\n`.

La seconde approche consiste à abandonner totalement base64 et SSE sur le tronçon en amont du modèle. L'audio binaire et les commandes JSON circulent dans une même session bidirectionnelle, et une seule règle les distingue : si un chunk se parse en entier comme un objet JSON, c'est une trame de contrôle, sinon c'est de l'audio. Se fier au premier octet ne marche pas : le PCM peut commencer par 0x7b, soit « { ». Mais un chunk PCM complet ne devient pratiquement jamais un JSON valide.

L'agent de bout en bout

Dans la configuration complète, l'agent Strands, doté d'un modèle d'Amazon Bedrock, s'exécute dans le runtime Bedrock AgentCore et utilise trois canaux : SSE vers le navigateur, un appel Bedrock en streaming pour le texte, et une session bidirectionnelle avec Kokoro pour la synthèse. La requête comporte un champ mode : « bidi » ou « batch ». Dans run_bidi, deux tâches tournent en parallèle : l'une lit le flux Bedrock et transmet au chunker les phrases terminées, l'autre lit les réponses de Kokoro et émet l'audio.

Toutes deux déposent leur résultat dans une file commune, si bien que le son des premières phrases peut partir vers l'appelant pendant que Bedrock écrit encore les suivantes. run_batch attend tout le texte et effectue un seul appel de synthèse : une base de comparaison honnête.

Sur le tronçon entre l'agent et l'appelant, le base64 reste de toute façon en place : l'entrypoint de BedrockAgentCoreApp doit renvoyer des dictionnaires sérialisables en JSON, que le SDK transforme lui-même en trames `data: {json}`. Impossible d'y faire passer une trame binaire. Ce coût est identique dans les deux modes, de sorte que l'écart observé dans les mesures reflète uniquement le timing du premier chunk audio.

Que choisir en pratique

Si le frontend gère déjà du SSE pour le texte, le base64 dans l'événement est un choix pragmatique : +33 % de taille et un tampon d'assemblage, mais une infrastructure déjà prête. Si le canal vocal est construit de zéro ou si la latence est critique, un schéma binaire unique avec pilotage en JSON supprime les deux surcoûts et réutilise la connexion qui transporte déjà le texte. Dans tous les cas, il faut tester précisément les trames binaires, vérifier l'assemblage aux frontières des trames MP3 et ne pas fermer la session après chaque phrase. Et avant la production, mettre à jour la version figée de PyTorch.

Dossier

Questions clés sur cet article

Que sait-on sur Un petit modèle, un seul GPU et deux routes ?

Kokoro-82M compte 82 millions de paramètres, ce qui suffit pour le faire tourner sur une seule instance GPU de milieu de gamme. Le modèle s'exécute dans un conteneur personnalisé, sur un endpoint temps réel (real-time) SageMaker AI, avec une instance de type ml.g5.xlarge. Le conteneur expose deux routes : POST /invocations pour une réponse en streaming à une entrée complète unique, et WS /invocations-bidirectional-stream, où une même connexion porte de nombreux messages dans les deux sens. Le conteneur doit déclarer le label Docker com.amazonaws.sagemaker.capabilities.bidir

Que sait-on sur Trois pièges du streaming bidirectionnel ?

Premier piège : le type de trame WebSocket. SageMaker livre chaque message du client sous forme de trame binaire. Un gestionnaire Starlette classique appelle receive_text(), lit message["text"] et plante avec une KeyError. Si le gestionnaire d'exceptions global renvoie str(e) au client, celui-ci voit l'erreur « 'text' », qui ressemble à un champ manquant et non à un problème de framing. L'endpoint, lui, affiche honnêtement InService : il accepte les connexions et répond à chaque message, mais par une erreur. La solution consiste à accepter les deux types de trames et à déco

Que sait-on sur SSE avec base64 contre multiplexage binaire ?

Pour livrer l'audio au navigateur, AWS a comparé deux approches. La première est le SSE classique : l'audio est encodé en base64 et enveloppé dans un événement JSON. Sur 24 clips, AWS a mesuré le coût de cette solution (la qualité mesurée par Amazon Transcribe ne dépendait pas de l'encodage) : PCM brut : 2 396 400 octets, 3 195 200 en base64, débit de 384 kbit/s MP3 48k : 308 304 octets, 411 072 en base64, 49,5 kbit/s AAC 32k fMP4 : 258 039 octets, 344 084 en base64, 41,6 kbit/s Le base64 ajoute presque exactement les 33 % attendus. Un problème moins évident s'y ajoute : le

ZK
Zhemal Khamidun — CPO d'Alpina Digital, product manager d'AlpinaGPT. Conçoit des produits IA depuis 2019. Hamidun News est un projet personnel devenu un agrégateur en production avec plus de 6 000 lecteurs.
✦ Autonomous AI Execution · Nomad Agent

Delegate Deep Research, Coding & Multi-Step Workflows to Nomad Agent

Deploy autonomous task execution with Nomad Agent (agent.nomadnet.ai) — automated web research, data synthesis, code generation, and MCP tool orchestration.

Launch Nomad Agent →Nomad AI Workspace
Qu'en pensez-vous ?
Chargement des commentaires…