185 lines
6.4 KiB
Markdown
185 lines
6.4 KiB
Markdown
# 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).
|