Load bge-m3 as safetensors for x86; update README, add EINSTIEG.md

- EMBEDDING_REVISION pins the official safetensors conversion of bge-m3, so the x86
  env (torch 2.2.2) can load it; identical vectors, no index rebuild
- README rewritten for the current state (personas, providers, retrieval, environments)
- EINSTIEG.md: short German guide to running and configuring the chat

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-26 07:26:29 +01:00
co-authored by Claude Opus 5.5
parent e4b7f0ce34
commit ce9a863a87
3 changed files with 381 additions and 209 deletions
+156
View File
@@ -0,0 +1,156 @@
# Kafka-Chat: Einstieg
Stand: Oktober 2026. Ausführliche englische Referenz: [README.md](README.md).
## Starten
**Conda-Umgebungen**
| Umgebung | Architektur | Einsatz |
|---|---|---|
| `docling` | arm64 (Apple Silicon), Python 3.12, torch 2.14 | Standard. Auch für Docling (`prepare_corpus.py`). |
| `local_rag` | x86_64 (Intel-Mac oder Rosetta), Python 3.10, torch 2.2.2 | Alternative für x86. Chat und Suche getestet; Indexbau sollte gehen (gleiches Embedding), ungetestet. Docling nicht. |
- **Warum bge-m3 auf x86 nur über safetensors lädt:** Für macOS x86 gibt es höchstens torch 2.2.2. `transformers` lädt `.bin`-Gewichte aber erst ab torch 2.6, und bge-m3 hat im Hauptzweig nur `pytorch_model.bin`.
- **Die Lösung im Code:** `local_rag.py` lädt deshalb die offizielle safetensors-Konvertierung (`EMBEDDING_REVISION`). Die Gewichte sind dieselben, die Vektoren in beiden Umgebungen identisch, und dieselben Indizes funktionieren in beiden.
- **Geschwindigkeit unter Rosetta:** Spürbar langsamer. Im Test brauchte die Suche gut 5 statt gut 1 Sekunde, der Serverstart war dagegen nicht langsamer.
**Voraussetzungen (einmalig)**
- Ollama mit dem Modell: `ollama pull gemma4:12b`.
- Für ChatGPT und Realtime eine Datei `.env` im Projektordner mit `OPENAI_API_KEY=sk-...`. Sie ist git-ignoriert, am besten mit `chmod 600` schützen. Ohne Key sind beide Einträge im Menü ausgegraut.
- Die beiden Vektorspeicher `vectorstore_pypdf_2000/` und `vectorstore_primary_pypdf_2000/`. Sie sind nicht im Git; Neubau siehe unten.
**Server starten**
```bash
cd /Users/Philipp/Desktop/workspace/diverses/local_rag
conda activate docling # auf x86: conda activate local_rag
uvicorn server:app --port 8010
```
Dann im Browser http://localhost:8010 öffnen. Der Start dauert etwa eine Minute: Embedding-Modell, Reranker und Stichwortindex werden geladen. Danach ist auch die erste Frage schnell.
- Optionen: `--reload` startet den Server bei Codeänderungen neu, `--log-level debug` zeigt mehr Details.
- Ohne `--reload` braucht jede Änderung an `.py`-Dateien einen Neustart. Bei `templates/chat.html` reicht es, die Seite neu zu laden.
**Was im Terminal steht, pro Frage**
```
[RAG] User question: '…'
[Zeit] LLM-Aufruf 1: 6.86s (Suche angefordert)
[RAG] Tool search '…': 5 chunk(s)
[Zeit] Suche 1.37s (davon Reranker 1.21s)
[Zeit] LLM-Aufruf 2: 15.31s (Antwort)
[Zeit] Gesamt 23.54s
```
Danach folgen die gefundenen Chunks mit Quelle, Seite und Score sowie die vollständige Antwort.
## Aufbau
```
Browser (templates/chat.html)
│ POST /api/chat {message, history, persona, llm_provider}
▼
server.py ── wählt Figur (personas.py) und RAG-Instanz (Anbieter × Vektorspeicher)
▼
local_rag.py · LocalRAG.query_with_history
│ Systemprompt + Verlauf + Frage an das LLM, Such-Werkzeug angeboten
│ ┌─ LLM ruft search_kafka_corpus(query) auf (höchstens 3 Runden)
│ │ retrieve(): FAISS (bge-m3) + BM25 → Fusion (RRF) → 25 Kandidaten
│ │ → Reranker (bge-reranker-v2-m3) → beste 5 Chunks
│ └─ LLM antwortet mit den Auszügen
▼
Antwort + gefundene Chunks + Suchanfragen → Browser
```
**Dateien**
| Datei | Inhalt |
|---|---|
| [server.py](server.py) | FastAPI-Server, Modellwahl, eine RAG-Instanz pro Anbieter und Vektorspeicher, Konsolenausgabe |
| [personas.py](personas.py) | die drei Figuren: Texte, Bilder, Systemprompts, Werkzeugbeschreibung, Vektorspeicher |
| [local_rag.py](local_rag.py) | Laden der Modelle, Suche (`retrieve`), Werkzeugschleife (`query_with_history`), Indexbau |
| [realtime_chat.py](realtime_chat.py) | Anbindung der OpenAI Realtime API (WebSocket, nur Text) als LangChain-Modell |
| [templates/chat.html](templates/chat.html) | Oberfläche, Menüs für Figur und Modell |
| `templates/visuals/` | Bilder (`*.png` Vorlagen, `*.jpg` Webversionen, `*_avatar.jpg`) |
| [add_pdfs.py](add_pdfs.py) | Vektorspeicher aus einem PDF-Ordner bauen |
| [eval_retrieval.py](eval_retrieval.py) | Suchqualität auf einem Goldset messen (Recall@k, MRR, optional LLM-Judge) |
## Stellschrauben
### Verhalten der Figuren: [personas.py](personas.py)
Pro Figur ein Eintrag in `PERSONAS`; eine neue Figur ist ein neuer Eintrag mit eigenem Bild.
| Feld | wirkt auf |
|---|---|
| `system_prompt` | Ton, Rolle, Regeln für Suche und Belege |
| `tool_description` | was das Modell über das Such-Werkzeug erfährt (ohne Feld gilt `SEARCH_TOOL_DESCRIPTION`) |
| `vectorstore` | welcher Index durchsucht wird |
| `label`, `topic`, `name`, `role`, `place`, `status`, `greeting`, `suggestions` | Texte in der Oberfläche |
| `image`, `image_position`, `image_alt`, `avatar` | Bilder |
- Die Regeln zur Suche für Zeitgenosse und Autor stehen gemeinsam in `_TOOL_RULES`.
- Der Prompt der Forscherin ist `SYSTEM_PROMPT` in `local_rag.py`.
### Sprachmodelle: [server.py](server.py) und [local_rag.py](local_rag.py)
| Einstellung | aktuell | wo |
|---|---|---|
| lokales Modell (Ollama) | `gemma4:12b`, denkt standardmäßig (`reasoning` nicht gesetzt) | `OLLAMA_MODEL` in server.py, `ChatOllama(...)` in local_rag.py |
| ChatGPT | `gpt-6.1-sol` | `OPENAI_MODEL` in server.py |
| Denkaufwand ChatGPT | `low`, über die Responses API | `ChatOpenAI(...)` in local_rag.py |
| Realtime | `gpt-realtime-2.1-mini` | `REALTIME_MODEL` in server.py |
| Voreinstellung im Menü | ChatGPT | `selected` in chat.html |
- **gpt-6.1-sol:** kennt keinen Denkaufwand `none`. Werkzeuge funktionieren nur über die Responses API, deshalb steht `use_responses_api=True`.
- **Realtime:** öffnet pro Modellaufruf eine eigene WebSocket-Sitzung.
### Suche: Konstanten oben in [local_rag.py](local_rag.py)
| Konstante | aktuell | Bedeutung |
|---|---|---|
| `RETRIEVAL_K` | 5 | Chunks pro Suche an das LLM |
| `RETRIEVAL_MODE` | `hybrid` | `dense` (nur Vektoren), `hybrid` (Vektoren + BM25, RRF), `union` |
| `CANDIDATES_K` | 25 | Kandidaten vor dem Reranker. 25 war schneller und nicht schlechter als 50. |
| `RERANKER_MODEL` | `BAAI/bge-reranker-v2-m3` | `None` schaltet das Reranking ab. Läuft auf der GPU in fp16. |
| `MAX_TOOL_ROUNDS` | 3 | Suchen pro Nachricht, danach muss das Modell antworten |
| `SEARCH_TOOL_DESCRIPTION` | Text | Werkzeugbeschreibung für die Forscherin |
Die Suche dauert etwa 1 bis 1,5 Sekunden pro Aufruf, fast nur der Reranker. Den größten Teil der Antwortzeit brauchen die Modellaufrufe.
### Index: Neubau nötig bei Änderung
| Konstante | aktuell |
|---|---|
| `EMBEDDING_MODEL` | `BAAI/bge-m3` (normalisierte Vektoren) |
| `CHUNK_SIZE` / `CHUNK_OVERLAP` | 2000 / 400 Zeichen |
| `PDF_LOADER` | `pypdf`; `docling` war im Goldset-Vergleich nicht besser |
| `CHUNK_HEADERS` | `False` |
Neubau der beiden Indizes. `data_primary/` enthält nur einen Verweis auf `data/Kafka_Die_Verwandlung_.pdf`.
```bash
python add_pdfs.py --data data --index ./vectorstore_pypdf_2000 --loader pypdf --no-headers
```
```bash
python add_pdfs.py --data data_primary --index ./vectorstore_primary_pypdf_2000 --loader pypdf --no-headers
```
Jeder Index speichert seine Einstellungen in `index_info.json`. Passt das Embedding-Modell nicht zum Code, verweigert `LocalRAG` das Laden.
### Suchqualität messen
```bash
python eval_retrieval.py --goldset eval/goldset_kafka_human.csv --report eval/mein_bericht.txt
```
- `--judge gpt-6-luna` lässt zusätzlich ein LLM prüfen, ob die Antwort in den Top 5 steht; das braucht den Key.
- Stand auf dem menschlichen Goldset (30 Fragen): R@5 0,73, MRR 0,68.
## Gut zu wissen
- **Speicher:** Embedding-Modell, Reranker und BM25-Index gibt es pro Prozess nur einmal, auch bei mehreren Anbietern. Ollama braucht zusätzlich etwa 8 bis 10 GB für gemma4:12b.
- **Nicht im Git:** Korpus-PDFs (`data/`), Vektorspeicher, `.env` und Schlüsseldateien.