RAG-Suche: Elasticsearch direkt statt RAGFlow-API #51

Closed
opened 2026-03-26 12:25:05 +00:00 by orbitalo · 3 comments
Owner

Problem

Die RAGFlow Hybrid-Suche liefert für deutsche Dokumente nur 60% Trefferquote:

  • Keyword-Tokenisierung versteht kein Deutsch (Stemming/Stopwords falsch)
  • RERANK_LIMIT (64) zu klein für 172k Chunks
  • Gescannte PDFs (Frakturschrift) haben unbrauchbaren OCR-Text
  • Session-History vergiftet Folgefragen mit altem OCR-Müll

Lösung

rag.py im Hausmeister-Bot (CT 116) umbauen: Elasticsearch direkt abfragen statt RAGFlow /retrieval API.

Architektur

Upload:  Browser → RAGFlow (CT 700) → chunken → embedden → Elasticsearch
Suche:   Telegram → Hausmeister (CT 116) → Elasticsearch direkt (CT 700)

RAGFlow bleibt als Ingestion-Pipeline (Upload, OCR, Chunking, Embedding). Nur der Suchweg ändert sich.

Aufgaben

  • iptables Port-Forward für ES Port 9200 auf pve-mu-3 (analog zu 8080)
  • rag.py umschreiben: ES-Query mit deutschem Analyzer + Vektor-Suche
  • Deutschen Analyzer in ES konfigurieren (Stemming, Stopwords, Compounds)
  • PDF-Chunks als "nicht maschinenlesbar" markieren wenn OCR-Qualität schlecht
  • Testen gegen alle 20 Testkategorien (Ziel: >85% Trefferquote)
  • Bot neu deployen via scp + pct push + systemctl restart

Technische Details

Key Value
ES-Index ragflow_61f51c8c279011f1a174bd19863ba33e
Chunks 172.732 (2.6 GB)
Dokumente 21.725
Embedding nomic-embed-text, 768d, Feld: q_768_vec
Dataset-ID dc24edda27a311f19fe7fb811de6f016
ES-Credentials elastic:infini_rag_flow
Text-Feld content_with_weight (Klartext)
Dateiname-Feld docnm_kwd (keyword, __ = Ordnertrenner)
Tailscale pve-mu-3 100.109.101.12

Hinweise

  • Dateien auf CT 116 schreiben via scp pve-hetzner:/tmp/ && pct push 116, NICHT SSH-Heredoc
  • Bot-Restart: systemctl restart hausmeister-bot.service
  • Aktuelle rag.py ist noch alte Version (RAGFlow-API)
## Problem Die RAGFlow Hybrid-Suche liefert für deutsche Dokumente nur **60% Trefferquote**: - Keyword-Tokenisierung versteht kein Deutsch (Stemming/Stopwords falsch) - `RERANK_LIMIT` (64) zu klein für 172k Chunks - Gescannte PDFs (Frakturschrift) haben unbrauchbaren OCR-Text - Session-History vergiftet Folgefragen mit altem OCR-Müll ## Lösung `rag.py` im Hausmeister-Bot (CT 116) umbauen: **Elasticsearch direkt abfragen** statt RAGFlow `/retrieval` API. ### Architektur ``` Upload: Browser → RAGFlow (CT 700) → chunken → embedden → Elasticsearch Suche: Telegram → Hausmeister (CT 116) → Elasticsearch direkt (CT 700) ``` RAGFlow bleibt als Ingestion-Pipeline (Upload, OCR, Chunking, Embedding). Nur der Suchweg ändert sich. ### Aufgaben - [ ] iptables Port-Forward für ES Port 9200 auf pve-mu-3 (analog zu 8080) - [ ] `rag.py` umschreiben: ES-Query mit deutschem Analyzer + Vektor-Suche - [ ] Deutschen Analyzer in ES konfigurieren (Stemming, Stopwords, Compounds) - [ ] PDF-Chunks als "nicht maschinenlesbar" markieren wenn OCR-Qualität schlecht - [ ] Testen gegen alle 20 Testkategorien (Ziel: >85% Trefferquote) - [ ] Bot neu deployen via `scp` + `pct push` + `systemctl restart` ### Technische Details | Key | Value | |---|---| | ES-Index | `ragflow_61f51c8c279011f1a174bd19863ba33e` | | Chunks | 172.732 (2.6 GB) | | Dokumente | 21.725 | | Embedding | nomic-embed-text, 768d, Feld: `q_768_vec` | | Dataset-ID | `dc24edda27a311f19fe7fb811de6f016` | | ES-Credentials | `elastic:infini_rag_flow` | | Text-Feld | `content_with_weight` (Klartext) | | Dateiname-Feld | `docnm_kwd` (keyword, `__` = Ordnertrenner) | | Tailscale pve-mu-3 | `100.109.101.12` | ### Hinweise - Dateien auf CT 116 schreiben via `scp pve-hetzner:/tmp/ && pct push 116`, NICHT SSH-Heredoc - Bot-Restart: `systemctl restart hausmeister-bot.service` - Aktuelle `rag.py` ist noch alte Version (RAGFlow-API)
Author
Owner

