# BSN-Chatsystem — Chat-Agent: RAG-Antworten auf WhatsApp/Telegram > **Für Alicia (Qwen 3.6, 256K Context)** > **Stand:** 25.06.2026 > **Repo:** `git@git.datenhimmel.work:danielkrause/BSN-Chatsystem.git` > **Branch:** `main` > **Arbeitsverzeichnis:** `/home/hermes/workspace/bsn-chatbot/` > **Issue:** [#14](https://git.datenhimmel.work/danielkrause/BSN-Chatsystem/issues/14) --- ## Ziel WhatsApp- und Telegram-Nutzer können natürliche Fragen an den Bot stellen und bekommen RAG-basierte Antworten aus der Datenbank (Submissions, Aussteller, Neuheiten). Beispielfragen: - „Welches Spiel ist ausverkauft?" - „Was passiert in Halle 3?" - „Wo finde ich eine Katze?" - „Gibt es Neuheiten von Kosmos?" --- ## Aufgabe 14a — Neues Modul: `chat_agent.py` ### Datei: `chat_agent.py` (neue Datei im Repo-Root) **Funktionen:** 1. **`is_boardgame_question(text: str) -> bool`** - Guard-Filter: Prüft ob die Frage Brettspiel/SPIEL-bezogen ist - Keywords: `spiel`, `brettspiel`, `halle`, `stand`, `verlag`, `aussteller`, `messe`, `essen`, `spiel'`, `ausverkauft`, `neuheit`, `katze`, `kOSMOS`, `feuerland`, `catan`, `pegasus`, `asmodee`, `ravensburger`, `schmidt`, `hans im glück`, `lookout`, `zoch`, `amigo`, `queen games`, `days of wonder`, `rebel`, `portal` - Auch erlaubt: Fragen mit `?` am Ende + deutschem Text (mind. 10 Zeichen) - IMMER True bei offensichtlichen Brettspiel-Fragen - IMMER False bei: Politik, Erwachseneninhalte, Beleidigungen, irrelevante Themen 2. **`answer_question(db, message: str, sender_name: str = "", history: list | None = None) -> str`** - Baut Kontext aus 3 Tabellen (wie `/api/chat` in `app.py`): - `submissions` (veröffentlichte Beiträge, LIMIT 40) - `aussteller` (Verlage/Stände, gefiltert nach Keywords) - `neuheiten` (Spiele-Neuheiten, gefiltert nach Keywords, mit AUSVERKAUFT-Flag) - Sendet Kontext + Frage an LLM (`deepseek-v4-flash:cloud` via `https://ui.datenhimmel.work/api/chat/completions`) - System-Prompt: Nur antworten wenn Info im Kontext, sonst „Dazu habe ich leider keine Informationen." - Antwort immer auf Deutsch, 2-4 Sätze, mit Markdown-Links zu `/beitrag/ID` - API-Key aus `~/.hermes/config.yaml` lesen (wie `/api/chat`) - Timeout: 15 Sekunden, Fallback-Text bei Fehler **Referenz:** Die bestehende `/api/chat`-Route in `app.py` (Zeilen 4022–4310) enthält exakt die gleiche Logik. Extrahiere die Kernlogik in `answer_question()` und lass `/api/chat` die neue Funktion aufrufen (Refactoring). ### Qualität ```bash ruff check chat_agent.py --fix && ruff format chat_agent.py ``` **Commit:** `feat(chat): Chat-Agent-Modul mit Guard-Filter + RAG-Antworten` --- ## Aufgabe 14b — WhatsApp-Webhook: Frage-Erkennung + Antwort ### Datei: `app.py` — WhatsApp-Webhook (ab Zeile ~1162) **Einbau-Ort:** Nach dem Intake-Block (Speichern der Submission), VOR dem `db.commit()`. **Logik:** ```python # Nach dem Intake (nach Thread-Erstellung/Aktualisierung): if msg_type == "text" and not thread_found: from chat_agent import is_boardgame_question, answer_question if is_boardgame_question(content): try: reply = answer_question(db, content, sender_name) if reply: send_whatsapp_message(sender_id, reply) except Exception as e: print(f"[chat-agent error] {e}", file=sys.stderr) ``` **Wichtig:** - Nur bei TEXT-Nachrichten (nicht bei Bild/Audio/Video) - Nur bei NEUEN Threads (nicht bei Thread-Appends) - NACH allen anderen Checks (#out, registrieren, code, „Meine Daten löschen", FAQ) - Exception-Handling: Fehler NIE zum Webhook-500 führen → immer `return "OK", 200` - Inline-Import (vermeidet Circular-Imports) ### Qualität ```bash ruff check app.py --fix && ruff format app.py ``` **Commit:** `feat(whatsapp): Frage-Erkennung + Chat-Agent-Antwort im WhatsApp-Webhook` --- ## Aufgabe 14c — Telegram-Webhook: Gleiche Logik ### Datei: `app.py` — Telegram-Webhook (Zeilen 5293–5317) Alternativ: `webhook_queue.py` → `telegram_intake()` (Zeilen 172–215). **Wo einbauen:** Nachdem die Nachricht aus dem Telegram-Update extrahiert wurde. **Logik:** Gleiche wie WhatsApp: ```python text = msg.get("text", "") if text: from chat_agent import is_boardgame_question, answer_question if is_boardgame_question(text): chat_id = msg.get("chat", {}).get("id") reply = answer_question(db, text, sender_name) # Sende Antwort via Telegram API requests.post(f"{TELEGRAM_API}/sendMessage", json={ "chat_id": chat_id, "text": reply, "parse_mode": "Markdown" }) ``` ### Qualität ```bash ruff check app.py --fix && ruff format app.py ``` **Commit:** `feat(telegram): Chat-Agent-Antwort im Telegram-Webhook` --- ## Aufgabe 14d — Testfragen (manuel) **NICHT automatisieren.** Nachdem Alicia gebaut hat, testet Cody: ```bash # Test 1: Brettspiel-Frage curl -X POST http://localhost:5002/api/chat \ -H "Content-Type: application/json" \ -d '{"message": "Welches Spiel ist ausverkauft?"}' # Test 2: Halle-Frage curl -X POST http://localhost:5002/api/chat \ -H "Content-Type: application/json" \ -d '{"message": "Was passiert in Halle 3?"}' # Test 3: Katzen-Frage (Guard muss durchlassen!) curl -X POST http://localhost:5002/api/chat \ -H "Content-Type: application/json" \ -d '{"message": "Wo finde ich eine Katze?"}' # Test 4: Off-Topic (Guard muss blocken) curl -X POST http://localhost:5002/api/chat \ -H "Content-Type: application/json" \ -d '{"message": "Wie wird das Wetter morgen?"}' ``` --- ## Qualitätsregeln ```bash # Vor JEDEM Commit: ruff check . --fix && ruff format . && pytest -v ``` - **Commit-Messages:** `feat(chat):` / `feat(whatsapp):` / `feat(telegram):` - **Docstrings:** Deutsch, Google-Style - **Typ-Annotationen:** Jede Funktion (Python 3.13, PEP 604) - **Kein `print()`** → `sys.stderr` für Debug - **BSN-Blau `#20228a`** für Links in Antworten --- ## Wichtige Hinweise 1. **Kein Circular Import:** `chat_agent.py` importiert NICHTS aus `app.py`. Nur `sqlite3` + `requests` + `os` + `re`. 2. **Guard NICHT zu streng:** „Katze" ist ein legitimes Brettspiel-Thema (Die Crew, Katze im Sack, etc.). Der Guard soll nur WIRKLICHEN Missbrauch abwehren. 3. **Timeout:** LLM-Call max 15 Sekunden — WhatsApp erwartet schnelle Antwort (< 20s). 4. **Nie 500er:** Alle Chat-Agent-Fehler werden gefangen → immer `return "OK", 200`. 5. **`bsn-secrets` nicht nötig:** Der API-Key kommt aus `~/.hermes/config.yaml` (lokale Datei).