Note

Bienvenue sur la communauté Facebook SunFounder Raspberry Pi, Arduino & ESP32 !

  • Obtenez du support technique et de l’aide au dépannage.

  • Apprenez et partagez des projets, des astuces et des tutoriels.

  • Accédez en avant-première aux aperçus et mises à jour des produits.

  • Profitez de réductions exclusives et de cadeaux.

👉 Rejoignez-nous ici : [here]

6. Chatbot Vocal Local

Dans cette leçon, vous allez combiner tout ce que vous avez appris — reconnaissance vocale (STT), synthèse vocale (TTS) et un LLM local (Ollama) — pour construire un chatbot vocal entièrement hors ligne qui fonctionne sur votre Pironman 5 Pro MAX.

Le flux de travail est simple :

  1. Écouter — Le microphone capture votre discours et le transcrit avec Vosk.

  2. Penser — Le texte est envoyé à un LLM local fonctionnant sur Ollama (par exemple, llama3.2:3b).

  3. Parler — Le chatbot répond à voix haute en utilisant Piper TTS.

Cela crée un robot conversationnel mains libres capable de comprendre et de répondre en temps réel.


Avant de commencer

Assurez-vous d’avoir préparé les éléments suivants :


Exécuter le code

  1. Ouvrez le script d’exemple :

    cd ~/sunfounder-voice-assistant/examples/
    sudo nano local_voice_chatbot.py
    
  2. Mettez à jour les paramètres si nécessaire :

    • stt = Vosk(language="en-us") : Modifiez ceci pour correspondre à votre accent/pack linguistique (par exemple, en-us, zh-cn, es).

    • tts.set_model("en_US-amy-low") : Remplacez par le modèle vocal Piper que vous avez vérifié dans 1. Tester Piper.

    • llm = Ollama(ip="localhost", model="llama3.2:3b") : Mettez à jour à la fois ip et model selon votre propre configuration.

      • ip : Si Ollama fonctionne sur le même Pi, utilisez localhost. Si Ollama fonctionne sur un autre ordinateur de votre LAN, activez Exposer au réseau dans Ollama et définissez ip sur l’adresse IP LAN de cet ordinateur.

      • model : Doit correspondre exactement au nom du modèle que vous avez téléchargé/activé dans Ollama.

  3. Exécutez le script :

    cd ~/sunfounder-voice-assistant/examples/
    sudo python3 local_voice_chatbot.py
    
  4. Après l’exécution, vous devriez voir :

    • Le bot vous salue avec un message de bienvenue vocal.

    • Il attend une entrée vocale.

    • Vosk transcrit votre discours en texte.

    • Le texte est envoyé à Ollama, qui diffuse une réponse.

    • La réponse est nettoyée (suppression du raisonnement caché) et prononcée à voix haute par Piper.

    • Arrêtez le programme à tout moment avec Ctrl+C.


Code

import re
import time
from sunfounder_voice_assistant.llm import Ollama
from sunfounder_voice_assistant.stt import Vosk
from sunfounder_voice_assistant.tts import Piper

# Initialiser la reconnaissance vocale
stt = Vosk(language="en-us")

# Initialiser la TTS
tts = Piper()
tts.set_model("en_US-amy-low")

# Instructions pour le LLM
INSTRUCTIONS = (
    "You are a helpful assistant. Answer directly in plain English. "
    "Do NOT include any hidden thinking, analysis, or tags like <think>."
)
WELCOME = "Hello! I'm your voice chatbot. Speak when you're ready."

# Initialiser la connexion Ollama
llm = Ollama(ip="localhost", model="llama3.2:3b")
llm.set_max_messages(20)
llm.set_instructions(INSTRUCTIONS)