Umsetzungsplan (Analyse abgeschlossen)

Erkenntnisse aus der Analyse

Punkt Ergebnis
ES Docker Port 1200 (Docker-Mapping 1200→9200 intern), NICHT 9200
content_with_weight Nicht indexiert ("index": false) — nur gespeichert, keine Textsuche moeglich
Suchbares Textfeld content_ltks (whitespace-Analyzer, kein Deutsch)
Vektor-Feld q_768_vec (768d, cosine, nomic-embed-text)
German Analyzer ES built-in german verfuegbar und getestet
iptables 8080 Existiert, aber nicht persistiert (ueberlebt keinen Reboot)
Chunks 172.732 bestätigt
SSH pve-mu-3 Direkt erreichbar (kein Umweg ueber pve-hetzner noetig)
CT 700 Auf pve-mu-3, LAN 192.168.178.154

Architektur Vorher/Nachher

Vorher:

Telegram → Bot (CT 116) → RAGFlow /retrieval API (:8080) → ES intern

Nachher:

Telegram → Bot (CT 116) → ES direkt (:1200 via Tailscale)
                        → Ollama (nomic-embed-text fuer Query-Embedding)

RAGFlow bleibt als Ingestion-Pipeline (Upload, OCR, Chunking, Embedding).


Schritt 1: iptables Port-Forward (pve-mu-3)

Auf pve-mu-3 (100.109.101.12):

# NAT (analog zu bestehender 8080-Regel)
iptables -t nat -A PREROUTING -i tailscale0 -p tcp --dport 1200 \
  -j DNAT --to-destination 192.168.178.154:1200

# Forward
iptables -A FORWARD -d 192.168.178.154 -p tcp --dport 1200 -j ACCEPT

Persistierung: Beide + bestehende 8080-Regeln als post-up in /etc/network/interfaces unter vmbr0.

Test: curl -u elastic:infini_rag_flow http://100.109.101.12:1200/ von pve-hetzner.


Schritt 2: Deutsches Textfeld in ES

Auf CT 700 (pve-mu-3):

# 1. Neues Mapping
curl -X PUT -u elastic:infini_rag_flow \
  "http://localhost:1200/ragflow_61f51c8c279011f1a174bd19863ba33e/_mapping" \
  -H "Content-Type: application/json" \
  -d '{"properties": {"content_de": {"type": "text", "analyzer": "german"}}}'

# 2. Alle 172k Docs befuellen (async, ca. 10-30 Min)
curl -X POST -u elastic:infini_rag_flow \
  "http://localhost:1200/ragflow_61f51c8c279011f1a174bd19863ba33e/_update_by_query?wait_for_completion=false" \
  -H "Content-Type: application/json" \
  -d '{"script": {"source": "ctx._source.content_de = ctx._source.content_with_weight"}}'

Kein Index-Neustart noetig. German Analyzer = lowercase + Stopwords + Stemming (built-in).


Schritt 3: tools/rag.py umschreiben

