diff --git a/homelab-ai-bot/__pycache__/llm.cpython-311.pyc b/homelab-ai-bot/__pycache__/llm.cpython-311.pyc index f005230d..2c551411 100644 Binary files a/homelab-ai-bot/__pycache__/llm.cpython-311.pyc and b/homelab-ai-bot/__pycache__/llm.cpython-311.pyc differ diff --git a/homelab-ai-bot/__pycache__/monitor.cpython-311.pyc b/homelab-ai-bot/__pycache__/monitor.cpython-311.pyc index aa7192d3..9ee00642 100644 Binary files a/homelab-ai-bot/__pycache__/monitor.cpython-311.pyc and b/homelab-ai-bot/__pycache__/monitor.cpython-311.pyc differ diff --git a/homelab-ai-bot/tools/rag.py b/homelab-ai-bot/tools/rag.py index 5a3d2d67..b80126eb 100644 --- a/homelab-ai-bot/tools/rag.py +++ b/homelab-ai-bot/tools/rag.py @@ -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