rag: bessere Treffer + Anti-Halluzination

- top_k min=5, default=8 (LLM kann nicht mehr top_k=3 setzen)
- docnm_kwd boost 1.5→3.0 (Ordner/Dateinamen staerker gewichten)
- Ordnerpfad als Kategorie-Info in Ausgabe
- Content-Snippet 400→600 Zeichen
- SYSTEM_PROMPT_EXTRA: LLM darf keine Details erfinden
- es_size erhoehen fuer breitere Suche
Ref: Issue #51
This commit is contained in:
Homelab Cursor 2026-03-26 15:07:31 +01:00
parent 0e1c7a6ebb
commit a43c0b913b
3 changed files with 33 additions and 11 deletions

View file

@ -20,6 +20,8 @@ KB_ID = "dc24edda27a311f19fe7fb811de6f016"
OLLAMA_EMBED_URL = "http://100.84.255.83:11434/api/embeddings"
EMBED_MODEL = "nomic-embed-text"
MIN_TOP_K = 5
TOOLS = [
{
"type": "function",
@ -30,7 +32,9 @@ TOOLS = [
"Vertraege, Versicherungen, Rente, Finanzamt, Familiendokumente, "
"Anleitungen, Buecher, persoenliche Unterlagen). "
"Nutze dieses Tool wenn der User nach einem bestimmten Dokument, "
"Vertrag, Brief oder persoenlicher Information fragt."
"Vertrag, Brief oder persoenlicher Information fragt. "
"Bei breiten Fragen ('welche Versicherungen', 'alle Vertraege') "
"immer top_k=10 verwenden."
),
"parameters": {
"type": "object",
@ -44,8 +48,8 @@ TOOLS = [
},
"top_k": {
"type": "integer",
"description": "Anzahl Ergebnisse (1-10)",
"default": 5,
"description": "Anzahl Ergebnisse (5-10, Standard 8)",
"default": 8,
},
},
"required": ["query"],
@ -62,7 +66,12 @@ Die Suchanfrage sollte kurze Keywords sein, KEINE ganzen Saetze. Beispiele:
- "Grundsteuer Erklaerung"
- "Nuernberger Versicherung"
- "Allianz Beitraege"
Bei schlechten Ergebnissen: andere Keywords versuchen oder Dokumentnamen direkt suchen."""
Bei schlechten Ergebnissen: andere Keywords versuchen oder Dokumentnamen direkt suchen.
WICHTIG:
- Bei breiten Kategorie-Fragen ("welche Versicherungen", "alle Vertraege"): top_k=10
- Liste NUR die Dokumente auf die rag_search zurueckliefert. ERFINDE KEINE Details die nicht im Ergebnis stehen.
- Der Ordnerpfad im Dokumentnamen (vor dem Dateinamen, getrennt durch __) zeigt die Kategorie.
- Wenn das Ergebnis Dokumente zeigt, liste sie auf auch wenn du den Inhalt nicht vollstaendig kennst."""
def _basic_auth_header() -> str:
@ -104,6 +113,14 @@ def _ocr_note(text: str) -> str:
return ""
def _folder_from_docname(name: str) -> str:
"""Extrahiert den Ordnerpfad aus docnm_kwd (__ = Trenner)."""
parts = name.rsplit("__", 1)
if len(parts) == 2:
return parts[0].replace("__", " > ").replace("_", " ")
return ""
def _es_hybrid_search(query: str, es_size: int) -> dict:
qvec = _ollama_embed(query)
if not qvec:
@ -125,7 +142,7 @@ def _es_hybrid_search(query: str, es_size: int) -> dict:
"should": [
{"match": {"content_de": {"query": query, "boost": 2.0}}},
{"match": {"content_ltks": {"query": query.lower(), "boost": 0.4}}},
{"match": {"docnm_kwd": {"query": query, "boost": 1.5}}},
{"match": {"docnm_kwd": {"query": query, "boost": 3.0}}},
],
"minimum_should_match": 0,
}
@ -153,12 +170,12 @@ def _es_hybrid_search(query: str, es_size: int) -> dict:
return {"_error": str(e)}
def handle_rag_search(query: str, top_k: int = 5, **kw):
def handle_rag_search(query: str, top_k: int = 8, **kw):
if not query or not query.strip():
return "rag_search: query fehlt."
top_k = max(1, min(int(top_k or 5), 10))
es_size = min(100, max(top_k * 12, 35))
top_k = max(MIN_TOP_K, min(int(top_k or 8), 10))
es_size = min(120, max(top_k * 12, 50))
data = _es_hybrid_search(query.strip(), es_size)
if "_error" in data:
@ -170,7 +187,7 @@ def handle_rag_search(query: str, top_k: int = 5, **kw):
seen_docs: set[str] = set()
lines: list[str] = []
lines.append(f"**{len(hits)} Roh-Treffer fuer '{query}'** (Top {top_k} Dokumente):\n")
lines.append(f"**{len(hits)} Treffer fuer '{query}'** (Top {top_k} Dokumente):\n")
count = 0
for h in hits:
@ -185,10 +202,15 @@ def handle_rag_search(query: str, top_k: int = 5, **kw):
score = h.get("_score") or 0.0
raw = src.get("content_with_weight") or src.get("content_de") or ""
content = raw[:400].strip()
content = raw[:600].strip()
ocr = _ocr_note(raw)
folder = _folder_from_docname(doc_name)
filename = doc_name.rsplit("__", 1)[-1] if "__" in doc_name else doc_name
folder_line = f" Ordner: {folder}" if folder else ""
lines.append(f"---\n**{count + 1}. {doc_name}** (Score: {score:.3f}){ocr}")
lines.append(f"---\n**{count + 1}. {filename}** (Score: {score:.1f}){ocr}")
if folder_line:
lines.append(folder_line)
if content:
lines.append(f"```\n{content}\n```")
count += 1