Datei: /root/homelab-brain/homelab-ai-bot/tools/rag.py

  • Embedding: Query-Vektor via Ollama (http://100.84.255.83:11434/api/embeddings, nomic-embed-text)
  • Hybrid ES-Query: kNN auf q_768_vec (k=20, num_candidates=200) + match auf content_de (Fallback: content_ltks)
  • Deduplizierung nach docnm_kwd
  • OCR-Filter: Chunks mit >15% Sonderzeichen markieren
  • Plugin-Interface bleibt kompatibel: TOOLS, HANDLERS, SYSTEM_PROMPT_EXTRA
  • Nur stdlib (urllib.request, json)

Schritt 4: Deploy

Standard Git-Workflow: Sync aus → commit → push → restart → logs → Sync an.


Reihenfolge

  1. iptables (5 Min)
  2. ES content_de Feld anlegen + _update_by_query starten (5 Min + 10-30 Min Hintergrund)
  3. Waehrend _update_by_query laeuft: rag.py schreiben
  4. Deploy + Test
## Umsetzungsplan (Analyse abgeschlossen) ### Erkenntnisse aus der Analyse | Punkt | Ergebnis | |-------|----------| | ES Docker Port | **1200** (Docker-Mapping 1200→9200 intern), NICHT 9200 | | `content_with_weight` | **Nicht indexiert** (`"index": false`) — nur gespeichert, keine Textsuche moeglich | | Suchbares Textfeld | `content_ltks` (whitespace-Analyzer, kein Deutsch) | | Vektor-Feld | `q_768_vec` (768d, cosine, nomic-embed-text) | | German Analyzer | ES built-in `german` verfuegbar und getestet | | iptables 8080 | Existiert, aber **nicht persistiert** (ueberlebt keinen Reboot) | | Chunks | 172.732 bestätigt | | SSH pve-mu-3 | Direkt erreichbar (kein Umweg ueber pve-hetzner noetig) | | CT 700 | Auf pve-mu-3, LAN 192.168.178.154 | ### Architektur Vorher/Nachher **Vorher:** ``` Telegram → Bot (CT 116) → RAGFlow /retrieval API (:8080) → ES intern ``` **Nachher:** ``` Telegram → Bot (CT 116) → ES direkt (:1200 via Tailscale) → Ollama (nomic-embed-text fuer Query-Embedding) ``` RAGFlow bleibt als Ingestion-Pipeline (Upload, OCR, Chunking, Embedding). --- ### Schritt 1: iptables Port-Forward (pve-mu-3) Auf pve-mu-3 (100.109.101.12): ```bash # NAT (analog zu bestehender 8080-Regel) iptables -t nat -A PREROUTING -i tailscale0 -p tcp --dport 1200 \ -j DNAT --to-destination 192.168.178.154:1200 # Forward iptables -A FORWARD -d 192.168.178.154 -p tcp --dport 1200 -j ACCEPT ``` **Persistierung:** Beide + bestehende 8080-Regeln als `post-up` in `/etc/network/interfaces` unter `vmbr0`. **Test:** `curl -u elastic:infini_rag_flow http://100.109.101.12:1200/` von pve-hetzner. --- ### Schritt 2: Deutsches Textfeld in ES Auf CT 700 (pve-mu-3): ```bash # 1. Neues Mapping curl -X PUT -u elastic:infini_rag_flow \ "http://localhost:1200/ragflow_61f51c8c279011f1a174bd19863ba33e/_mapping" \ -H "Content-Type: application/json" \ -d '{"properties": {"content_de": {"type": "text", "analyzer": "german"}}}' # 2. Alle 172k Docs befuellen (async, ca. 10-30 Min) curl -X POST -u elastic:infini_rag_flow \ "http://localhost:1200/ragflow_61f51c8c279011f1a174bd19863ba33e/_update_by_query?wait_for_completion=false" \ -H "Content-Type: application/json" \ -d '{"script": {"source": "ctx._source.content_de = ctx._source.content_with_weight"}}' ``` Kein Index-Neustart noetig. German Analyzer = lowercase + Stopwords + Stemming (built-in). --- ### Schritt 3: tools/rag.py umschreiben Datei: `/root/homelab-brain/homelab-ai-bot/tools/rag.py` - **Embedding:** Query-Vektor via Ollama (`http://100.84.255.83:11434/api/embeddings`, `nomic-embed-text`) - **Hybrid ES-Query:** kNN auf `q_768_vec` (k=20, num_candidates=200) + match auf `content_de` (Fallback: `content_ltks`) - **Deduplizierung** nach `docnm_kwd` - **OCR-Filter:** Chunks mit >15% Sonderzeichen markieren - **Plugin-Interface** bleibt kompatibel: `TOOLS`, `HANDLERS`, `SYSTEM_PROMPT_EXTRA` - Nur stdlib (`urllib.request`, `json`) --- ### Schritt 4: Deploy Standard Git-Workflow: Sync aus → commit → push → restart → logs → Sync an. --- ### Reihenfolge 1. iptables (5 Min) 2. ES content_de Feld anlegen + _update_by_query starten (5 Min + 10-30 Min Hintergrund) 3. Waehrend _update_by_query laeuft: rag.py schreiben 4. Deploy + Test
Author
Owner

Umsetzung abgeschlossen (26.03.2026)

Erledigt

  • iptables pve-mu-3: DNAT Tailscale → 192.168.178.154:1200 (ES) + FORWARD-Regel; Persistenz: idempotente post-up-Zeilen in /etc/network/interfaces unter vmbr0 (8080 + 1200).
  • ES: Feld content_de (Analyzer german) angelegt; _update_by_query laeuft/lief (172k Chunks) — bei Start der Umsetzung ~35k updated, Task laeuft weiter bis complete.
  • Code: homelab-ai-bot/tools/rag.py — Hybrid kNN (q_768_vec) + match auf content_de, content_ltks, docnm_kwd; Filter kb_id; Embedding Ollama nomic-embed-text; OCR-Heuristik; Dedupe nach docnm_kwd.
  • Git: Commit f9b69ad2 auf main, gepusht.
  • Deploy: sync-state.sh, systemctl restart hausmeister-bot CT 116 — Bot aktiv, Ollama-Warmup inkl. nomic-embed-text.

Tests (CT 116 / Host)

  • handle_rag_search("Grundsteuer", top_k=2) → sinnvolle Treffer (Grundsteuerbescheid).
  • handle_rag_search("Allianz Versicherung", top_k=2) → Allianz-PDFs.

Hinweis Auto-Sync

/etc/crontab sync-state war kurz auskommentiert, nach Deploy wieder aktiv.

## Umsetzung abgeschlossen (26.03.2026) ### Erledigt - **iptables pve-mu-3:** DNAT Tailscale → `192.168.178.154:1200` (ES) + FORWARD-Regel; **Persistenz:** idempotente `post-up`-Zeilen in `/etc/network/interfaces` unter `vmbr0` (8080 + 1200). - **ES:** Feld `content_de` (Analyzer `german`) angelegt; `_update_by_query` laeuft/lief (172k Chunks) — bei Start der Umsetzung ~35k updated, Task laeuft weiter bis complete. - **Code:** `homelab-ai-bot/tools/rag.py` — Hybrid kNN (`q_768_vec`) + `match` auf `content_de`, `content_ltks`, `docnm_kwd`; Filter `kb_id`; Embedding Ollama `nomic-embed-text`; OCR-Heuristik; Dedupe nach `docnm_kwd`. - **Git:** Commit `f9b69ad2` auf `main`, gepusht. - **Deploy:** `sync-state.sh`, `systemctl restart hausmeister-bot` CT 116 — Bot aktiv, Ollama-Warmup inkl. `nomic-embed-text`. ### Tests (CT 116 / Host) - `handle_rag_search("Grundsteuer", top_k=2)` → sinnvolle Treffer (Grundsteuerbescheid). - `handle_rag_search("Allianz Versicherung", top_k=2)` → Allianz-PDFs. ### Hinweis Auto-Sync `/etc/crontab` sync-state war kurz auskommentiert, nach Deploy wieder aktiv.
Author
Owner

E2E-Tests bestanden: 19/20 (26.03.2026)

Commits

  • f9b69ad2 — rag.py: ES direkt statt RAGFlow
  • 0e1c7a6e — llm.py: Routing-Fix (Dokument-Keywords in LOCAL_OVERRIDES)
  • a43c0b91 — rag.py: bessere Treffer + Anti-Halluzination
  • 59e53a27 — rag.py: 19/20 E2E bestanden, Dedup + Pflicht-Prompt

Was wurde gefixt

Routing (llm.py): "aktuell" in Fragen wie "welche versicherungen habe ich aktuell?" routete zu Perplexity Sonar (kein Tool-Calling). Fix: Dokument-Keywords (versicherung, vertrag, steuer, rente, dokument...) in _LOCAL_OVERRIDES.

Dedup (rag.py): Dateinamen-basiert (Extension + Kopie-Marker ignorieren). Vorher: "Kuendigung.txt", "Kuendigung.pdf", "Kuendigung(1).pdf" als 3 verschiedene Treffer.

Anti-Halluzination: Expliziter Ende-Marker "(Ende der Ergebnisse)". LLM-Prompt: "ERFINDE KEINE Details die nicht im Ergebnis stehen".

Pflicht-Aufruf: SYSTEM_PROMPT_EXTRA verstaerkt: rag_search IMMER aufrufen bei Dokument-Fragen, auch wenn Gedaechtnis was zu wissen glaubt.

Testergebnisse (20 Fragen, vollstaendiger LLM+Tool-Flow)

# Frage v1 v2
1 welche versicherungen habe ich aktuell? Halluziniert 5 Dokumente gelistet
2 habe ich eine lebensversicherung? "Nein" Aspecta, HDI, Victoria
4 was steht in meinem mietvertrag? "Frag Ali" 3 Mietvertrag-PDFs
6 habe ich eine haftpflichtversicherung? "Nein" Ja, Scheinnr. 69.037.749.7-AH62
9 habe ich einen arbeitsvertrag? "Nein" Evesen GmbH Arbeitsvertrag
17 gibt es was zum kindergeld? "Keine Infos" 3 Kindergeld-Dokumente
5 finde mein familienbuch OK Abgewimmelt (einziger FAIL)

content_de Status

75k/172k Chunks befuellt, zweiter _update_by_query gestartet (Task 738488).

Offene Aufgabe

  • Testen gegen Testkategorien sobald content_de zu 100% befuellt (deutscher Volltext-Boost wird staerker)
## E2E-Tests bestanden: 19/20 (26.03.2026) ### Commits - `f9b69ad2` — rag.py: ES direkt statt RAGFlow - `0e1c7a6e` — llm.py: Routing-Fix (Dokument-Keywords in LOCAL_OVERRIDES) - `a43c0b91` — rag.py: bessere Treffer + Anti-Halluzination - `59e53a27` — rag.py: 19/20 E2E bestanden, Dedup + Pflicht-Prompt ### Was wurde gefixt **Routing (llm.py):** "aktuell" in Fragen wie "welche versicherungen habe ich aktuell?" routete zu Perplexity Sonar (kein Tool-Calling). Fix: Dokument-Keywords (versicherung, vertrag, steuer, rente, dokument...) in `_LOCAL_OVERRIDES`. **Dedup (rag.py):** Dateinamen-basiert (Extension + Kopie-Marker ignorieren). Vorher: "Kuendigung.txt", "Kuendigung.pdf", "Kuendigung(1).pdf" als 3 verschiedene Treffer. **Anti-Halluzination:** Expliziter Ende-Marker "(Ende der Ergebnisse)". LLM-Prompt: "ERFINDE KEINE Details die nicht im Ergebnis stehen". **Pflicht-Aufruf:** SYSTEM_PROMPT_EXTRA verstaerkt: rag_search IMMER aufrufen bei Dokument-Fragen, auch wenn Gedaechtnis was zu wissen glaubt. ### Testergebnisse (20 Fragen, vollstaendiger LLM+Tool-Flow) | # | Frage | v1 | v2 | |---|-------|----|----| | 1 | welche versicherungen habe ich aktuell? | Halluziniert | 5 Dokumente gelistet | | 2 | habe ich eine lebensversicherung? | "Nein" | Aspecta, HDI, Victoria | | 4 | was steht in meinem mietvertrag? | "Frag Ali" | 3 Mietvertrag-PDFs | | 6 | habe ich eine haftpflichtversicherung? | "Nein" | Ja, Scheinnr. 69.037.749.7-AH62 | | 9 | habe ich einen arbeitsvertrag? | "Nein" | Evesen GmbH Arbeitsvertrag | | 17 | gibt es was zum kindergeld? | "Keine Infos" | 3 Kindergeld-Dokumente | | 5 | finde mein familienbuch | OK | Abgewimmelt (einziger FAIL) | ### content_de Status 75k/172k Chunks befuellt, zweiter _update_by_query gestartet (Task 738488). ### Offene Aufgabe - [ ] Testen gegen Testkategorien sobald content_de zu 100% befuellt (deutscher Volltext-Boost wird staerker)
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: orbitalo/homelab-brain#51
No description provided.