Aller au contenu principal

Pipeline vocal complet

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

Tout au long de cette formation, vous avez appris à créer des voix, générer de la parole et optimiser la latence. Cette dernière leçon rassemble tous ces éléments dans un pipeline vocal complet : l’utilisateur parle, le système écoute, comprend, raisonne et répond — le tout avec une voix naturelle.

Architecture du pipeline

Un assistant vocal complet se compose de trois étapes :

Étape 1 : Speech-to-Text (STT)

La reconnaissance vocale convertit la parole de l’utilisateur en texte. Mistral propose son propre modèle STT, mais vous pouvez aussi utiliser Whisper (OpenAI) ou d’autres alternatives.

Étape 2 : Large Language Model (LLM)

Le texte transcrit est envoyé à un LLM qui comprend la question et génère une réponse textuelle. Vous pouvez utiliser n’importe quel LLM : Mistral Large, Claude, GPT-4, Llama, etc.

Étape 3 : Text-to-Speech (TTS)

La réponse textuelle est convertie en parole avec Voxtral TTS et envoyée à l’utilisateur en streaming.

Implémentation Python

Voici un pipeline fonctionnel qui combine les trois étapes :

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

Optimiser la latence du pipeline

Le pipeline séquentiel ci-dessus fonctionne, mais chaque étape attend la fin de la précédente. Pour un assistant vocal fluide, vous pouvez paralléliser les étapes 2 et 3 :

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)

L’idée clé : dès que le LLM a produit une phrase complète, le TTS commence à la synthétiser pendant que le LLM continue à générer la suite.

Boucle conversationnelle

Pour un véritable assistant interactif, encapsulez le pipeline dans une boucle :

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

Considérations de production

Pour déployer un assistant vocal en production, tenez compte de :

  • Détection de fin de parole (VAD) : utilisez un Voice Activity Detector pour savoir quand l’utilisateur a fini de parler, plutôt qu’un délai fixe
  • Gestion des interruptions : l’utilisateur peut interrompre l’assistant — votre code doit pouvoir arrêter la synthèse en cours
  • Historique de conversation : maintenez le contexte entre les tours de parole pour des réponses cohérentes
  • Fallback : si le STT ou le TTS échoue, affichez le texte en alternative
  • Monitoring : mesurez la latence de chaque étape en production pour identifier les goulots d’étranglement

Prochaines étapes

Vous avez maintenant toutes les connaissances pour exploiter Voxtral TTS dans vos projets. Voici quelques pistes pour aller plus loin :

  • Héberger Voxtral en local pour réduire la latence et contrôler les données
  • Intégrer avec des frameworks comme LangChain, LlamaIndex ou Haystack
  • Construire une application web complète avec WebRTC pour l’audio bidirectionnel
  • Explorer les modèles multimodaux de Mistral qui combinent texte, image et audio
  • Rejoindre la communauté Mistral sur Discord pour partager vos projets

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