# 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, Suche und Indexbau getestet; Docling läuft hier 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. - **Neu anlegen:** Mit festen Versionen aus `envs/environment-docling.yml` bzw. `envs/environment-local_rag.yml`. Die Befehle stehen jeweils im Kopf der Datei. - **Geschwindigkeit unter Rosetta:** Spürbar langsamer. Im Test brauchte die Suche etwa 2,3 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.