Zurück zur Startseite

Bot-Architektur mit Whisper.cpp und aiogram 3.x

Der Artikel beschreibt die Architektur eines Telegram-Bots für Spanisch-Training mit lokalem Whisper.cpp. Implementierte asynchrone Sprachverarbeitung, wortweise Aussprachebewertung und FSM für Modi. Code-Beispiele für die Integration in aiogram 3.x.

Whisper.cpp in Telegram: asynchrones STT und FSM für Bot
Advertisement 728x90

Telegram-Bot für spanische Spracherkennung mit Whisper.cpp und FSM erstellen

Ein Telegram-Bot, der Nutzern beim Lernen der spanischen Sprache hilft, nutzt die lokale Whisper.cpp-Engine zur Spracherkennung, ohne auf Cloud-APIs angewiesen zu sein. Das System bewertet die Aussprache anhand Referenzphrasen, unterstützt konversationelle Interaktionen über KI-Modelle und läuft effizient in Umgebungen mit begrenzten Ressourcen wie VPS. Durch die Integration von async aiogram 3.x mit dem whisper.cpp-Binärfile verarbeitet der Bot mehrere Anfragen gleichzeitig.

Wichtige Komponenten sind die Umwandlung von Audio in das 16 kHz mono WAV-Format, die Ausführung von whisper-cli in einem separaten Prozess sowie ein Wort-für-Wort-Vergleichsalgorithmus zur Bewertung der Genauigkeit.

Nicht-blockierende Integration von Whisper.cpp

Die Verwendung von subprocess.run() blockiert die aiogram-Ereignisschleife. Die Lösung? Einsatz von asyncio.create_subprocess_exec für asynchrone Ausführung und asyncio.to_thread, um rechenintensive pydub-Operationen auszulagern.

Google AdInline article slot
import asyncio
from pydub import AudioSegment
import subprocess
import os

async def recognize_voice_async(file_path: str) -> str:
    wav_path = "temp_voice.wav"
    txt_path = wav_path + ".txt"
    
    try:
        # 1. Konvertiere Audio in Whisper-kompatibles Format (16kHz, mono, WAV)
        # pydub-Verarbeitung in einem Thread auslagern, um die Ereignisschleife nicht zu blockieren
        audio = AudioSegment.from_file(file_path)
        await asyncio.to_thread(
            lambda: audio.set_frame_rate(16000)
                     .set_channels(1)
                     .set_sample_width(2)
                     .export(wav_path, format="wav")
        )

        if not os.path.exists(wav_path):
            raise FileNotFoundError("WAV-Datei wurde nicht erstellt")

        # 2. Führe whisper.cpp asynchron aus
        process = await asyncio.create_subprocess_exec(
            "/root/whisper.cpp/build/bin/whisper-cli", # Pfad zur Binärdatei
            "-m", "/root/whisper.cpp/models/ggml-tiny.bin", # Tiny-Modell
            "-f", wav_path,
            "--language", "es",
            "--output-txt",
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE
        )
        
        stdout, stderr = await process.communicate()

        if process.returncode != 0:
            print(f"[ERROR] Whisper fehlgeschlagen: {stderr.decode()}")
            return ""

        # 3. Ergebnis lesen
        if os.path.exists(txt_path):
            with open(txt_path, "r", encoding="utf-8") as f:
                text = f.read().strip()
            return text
        return ""
    
    except Exception as e:
        print(f"[KRITISCHER FEHLER] {e}")
        return ""
    finally:
        # Temporäre Dateien löschen
        for f in [wav_path, txt_path, file_path]:
            if os.path.exists(f):
                os.remove(f)

Dieser Code gewährleistet eine Echtzeit-Spracherkennung ohne, dass der Bot einfriert. Das ggml-tiny.bin-Modell wurde wegen seiner Geschwindigkeit auf der CPU ausgewählt.

Aussprachebewertungsalgorithmus

Eine einfache Zeichenkettenvergleich ist aufgrund typischer Fehler von Whisper bei Artikeln und Endungen unzuverlässig. Stattdessen verwenden wir einen wortbasierten Positionsscore-Algorithmus:

def calculate_accuracy(expected: str, recognized: str) -> int:
    if not recognized:
        return 0
    
    expected_words = expected.lower().split()
    recognized_words = recognized.lower().split()
    
    if not expected_words:
        return 0

    # Wörter nach Position vergleichen
    min_len = min(len(expected_words), len(recognized_words))
    matches = sum(1 for i in range(min_len) if expected_words[i] == recognized_words[i])
    
    # Formel: (Treffer / Länge der Referenz) * 100
    accuracy = int((matches / len(expected_words)) * 100)
    
    return max(0, min(100, accuracy))

Beispiel-Ergebnisse:

Google AdInline article slot
  • Vollständige Übereinstimmung: 100%
  • Letztes Wort fehlt: 80–90%
  • Keine Übereinstimmung: 0–20%

Der Erfolgs-Schwellenwert liegt bei 70 %. Zukünftige Verbesserungen werden den Levenshtein-Abstand für Fehler innerhalb von Wörtern nutzen.

FSM für Zustandsmanagement und Routing

aiogram.fsm ermöglicht die Trennung zweier Modi: freie Konversation und Aussprachetraining.

from aiogram.fsm.state import State, StatesGroup

class UserStates(StatesGroup):
    waiting_for_ai_dialog = State()  # Freier Chat-Modus

Ein universeller Handler für Sprachnachrichten:

Google AdInline article slot
@dp.message(F.voice)
async def universal_voice_handler(message: Message, state: FSMContext):
    current_state = await state.get_state()
    
    # ZWEIG 1: KI-Konversation
    if current_state == "UserStates:waiting_for_ai_dialog":
        from handlers.voice_ai import handle_ai_dialog_voice
        await handle_ai_dialog_voice(message, bot, state)
        return

    # ZWEIG 2: Aussprachetraining (Lektionen)
    # ... Logik zum Abrufen der Referenzphrase, Erkennen und Bewerten

Technologiestack und Datenhaltung

  • STT: whisper.cpp (ggml-tiny.bin, spanisch)
  • TTS: gTTS + pydub
  • LLM: Externe API
  • Speicherung: JSON-Dateien (users.json, lessons.json, phrases.json)

Admin-Befehle (/addphrase, /stats) ermöglichen dynamische Inhaltsaktualisierungen. Skalierbare Bereitstellung via PostgreSQL und Docker.

Wesentliche Vorteile:

  • Asynchrone Integration von whisper.cpp verhindert das Einfrieren des Bots.
  • Wortbasierte Vergleichslogik liefert präzise Ausspracheproben ohne Cloud-Abhängigkeiten.
  • FSM erlaubt problemlose Erweiterung von Modi ohne Handler-Neuimplementierung.
  • Lokale Verarbeitung garantiert Datenschutz und Kosteneffizienz.
  • Das tiny.bin-Modell läuft schnell auf der CPU mit minimaler Latenz.

— Editorial Team

Advertisement 728x90

Weiterlesen