.. include:: /index.rst :start-after: start_hello_message :end-before: end_hello_message 6. Lokaler Sprach-Chatbot =========================== In dieser Lektion kombinieren Sie alles, was Sie bisher gelernt haben — **Spracherkennung (STT)**, **Text-to-Speech (TTS)** und ein **lokales LLM (Ollama)** —, um einen vollständig offline arbeitenden **Sprach-Chatbot** zu erstellen, der auf Ihrem Fusion HAT+ läuft. .. raw:: html Der Ablauf ist einfach: #. **Zuhören** — Das Mikrofon erfasst Ihre Sprache und transkribiert sie mit **Vosk**. #. **Denken** — Der Text wird an ein lokales **LLM** gesendet, das über Ollama läuft (z. B. ``llama3.2:3b``). #. **Sprechen** — Der Chatbot antwortet laut mit **Piper TTS**. So entsteht ein **freihändig nutzbarer Gesprächsroboter**, der Sie in Echtzeit verstehen und beantworten kann. ---- Bevor Sie beginnen --------------------------------- Stellen Sie sicher, dass Sie Folgendes vorbereitet haben: * **Piper TTS** getestet haben (:ref:`test_piper`) und ein funktionierendes Sprachmodell ausgewählt haben. * **Vosk STT** getestet haben (:ref:`test_vosk`) und das passende Sprachpaket gewählt haben (z. B. ``en-us``). * **Ollama** installiert haben (:ref:`download_ollama`) — entweder auf Ihrem Pi oder auf einem anderen Computer — und ein Modell wie ``llama3.2:3b`` heruntergeladen haben (oder ein kleineres wie ``moondream:1.8b``, falls der Speicher begrenzt ist). ---- Code ausführen -------------- #. Öffnen Sie das Beispielskript: .. code-block:: bash cd ~/ai-lab-kit/llm/ sudo nano local_voice_chatbot.py #. Passen Sie die Parameter bei Bedarf an: * ``stt = Vosk(language="en-us")``: Ändern Sie dies so, dass es zu Ihrem Akzent bzw. Sprachpaket passt (z. B. ``en-us``, ``zh-cn``, ``es``). * ``tts.set_model("en_US-amy-low")``: Ersetzen Sie dies durch das Piper-Sprachmodell, das Sie in :ref:`test_piper` geprüft haben. * ``llm = Ollama(ip="localhost", model="llama3.2:3b")``: Aktualisieren Sie sowohl ``ip`` als auch ``model`` entsprechend Ihrer Konfiguration. * ``ip``: Wenn Ollama auf **demselben Pi** läuft, verwenden Sie ``localhost``. Wenn Ollama auf einem anderen Computer im lokalen Netzwerk läuft, aktivieren Sie in Ollama **Expose to network** und setzen ``ip`` auf die LAN-IP dieses Computers. * ``model``: Muss exakt mit dem Modellnamen übereinstimmen, den Sie in Ollama heruntergeladen bzw. aktiviert haben. #. Führen Sie das Skript aus: .. code-block:: bash cd ~/ai-lab-kit/llm/ sudo python3 local_voice_chatbot.py #. Nach dem Start sollten Sie Folgendes sehen: * Der Bot begrüßt Sie mit einer gesprochenen Willkommensnachricht. * Er wartet auf Spracheingabe. * Vosk transkribiert Ihre Sprache in Text. * Der Text wird an Ollama gesendet, das die Antwort als Stream zurückgibt. * Die Antwort wird bereinigt (versteckte Denkprozesse werden entfernt) und anschließend von Piper laut gesprochen. * Sie können das Programm jederzeit mit ``Ctrl+C`` beenden. ---- Code ---- .. code-block:: python import re import time from fusion_hat.llm import Ollama from fusion_hat.stt import Vosk from fusion_hat.tts import Piper # Initialize speech recognition stt = Vosk(language="en-us") # Initialize TTS tts = Piper() tts.set_model("en_US-amy-low") # Instructions for the LLM INSTRUCTIONS = ( "You are a helpful assistant. Answer directly in plain English. " "Do NOT include any hidden thinking, analysis, or tags like ." ) WELCOME = "Hello! I'm your voice chatbot. Speak when you're ready." # Initialize Ollama connection llm = Ollama(ip="localhost", model="llama3.2:3b") llm.set_max_messages(20) llm.set_instructions(INSTRUCTIONS) # Utility: clean hidden reasoning 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🎤 Listening... (Press Ctrl+C to stop)") # Collect final transcript from Vosk text = "" for result in stt.listen(stream=True): if result["done"]: text = result["final"].strip() print(f"[YOU] {text}") else: print(f"[YOU] {result['partial']}", end="\r", flush=True) if not text: print("[INFO] Nothing recognized. Try again.") time.sleep(0.1) continue # Query Ollama with 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("") # Clean and speak 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] Stopping...") finally: tts.say("Goodbye!") print("Bye.") if __name__ == "__main__": main() ---- Code-Analyse ------------- **Importe und globale Initialisierung** .. code-block:: python import re import time from fusion_hat.llm import Ollama from fusion_hat.stt import Vosk from fusion_hat.tts import Piper Bindet die drei Untersysteme ein, die Sie zuvor verwendet haben: **Vosk** für Speech-to-Text (STT), **Ollama** für das LLM und **Piper** für Text-to-Speech (TTS). **STT initialisieren (Vosk)** .. code-block:: python stt = Vosk(language="en-us") Lädt das Vosk-Modell für US-Englisch. Ändern Sie den Sprachcode (z. B. ``zh-cn``, ``es``), damit er zu Ihrem Sprachpaket passt und eine bessere Erkennungsgenauigkeit erreicht wird. **TTS initialisieren (Piper)** .. code-block:: python tts = Piper() tts.set_model("en_US-amy-low") Erstellt eine Piper-Engine und wählt eine bestimmte Stimme aus. Wählen Sie ein Modell, das Sie bereits in :ref:`test_piper` getestet haben. Stimmen mit geringerer Qualität sind schneller und benötigen weniger CPU-Leistung. **LLM-Anweisungen und Begrüßungstext** .. code-block:: python INSTRUCTIONS = ( "You are a helpful assistant. Answer directly in plain English. " "Do NOT include any hidden thinking, analysis, or tags like ." ) WELCOME = "Hello! I'm your voice chatbot. Speak when you're ready." Hier gibt es zwei wichtige UX-Entscheidungen: * Halten Sie die **Antworten kurz und direkt** (das verbessert die Verständlichkeit bei TTS). * Verhindern Sie ausdrücklich versteckte „Chain-of-Thought“-Tags, um störende Ausgaben zu reduzieren. **Mit Ollama verbinden und den Gesprächsumfang festlegen** .. code-block:: python llm = Ollama(ip="localhost", model="llama3.2:3b") llm.set_max_messages(20) llm.set_instructions(INSTRUCTIONS) * ``ip="localhost"`` setzt voraus, dass der Ollama-Server auf demselben Pi läuft. Wenn er auf einem anderen Gerät im lokalen Netzwerk läuft, tragen Sie die **LAN-IP** dieses Computers ein und aktivieren Sie in Ollama *Expose to network*. * ``set_max_messages(20)`` begrenzt den Gesprächsverlauf auf eine kurze Historie. Reduzieren Sie diesen Wert, wenn Arbeitsspeicher oder Reaktionszeit knapp sind. **Versteckte Denkprozesse / Tags vor der Sprachausgabe entfernen** .. code-block:: python 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() Einige Modelle geben möglicherweise interne Tags aus (z. B. ``…``). Diese Funktion entfernt solche Inhalte, damit Ihre TTS-Ausgabe **nur** die endgültige Antwort spricht. **Tipp:** Wenn Sie andere Artefakte auf dem Bildschirm sehen (weil rohe Tokens gestreamt werden), sorgt diese Funktion bereits dafür, dass die **gesprochene** Ausgabe sauber bleibt. **Hauptschleife: einmal begrüßen, dann zuhören → denken → sprechen** .. code-block:: python print(WELCOME) tts.say(WELCOME) Begrüßt den Benutzer über das Terminal und den Lautsprecher. Dies geschieht einmal beim Start. **Zuhören (streamendes STT mit Live-Teilergebnissen)** .. code-block:: python print("\n🎤 Listening... (Press Ctrl+C to stop)") text = "" for result in stt.listen(stream=True): if result["done"]: text = result["final"].strip() print(f"[YOU] {text}") else: print(f"[YOU] {result['partial']}", end="\r", flush=True) * ``stream=True`` liefert **Teilergebnisse** für sofortiges Feedback und ein **Endergebnis**, sobald die Äußerung abgeschlossen ist. * Der endgültig erkannte Text wird in ``text`` gespeichert und einmal ausgegeben. **Schutzmechanismus:** Wenn nichts erkannt wurde, wird der LLM-Aufruf übersprungen: .. code-block:: python if not text: print("[INFO] Nothing recognized. Try again.") time.sleep(0.1) continue Dadurch werden keine leeren Eingaben an das Modell gesendet (das spart Zeit und Tokens). **Denken (LLM) mit gestreamter Ausgabe** .. code-block:: python 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("") * Sendet den finalen Transkripttext an das lokale LLM und **gibt Tokens sofort aus**, sobald sie eintreffen, um eine geringe Latenz zu erreichen. * Gleichzeitig wird die vollständige Antwort in ``reply_accum`` gesammelt, damit sie anschließend weiterverarbeitet werden kann. **Hinweis:** Wenn Sie **keine** rohen Tokens anzeigen möchten, setzen Sie ``stream=False`` und geben nur den fertigen Text aus. **Sprechen (zuerst bereinigen, dann TTS einmal ausführen)** .. code-block:: python clean = strip_thinking(reply_accum) if clean: tts.say(clean) else: tts.say("Sorry, I didn't catch that.") * Bereinigt den finalen Text, entfernt versteckte Tags und **spricht dann genau einmal**. * Wenn TTS nur einmal ausgeführt wird, werden wiederholte Ausgaben wie „[LLM] / [SAY]“ vermieden. **Beenden und Aufräumen** .. code-block:: python except KeyboardInterrupt: print("\n[INFO] Stopping...") finally: tts.say("Goodbye!") print("Bye.") Verwenden Sie **Ctrl+C**, um das Programm zu beenden. Der Bot spricht zum Abschluss eine kurze Verabschiedung, um einen sauberen Programmende zu signalisieren. ---- Fehlerbehebung & FAQ --------------------- * **Das Modell ist zu groß (Speicherfehler)** Verwenden Sie ein kleineres Modell wie ``moondream:1.8b`` oder führen Sie Ollama auf einem leistungsstärkeren Computer aus. * **Keine Antwort von Ollama** Stellen Sie sicher, dass Ollama läuft (``ollama serve`` oder Desktop-App geöffnet). Wenn Sie einen entfernten Rechner verwenden, aktivieren Sie **Expose to network** und prüfen Sie die IP-Adresse. * **Vosk erkennt Sprache nicht** Überprüfen Sie, ob Ihr Mikrofon funktioniert. Versuchen Sie bei Bedarf ein anderes Sprachpaket (``zh-cn``, ``es`` usw.). * **Piper bleibt stumm oder meldet Fehler** Stellen Sie sicher, dass das ausgewählte Sprachmodell heruntergeladen wurde und in :ref:`test_piper` getestet worden ist. * **Die Antworten sind zu lang oder weichen vom Thema ab** Bearbeiten Sie ``INSTRUCTIONS`` und fügen Sie hinzu: **„Keep answers short and to the point.“**