# Utilitaire : supprimer le raisonnement caché
def strip_thinking(text: str) -> str:
    if not text:
        return ""
    text = re.sub(r"<\s*think[^>]*>.*?<\s*/\s*think\s*>", "", text, flags=re.DOTALL|re.IGNORECASE)
    text = re.sub(r"<\s*thinking[^>]*>.*?<\s*/\s*thinking\s*>", "", text, flags=re.DOTALL|re.IGNORECASE)
    text = re.sub(r"```(?:\s*thinking)?\s*.*?```", "", text, flags=re.DOTALL|re.IGNORECASE)
    text = re.sub(r"\[/?thinking\]", "", text, flags=re.IGNORECASE)
    return re.sub(r"\s+\n", "\n", text).strip()

def main():
    print(WELCOME)
    tts.say(WELCOME)

    try:
        while True:
            print("\n🎤 Écoute... (Appuyez sur Ctrl+C pour arrêter)")

            # Récupérer la transcription finale de Vosk
            text = ""
            for result in stt.listen(stream=True):
                if result["done"]:
                    text = result["final"].strip()
                    print(f[VOUS] {text}")
                else:
                    print(f"[VOUS] {result['partial']}", end="\r", flush=True)

            if not text:
                print("[INFO] Rien n'a été reconnu. Réessayez.")
                time.sleep(0.1)
                continue

            # Interroger Ollama avec streaming
            reply_accum = ""
            response = llm.prompt(text, stream=True)
            for next_word in response:
                if next_word:
                    print(next_word, end="", flush=True)
                    reply_accum += next_word
            print("")

            # Nettoyer et parler
            clean = strip_thinking(reply_accum)
            if clean:
                tts.say(clean)
            else:
                tts.say("Sorry, I didn't catch that.")

            time.sleep(0.05)

    except KeyboardInterrupt:
        print("\n[INFO] Arrêt en cours...")
    finally:
        tts.say("Goodbye!")
        print("Au revoir.")

if __name__ == "__main__":
    main()

Analyse du code

Importations et configuration globale

import re
import time
from sunfounder_voice_assistant.llm import Ollama
from sunfounder_voice_assistant.stt import Vosk
from sunfounder_voice_assistant.tts import Piper

Importe les trois sous-systèmes que vous avez construits précédemment : Vosk pour la reconnaissance vocale (STT), Ollama pour le LLM, et Piper pour la synthèse vocale (TTS).

Initialiser la STT (Vosk)

stt = Vosk(language="en-us")

Charge le modèle Vosk pour l’anglais américain. Changez le code de langue (par exemple, zh-cn, es) pour correspondre à votre pack vocal pour une meilleure précision.

Initialiser la TTS (Piper)

tts = Piper()
tts.set_model("en_US-amy-low")

Crée un moteur Piper et sélectionne une voix spécifique. Choisissez un modèle que vous avez testé dans 1. Tester Piper. Les voix de qualité inférieure sont plus rapides et utilisent moins de CPU.

Instructions LLM et message de bienvenue

INSTRUCTIONS = (
    "You are a helpful assistant. Answer directly in plain English. "
    "Do NOT include any hidden thinking, analysis, or tags like <think>."
)
WELCOME = "Hello! I'm your voice chatbot. Speak when you're ready."

Deux choix clés pour l’expérience utilisateur :

  • Garder les réponses courtes et directes (aide à la clarté de la TTS).

  • Interdire explicitement les balises de « chaîne de pensée » cachées pour réduire les sorties bruitées.

Se connecter à Ollama et définir le périmètre de conversation

llm = Ollama(ip="localhost", model="llama3.2:3b")
llm.set_max_messages(20)
llm.set_instructions(INSTRUCTIONS)
  • ip="localhost" suppose que le serveur Ollama fonctionne sur le même Pi. S’il fonctionne sur une autre machine du LAN, mettez l”adresse IP LAN de cet ordinateur et activez Exposer au réseau dans Ollama.

  • set_max_messages(20) conserve un historique de conversation court. Réduisez ceci si la mémoire/la latence est limitée.

Supprimer le raisonnement / balises cachés avant de parler

def strip_thinking(text: str) -> str:
    if not text:
        return ""
    text = re.sub(r"<\s*think[^>]*>.*?<\s*/\s*think\s*>", "", text, flags=re.DOTALL|re.IGNORECASE)
    text = re.sub(r"<\s*thinking[^>]*>.*?<\s*/\s*thinking\s*>", "", text, flags=re.DOTALL|re.IGNORECASE)
    text = re.sub(r"```(?:\s*thinking)?\s*.*?```", "", text, flags=re.DOTALL|re.IGNORECASE)
    text = re.sub(r"\[/?thinking\]", "", text, flags=re.IGNORECASE)
    return re.sub(r"\s+\n", "\n", text).strip()

Certains modèles peuvent émettre des balises de style interne (par exemple, <think>…). Cette fonction les supprime afin que votre TTS ne prononce que la réponse finale.

Astuce : Si vous voyez d’autres artefacts à l’écran (parce que vous diffusez des jetons bruts), cette fonction garantit déjà que la sortie parlée reste propre.

Boucle principale : saluer une fois, puis écouter → penser → parler

print(WELCOME)
tts.say(WELCOME)

Saluer l’utilisateur via le terminal et le haut-parleur. Se produit une fois au démarrage.

Écouter (STT en streaming avec partiels en direct)

print("\n🎤 Écoute... (Appuyez sur Ctrl+C pour arrêter)")

text = ""
for result in stt.listen(stream=True):
    if result["done"]:
        text = result["final"].strip()
        print(f"[VOUS] {text}")
    else:
        print(f"[VOUS] {result['partial']}", end="\r", flush=True)
  • stream=True produit des transcriptions partielles pour un retour immédiat et une transcription finale lorsque l’énoncé se termine.

  • Le texte reconnu final est stocké dans text et imprimé une fois.

Garde : Si rien n’a été reconnu, vous sautez l’appel LLM :

if not text:
    print("[INFO] Rien n'a été reconnu. Réessayez.")
    time.sleep(0.1)
    continue

Cela évite d’envoyer des invites vides au modèle (économie de temps et de jetons).

Penser (LLM) avec impression en streaming

reply_accum = ""
response = llm.prompt(text, stream=True)
for next_word in response:
    if next_word:
        print(next_word, end="", flush=True)
        reply_accum += next_word
print("")
  • Envoie la transcription finale au LLM local et imprime les jetons à leur arrivée pour une faible latence.

  • Pendant ce temps, vous accumulez la réponse complète dans reply_accum pour le post-traitement.

Remarque : Si vous préférez ne pas afficher les jetons bruts, définissez stream=False et imprimez simplement la chaîne finale.

Parler (nettoyer d’abord, puis TTS une fois)

clean = strip_thinking(reply_accum)
if clean:
    tts.say(clean)
else:
    tts.say("Sorry, I didn't catch that.")
  • Nettoie le texte final pour supprimer les balises cachées, puis parle exactement une fois.

  • Garder la TTS sur un seul passage évite les invites répétées comme « [LLM] / [SAY] ».

Sortie et nettoyage

except KeyboardInterrupt:
    print("\n[INFO] Arrêt en cours...")
finally:
    tts.say("Goodbye!")
    print("Au revoir.")

Utilisez Ctrl+C pour arrêter. Le bot dit un court au revoir pour signaler une sortie propre.


Dépannage et FAQ

  • Le modèle est trop gros (erreur de mémoire)

    Utilisez un modèle plus petit comme moondream:1.8b ou exécutez Ollama sur un ordinateur plus puissant.

  • Pas de réponse d’Ollama

    Assurez-vous qu’Ollama est en cours d’exécution (ollama serve ou application de bureau ouverte). Si distant, activez Exposer au réseau et vérifiez l’adresse IP.

  • Vosk ne reconnaît pas la parole

    Vérifiez que votre microphone fonctionne. Essayez un autre pack linguistique (zh-cn, es, etc.) si nécessaire.

  • Piper silencieux ou erreurs

    Confirmez que le modèle vocal choisi est téléchargé et testé dans 1. Tester Piper.

  • Réponses trop longues ou hors sujet

    Modifiez INSTRUCTIONS pour ajouter : « Keep answers short and to the point. »