Aller au contenu principal

Pipeline vocal complet

Mis à jour le 29 juillet 2026

De la voix à la voix : le pipeline STT → LLM → TTS

Vous savez créer des voix, générer de la parole et maîtriser la latence. Cette dernière leçon assemble ces briques dans un système complet : l’utilisateur parle, la machine écoute, comprend, raisonne et répond d’une voix naturelle.

L’architecture tient en trois maillons. La reconnaissance vocale, d’abord, convertit la parole en texte ; Mistral propose son propre modèle, mais Whisper d’OpenAI ou une autre solution font aussi bien l’affaire. Ce texte part ensuite vers un grand modèle de langage, qui interprète la demande et rédige une réponse — Mistral Large 3, Claude, GPT-5.6 ou Llama, le pipeline ne présuppose rien du fournisseur. La réponse revient enfin à Voxtral TTS, qui la restitue en parole envoyée en streaming.

Un pipeline fonctionnel

Le code ci-dessous met les trois étapes bout à bout. Chacune est isolée dans sa propre fonction, ce qui vous permettra de changer de modèle STT ou de LLM sans toucher au reste. Notez que l’historique de conversation est passé et enrichi par generer_reponse() : c’est lui qui donnera de la cohérence aux échanges successifs.

import base64
from pathlib import Path
from mistralai.client import Mistral

client = Mistral(api_key="votre-cle-api")

# Configuration
VOICE_ID = "votre-voice-id"
LLM_MODEL = "mistral-large-latest"
TTS_MODEL = "voxtral-mini-tts-2603"


def transcrire_audio(chemin_audio: str) -> str:
    """Étape 1 : Convertir l'audio en texte (STT)."""
    audio_b64 = base64.b64encode(
        Path(chemin_audio).read_bytes()
    ).decode()

    response = client.audio.transcriptions.create(
        model="mistral-audio-latest",
        file=audio_b64,
    )
    return response.text


def generer_reponse(question: str, historique: list) -> str:
    """Étape 2 : Générer une réponse avec le LLM."""
    historique.append({"role": "user", "content": question})

    response = client.chat.complete(
        model=LLM_MODEL,
        messages=historique,
    )

    reponse = response.choices[0].message.content
    historique.append({"role": "assistant", "content": reponse})
    return reponse


def synthetiser_parole(texte: str, fichier_sortie: str):
    """Étape 3 : Convertir le texte en parole (TTS)."""
    audio_chunks = []

    with client.audio.speech.complete(
        model=TTS_MODEL,
        input=texte,
        voice_id=VOICE_ID,
        response_format="pcm",
        stream=True,
    ) as stream:
        for event in stream:
            if event.event == "speech.audio.delta":
                audio_chunks.append(
                    base64.b64decode(event.data.audio_data)
                )

    Path(fichier_sortie).write_bytes(b"".join(audio_chunks))


def pipeline_vocal(chemin_audio: str):
    """Pipeline complet : STT → LLM → TTS."""
    historique = [
        {
            "role": "system",
            "content": (
                "Vous êtes un assistant vocal professionnel. "
                "Répondez de manière concise et naturelle, "
                "comme dans une conversation orale."
            ),
        }
    ]

    # Étape 1 : Transcrire
    print("Transcription en cours...")
    question = transcrire_audio(chemin_audio)
    print(f"Question : {question}")

    # Étape 2 : Raisonner
    print("Génération de la réponse...")
    reponse = generer_reponse(question, historique)
    print(f"Réponse : {reponse}")

    # Étape 3 : Synthétiser
    print("Synthèse vocale en cours...")
    synthetiser_parole(reponse, "reponse.pcm")
    print("Pipeline terminé — fichier reponse.pcm généré")


# Lancer le pipeline
pipeline_vocal("question_utilisateur.wav")

Le prompt système mérite un mot : demander explicitement des réponses concises et orales n’est pas une coquetterie. Un LLM laissé libre produit volontiers des paragraphes structurés avec des titres, catastrophiques une fois lus à voix haute — et d’autant plus longs à synthétiser.

Ne plus attendre la fin du raisonnement

Le pipeline précédent est séquentiel : la synthèse ne démarre qu’une fois la réponse du LLM entièrement rédigée. Sur une réponse de trente mots, cela ajoute plusieurs secondes de silence dont l’utilisateur n’a aucune explication. La solution consiste à faire se recouvrir les étapes 2 et 3 : le LLM génère en streaming, et dès qu’une phrase complète est disponible — c’est-à-dire dès qu’un token se termine par un point, un point d’exclamation ou un point d’interrogation —, elle part en synthèse pendant que le modèle continue de rédiger la suite.

