Pipeline vocal temps reel : de l'audio brut a l'IA
Points cles : Construire un pipeline vocal temps reel, c'est enchainer Deepgram STT (streaming avec VAD), un LLM via OpenRouter (function calling), et OpenAI TTS — le tout connecte par WebSocket authentifie JWT. Cet article detaille chaque etape, les latences, les fallbacks, et les bugs qui m'ont coute des nuits blanches.
Le coeur de TAMSIV, c'est la voix. Pas un gadget, pas un bouton micro planque dans un coin. La voix EST l'interface principale. Tu appuies, tu parles, l'IA comprend et execute. Mais construire un pipeline vocal temps reel en solo, c'est entrer dans un monde ou chaque milliseconde compte et ou tout peut casser a tout moment.
Apres trois semaines de developpement intensif et une quantite deraisonnable de cafe, j'ai un pipeline qui repond en 1.5 a 3 secondes. Voici comment ca marche, chunk par chunk.
Comment fonctionne l'architecture du pipeline vocal ?
Le pipeline complet en une ligne :
Audio PCM 16kHz mono → WebSocket (JWT) → Deepgram Live STT (VAD) → OpenRouter LLM → Function calling → OpenAI TTS → Reponse vocale
Six etapes, six points de defaillance potentiels. Chacune a ses contraintes, ses latences, et ses pieges. Le tout doit s'enchainer en moins de 3 secondes pour que l'experience soit fluide. Au-dela, l'utilisateur pense que l'app a plante.
Decomposons chaque etape.
Pourquoi le WebSocket est-il indispensable pour l'audio temps reel ?
HTTP classique ne fonctionne pas pour le streaming audio. Tu aurais besoin d'envoyer tout l'enregistrement d'un bloc, attendre le traitement, puis recevoir la reponse. La latence serait inacceptable.
Le WebSocket permet un flux bidirectionnel continu : le telephone envoie des chunks audio pendant que l'utilisateur parle, et le backend peut commencer a traiter avant meme que l'utilisateur ait fini.
L'authentification JWT
Chaque connexion WebSocket est authentifiee via un token JWT Supabase :
ws://backend:3001?token=eyJhbGciOiJIUzI1NiIs...
Le token est valide a la connexion. Si le token expire en pleine conversation (les tokens Supabase expirent apres 1 heure), le client detecte la deconnexion et se reconnecte automatiquement avec un token frais. J'ai du gerer ce cas explicitement — au debut, les conversations longues plantaient mysterieusement.
La securite du WebSocket est detaillee dans l'article sur l'audit de securite et le rate limiting.
Comment Deepgram gere-t-il le Speech-to-Text en streaming ?
L'audio doit etre en PCM 16 bits, 16kHz, mono. Le telephone capture l'audio dans ce format et envoie des chunks binaires bruts via le WebSocket. Pas de compression, pas d'encodage — le PCM brut est le format le plus rapide a traiter.
Deepgram recoit ces chunks et transcrit en streaming. Mais la vraie magie, c'est le VAD (Voice Activity Detection).
Pourquoi le VAD change tout ?
Sans VAD, il faut implementer un timeout de silence cote client : si l'utilisateur ne parle pas pendant X secondes, on considere qu'il a fini. Le probleme :
- Trop court (1s) : tu coupes l'utilisateur qui reflechit entre deux phrases.
- Trop long (3s) : l'app rame, l'utilisateur attend.
- Variable selon l'utilisateur : certains parlent vite, d'autres prennent leur temps.
Le VAD de Deepgram detecte quand l'utilisateur a fini de parler avec une precision remarquable. Il analyse le signal audio en temps reel et envoie un evenement speech_final quand il est certain que l'utilisateur a termine. Ca prend environ 200ms apres la fin de la parole.
Les resultats intermediaires vs finaux
Deepgram envoie deux types de resultats :
is_final: false— Resultats intermediaires, instables. Le mot detecte peut changer a mesure que le contexte s'enrichit.is_final: true— Resultats confirmes. Le texte ne changera plus.
Le piege : accumuler les resultats intermediaires pour afficher un preview en temps reel (bon pour l'UX) tout en ne transmettant que les resultats finaux au LLM (bon pour la qualite). J'affiche les resultats intermediaires en gris et les finaux en blanc — l'utilisateur voit sa voix se transformer en texte en direct.
Pour la comparaison entre STT natif (gratuit, sur le device) et Deepgram cloud (plus precis), lis mon article detaille STT natif vs Deepgram.
Comment le LLM orchestre-t-il les actions via function calling ?
La transcription complete part vers OpenRouter avec function calling. OpenRouter est un routeur qui donne acces a 400+ modeles LLM avec fallback automatique — si le modele principal est down, un fallback prend le relais en quelques secondes.
Le LLM recoit la transcription et doit comprendre l'intention de l'utilisateur. Il dispose de 7 fonctions :
create_task— Creer une tacheupdate_task— Modifier une tache existantecreate_memo— Creer un memoupdate_memo— Modifier un memo existantcreate_calendar_event— Creer un evenement agendaask_clarification— Demander une precision a l'utilisateurend_conversation— Terminer la conversation
Le LLM analyse la phrase ("rappelle-moi d'acheter du pain demain a 10h"), identifie l'action (create_task), extrait les parametres (titre, date, heure), et retourne un appel de fonction structure. Le backend execute l'action et renvoie le resultat au frontend.
Le pattern PendingCreation est crucial ici : le backend cree une preview de l'element, et l'utilisateur peut valider, editer ou annuler avant la sauvegarde definitive en base de donnees. Zero mauvaise surprise.
La latence LLM
Le LLM represente le gros de la latence : entre 800ms et 2 secondes selon le modele et la complexite de la requete. C'est ici que le choix du modele via OpenRouter fait la difference — un modele rapide mais moins precis vs un modele lent mais plus fiable. TAMSIV utilise un modele configurable avec fallback automatique si le modele principal est trop lent ou indisponible.
Comment OpenAI TTS genere-t-il la reponse vocale ?
Une fois l'action executee, le backend genere une reponse textuelle ("C'est note ! J'ai cree la tache 'Acheter du pain' pour demain a 10h"). Ce texte part vers OpenAI TTS avec la voix nova.
L'audio est streame en retour via le meme WebSocket. Le frontend commence a jouer des les premiers chunks audio, sans attendre la reponse complete. Ca reduit la latence percue d'environ 500ms — l'utilisateur entend le debut de la reponse pendant que la fin est encore en cours de generation.
Pour la personnalisation vocale et le choix de la voix TTS, j'en parle dans l'article sur la personnalisation vocale.
Quels sont les trois modes WebSocket de TAMSIV ?
Au fil du developpement, trois modes WebSocket ont emerge :
- LiveWebSocketServer (defaut) — STT natif device + Deepgram fallback, orchestration LLM, OpenAI TTS. C'est le mode standard, le plus economique.
- RealtimeWebSocketServer — API OpenAI Realtime, bidirectionnel, basse latence. Plus couteux mais plus fluide pour les conversations longues.
- WebSocketServer — Batch STT/TTS, mode legacy. Utilise pour les cas ou le streaming n'est pas necessaire.
Le mode est selectionnable cote admin via la table app_config dans Supabase. Ca permet de basculer entre les modes sans deployer une nouvelle version de l'app.
Comment gerer les erreurs dans un pipeline temps reel ?
La regle d'or : tout peut echouer a tout moment. Deepgram peut etre down. OpenRouter peut timeout. OpenAI TTS peut retourner une erreur 429. La connexion WebSocket peut couper en pleine conversation.
Voici les mecanismes de resilience :
- Retry intelligents : Chaque etape a un nombre de retries configure avec backoff exponentiel. Pas de retry en boucle infinie.
- Circuit breakers : Si un service echoue trop souvent, on arrete de l'appeler pendant X secondes pour eviter de surcharger un service deja en difficulte.
- Fallbacks a chaque etape : STT natif si Deepgram est down, modele LLM alternatif via OpenRouter, reponse texte si TTS echoue.
- AlertService : Chaque fallback declenche une alerte email (via Resend) + log Supabase. Je sais en temps reel quand quelque chose degrade.
Chaque ligne de gestion d'erreur represente un bug vecu en production. Le pipeline est robuste aujourd'hui, mais il a fallu des dizaines de sessions de debug pour en arriver la.
Quel est le budget latence de chaque etape ?
Decomposition d'une interaction vocale complete :
- Capture audio + envoi WebSocket : ~50ms (negligeable)
- Deepgram STT + VAD : ~200-400ms apres fin de parole
- OpenRouter LLM (function calling) : ~800ms-2000ms (variable)
- OpenAI TTS (premier chunk) : ~300-500ms
Total : 1.3 a 3 secondes. L'objectif est de rester sous 2 secondes pour 90% des interactions. Au-dela, l'experience devient frustrante.
Le streaming TTS est le meilleur levier : l'utilisateur entend le debut de la reponse apres ~1.5s en moyenne, meme si la generation complete prend 3 secondes. La latence percue est bien inferieure a la latence reelle.
Quelles lecons tirer pour construire ton propre pipeline vocal ?
Apres trois semaines de dev intensif, voici ce que je conseillerais :
- Commence par le WebSocket : C'est la colonne vertebrale. Si le WebSocket est solide, le reste s'emboite.
- Utilise le VAD du provider STT : N'implemente pas ta propre detection de silence — c'est un gouffre de complexite pour un resultat inferieur.
- Streame tout : STT en streaming, TTS en streaming. Chaque milliseconde economisee ameliore l'UX.
- Prevois les fallbacks des le jour 1 : Pas en mode "je verrai plus tard". Chaque etape doit avoir un plan B.
- Mesure la latence en production : Les benchmarks locaux sont trompeurs. La latence reelle depend du reseau, de la charge serveur, et de la localisation geographique. Le dashboard admin m'a ete indispensable pour ca.
FAQ
Pourquoi Deepgram plutot que Google Speech-to-Text ou AWS Transcribe ?
Deepgram offre le meilleur rapport qualite/latence pour le streaming. Google STT est excellent mais plus cher et plus lent en mode streaming. AWS Transcribe est robuste mais l'integration WebSocket est plus complexe. Deepgram a aussi un VAD integre, ce qui simplifie enormement le code.
Le pipeline fonctionne-t-il en mode hors-ligne ?
Le STT natif du device fonctionne hors-ligne. Mais le LLM et le TTS necessitent une connexion internet. En mode hors-ligne, TAMSIV permet la saisie textuelle classique et met en queue les requetes vocales pour quand la connexion revient.
Combien coute une interaction vocale complete ?
Avec le STT natif (gratuit), un LLM economique via OpenRouter (~$0.001-0.01), et OpenAI TTS (~$0.015/1000 chars), une interaction coute entre $0.01 et $0.03. Le detail des couts est dans l'article retrospective sur les 650 commits.
Peut-on remplacer OpenAI TTS par une solution open-source ?
Techniquement oui. Des projets comme Coqui TTS ou Bark produisent des resultats corrects. Mais la qualite de la voix "nova" d'OpenAI reste superieure, et le streaming est mieux supporte. Pour un projet en production, le cout additionnel d'OpenAI TTS ($15/million de caracteres) se justifie par la qualite.
Comment gerer les langues multiples dans le pipeline vocal ?
Deepgram supporte la detection automatique de langue. Le LLM via OpenRouter est naturellement multilingue. Le TTS OpenAI genere de l'audio dans la langue du texte fourni. TAMSIV supporte 6 langues (FR, EN, DE, ES, IT, PT) sans configuration specifique par langue dans le pipeline. Le detail de l'internationalisation est dans l'article sur l'i18n en 6 langues.