def pipeline_streaming(question_texte: str):
    """Pipeline optimisé : le TTS commence dès les premiers
    tokens du LLM."""

    # Le LLM génère en streaming
    reponse_partielle = ""
    phrases_a_synthetiser = []

    stream_llm = client.chat.stream(
        model=LLM_MODEL,
        messages=[
            {"role": "system", "content": "Répondez brièvement."},
            {"role": "user", "content": question_texte},
        ],
    )

    for chunk in stream_llm:
        token = chunk.data.choices[0].delta.content or ""
        reponse_partielle += token

        # Dès qu'une phrase complète est disponible
        if token.endswith((".", "!", "?")):
            phrases_a_synthetiser.append(reponse_partielle)
            reponse_partielle = ""

    # Synthétiser chaque phrase en streaming
    for phrase in phrases_a_synthetiser:
        with client.audio.speech.complete(
            model=TTS_MODEL,
            input=phrase,
            voice_id=VOICE_ID,
            response_format="pcm",
            stream=True,
        ) as stream:
            for event in stream:
                if event.event == "speech.audio.delta":
                    yield base64.b64decode(event.data.audio_data)

La boucle conversationnelle

Un assistant véritable ne traite pas une question isolée : il enchaîne les tours de parole en conservant le fil. La boucle ci-dessous enregistre, transcrit, vérifie si l’utilisateur souhaite arrêter, puis répond — en réinjectant à chaque tour le même historique, qui grandit au fil de l’échange.

def assistant_vocal():
    """Boucle conversationnelle complète."""
    historique = [
        {
            "role": "system",
            "content": "Vous êtes un assistant vocal concis.",
        }
    ]

    print("Assistant vocal prêt. Parlez !")

    while True:
        # Enregistrer la question (via microphone)
        audio_question = enregistrer_microphone(duree=10)

        # Pipeline STT → LLM → TTS
        question = transcrire_audio(audio_question)
        if question.lower() in ("stop", "arrête", "au revoir"):
            synthetiser_parole("Au revoir !", "sortie.pcm")
            break

        reponse = generer_reponse(question, historique)
        synthetiser_parole(reponse, "sortie.pcm")
        jouer_audio("sortie.pcm")

Ce qui sépare ce prototype de la production

Le duree=10 de l’enregistrement illustre bien le chemin qu’il reste à parcourir : un délai fixe force l’utilisateur bavard à s’interrompre et fait attendre dix secondes celui qui répond « oui ». Un détecteur d’activité vocale (VAD) règle ce problème en repérant réellement la fin de la parole. Il faut ensuite gérer les interruptions, puisqu’un utilisateur coupe volontiers un assistant trop long : votre code doit pouvoir arrêter la synthèse en cours, ce que le streaming rend possible mais que ce prototype ne fait pas.

Trois autres points conditionnent la mise en service. L’historique de conversation, déjà présent ici, doit être borné et persisté si les sessions durent. Un fallback est indispensable — si le STT ou le TTS échoue, affichez le texte plutôt que de laisser un silence inexpliqué. Enfin, instrumentez chaque étape en production : c’est la seule façon de savoir si la seconde de trop vient de la transcription, du LLM ou de la synthèse.

Prochaines étapes

Vous disposez désormais de tout ce qu’il faut pour exploiter Voxtral TTS dans vos projets. Plusieurs directions s’ouvrent selon vos contraintes : héberger Voxtral en local si la latence ou la maîtrise des données prime, l’intégrer à un framework existant comme LangChain, LlamaIndex ou Haystack, ou construire une application web complète avec WebRTC pour l’audio bidirectionnel. Vous pouvez aussi explorer les modèles multimodaux de Mistral, qui combinent texte, image et audio, et rejoindre la communauté Mistral sur Discord pour confronter vos réalisations à celles des autres.

Points clés à retenir

  • Un assistant vocal complet enchaîne trois étapes : STT → LLM → TTS
  • La parallélisation des étapes (générer le TTS pendant que le LLM continue) réduit drastiquement la latence
  • Le format PCM en streaming offre la réactivité nécessaire pour une conversation naturelle
  • En production, gérez la détection de fin de parole, les interruptions et le fallback texte
  • Voxtral TTS s’intègre dans n’importe quel pipeline vocal grâce au SDK Python Mistral

Testez vos connaissances

Synthèse, voix, latence : dernier contrôle avant le pipeline complet.

1. Que permet l'API Voices ?

Réponse : Gérer les voix disponibles et en créer des personnalisées — la voix devient un actif configuré, réutilisable dans toutes vos générations.

2. Quelles règles éthiques encadrent le clonage vocal ?

Réponse : Le consentement explicite de la personne clonée et un usage transparent et légitime — cloner une voix sans autorisation est exclu, techniquement possible ne signifie pas permis.

3. Comment contrôler la prononciation d'un texte ?

Réponse : En préparant le texte : orthographier phonétiquement les termes ambigus, gérer sigles et nombres, ponctuer pour le rythme — le TTS lit ce qu’on lui écrit.

4. Quels leviers pour réduire la latence ?

Réponse : Le streaming temps réel (audio produit au fil du texte), des formats audio adaptés et des textes segmentés — la voix démarre avant la fin de la génération.

5. À quoi ressemble un pipeline vocal complet ?

Réponse : Transcription de l’utilisateur (Voxtral) → traitement par le modèle → synthèse de la réponse (TTS), en streaming de bout en bout : la boucle d’un assistant vocal.

La boucle est bouclée : de la voix entrante à la voix sortante — le pipeline final du cours assemble tout ce que vous venez de valider.