From 04f69dfd816f5cf92efee3ed5618cc666a257718 Mon Sep 17 00:00:00 2001 From: Homelab Cursor Date: Fri, 31 Jul 2026 21:55:04 +0200 Subject: [PATCH] =?UTF-8?q?fix(hausmeister-bot):=20Booking-Werbefilter=20u?= =?UTF-8?q?nd=20LangGraph-Aufr=C3=A4umen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Filtert campaign@/marketing@-Absender vor IMPORTANT_SENDERS (taegliche Booking-Alerts). Entfernt obsoletes deep_research.py (CT121 LangGraph) — Tiefenrecherche laeuft ueber Perplexity. --- homelab-ai-bot/STATE.md | 2 +- homelab-ai-bot/core/mail_client.py | 13 ++ homelab-ai-bot/patch_dr.py | 45 ------ homelab-ai-bot/patch_dr2.py | 27 ---- homelab-ai-bot/tools/deep_research.py | 222 -------------------------- homelab-ai-bot/tools/web_search.py | 2 +- 6 files changed, 15 insertions(+), 296 deletions(-) delete mode 100644 homelab-ai-bot/patch_dr.py delete mode 100644 homelab-ai-bot/patch_dr2.py delete mode 100644 homelab-ai-bot/tools/deep_research.py diff --git a/homelab-ai-bot/STATE.md b/homelab-ai-bot/STATE.md index ebdf6f500..b7fa3b530 100644 --- a/homelab-ai-bot/STATE.md +++ b/homelab-ai-bot/STATE.md @@ -37,7 +37,7 @@ session_search, session_summary, get_backup_status, get_server_metrics, get_server_warnings, get_savetv_status, get_savetv_tipps, get_savetv_archive_filme, savetv_record, savetv_download, get_seafile_status, get_service_directory, get_tailscale_status, -web_search, get_wordpress_stats, deep_research +web_search, get_wordpress_stats --- diff --git a/homelab-ai-bot/core/mail_client.py b/homelab-ai-bot/core/mail_client.py index 0005a10ab..3c6010fd5 100644 --- a/homelab-ai-bot/core/mail_client.py +++ b/homelab-ai-bot/core/mail_client.py @@ -194,6 +194,17 @@ def search_mail(query: str, days: int = 30, limit: int = 15) -> list[dict]: m.logout() +MARKETING_FROM_PATTERNS = [ + "email.campaign@", "campaign@", "marketing@", "newsletter@", + "promotion@", "promo@", "no-reply@mail.", +] + + +def _is_marketing_mail(frm_lower: str) -> bool: + """Werbemails von sonst wichtigen Absendern (z.B. booking.com campaign@).""" + return any(p in frm_lower for p in MARKETING_FROM_PATTERNS) + + def _is_spam_sender(frm_lower: str) -> bool: """Prueft ob Absender in der Spam-Liste steht.""" return any(s in frm_lower for s in SPAM_SENDERS) @@ -217,6 +228,8 @@ def get_important_mails(hours: int = 24) -> list[dict]: frm_lower = parsed["from"].lower() if _is_spam_sender(frm_lower): continue + if _is_marketing_mail(frm_lower): + continue if any(s in frm_lower for s in IMPORTANT_SENDERS): results.append(parsed) results.reverse() diff --git a/homelab-ai-bot/patch_dr.py b/homelab-ai-bot/patch_dr.py deleted file mode 100644 index 517384c77..000000000 --- a/homelab-ai-bot/patch_dr.py +++ /dev/null @@ -1,45 +0,0 @@ -from pathlib import Path -p = Path('/root/homelab-brain/homelab-ai-bot/tools/deep_research.py') -text = p.read_text() - -# 1. Add datetime import -text = text.replace( - 'import logging\nimport re\nimport time', - 'import logging\nimport re\nimport time\nfrom datetime import date' -) - -# 2. Add _inject_date_context function before _price_report_quality -text = text.replace( - 'def _price_report_quality(report: str):', - '''def _inject_date_context(query): - today = date.today() - return ( - f"Heutiges Datum: {today.strftime('%d. %B %Y')}. " - f"'Letzte 3 Monate' = {(today.replace(day=1) - __import__('datetime').timedelta(days=90)).strftime('%B %Y')} bis {today.strftime('%B %Y')}.\n\n" - + query - ) - - -def _price_report_quality(report: str):''' -) - -# 3. Add year check in _price_report_quality before return -text = text.replace( - ' return len(missing) == 0, missing', - ''' today = date.today() - ok_years = {str(today.year), str(today.year - 1)} - found = set(re.findall(r"20\d{2}", text)) - if found and not (found & ok_years): - missing.append(f"Jahre falsch: {found} statt {ok_years}") - - return len(missing) == 0, missing''' -) - -# 4. Inject date context in handle_deep_research -text = text.replace( - ' log.info("deep_research gestartet: %s", query[:120])', - ' query = _inject_date_context(query)\n log.info("deep_research gestartet: %s", query[:200])' -) - -p.write_text(text) -print('OK') diff --git a/homelab-ai-bot/patch_dr2.py b/homelab-ai-bot/patch_dr2.py deleted file mode 100644 index 86211cfa7..000000000 --- a/homelab-ai-bot/patch_dr2.py +++ /dev/null @@ -1,27 +0,0 @@ -from pathlib import Path -p = Path('/root/homelab-brain/homelab-ai-bot/tools/deep_research.py') -text = p.read_text() - -# Fix the broken _inject_date_context -old_fn = '''def _inject_date_context(query): - today = date.today() - return ( - f"Heutiges Datum: {today.strftime('%d. %B %Y')}. " - f"'Letzte 3 Monate' = {(today.replace(day=1) - __import__('datetime').timedelta(days=90)).strftime('%B %Y')} bis {today.strftime('%B %Y')}.\n\n" - + query - )''' - -new_fn = '''def _inject_date_context(query): - from datetime import timedelta - today = date.today() - three_months_ago = today - timedelta(days=90) - prefix = ( - "Heutiges Datum: " + today.strftime("%d. %B %Y") + ". " - "'Letzte 3 Monate' = " + three_months_ago.strftime("%B %Y") - + " bis " + today.strftime("%B %Y") + ".\n\n" - ) - return prefix + query''' - -text = text.replace(old_fn, new_fn) -p.write_text(text) -print('OK') diff --git a/homelab-ai-bot/tools/deep_research.py b/homelab-ai-bot/tools/deep_research.py deleted file mode 100644 index 8b3edbe8f..000000000 --- a/homelab-ai-bot/tools/deep_research.py +++ /dev/null @@ -1,222 +0,0 @@ -"""Deep Research Tool — Open Deep Research (CT 121) via LangGraph API.""" - -import logging -import re -import time - -import requests - -log = logging.getLogger("deep_research") - -DEEP_RESEARCH_URL = "http://10.10.10.121:2024" -ASSISTANT_ID = "e9a5370f-7a53-55a8-ada8-6ab9ef15bb5b" -RESEARCH_MODEL = "openai/gpt-4o-mini" -POLL_INTERVAL = 10 -MAX_WAIT = 600 - -SYSTEM_PROMPT_EXTRA = """DEEP RESEARCH: -Du hast Zugriff auf deep_research — eine KI-gestuetzte Tiefenrecherche die 20-30 Quellen durchsucht. -Nutze es NUR wenn der User explizit "deep research" oder "tiefenrecherche" sagt. Fuer alles andere: web_search. -NICHT fuer einfache Fakten oder Homelab-Fragen. -WICHTIG: deep_research dauert 2-5 Minuten. Das ist normal. Warte auf das Ergebnis. -Das Ergebnis ist ein ausfuehrlicher Report. Fasse ihn fuer Telegram zusammen (max ~3000 Zeichen). - -QUALITAET BEI PREISFRAGEN: -- Liefere konkrete Zahlen statt allgemeiner Markttexte. -- Zeige Zeitraum, Preis damals/heute, Delta in % und Quellen. -- Wenn keine belastbaren Daten vorhanden sind, sage es explizit.""" - -TOOLS = [ - { - "type": "function", - "function": { - "name": "deep_research", - "description": "KI-gestuetzte Tiefenrecherche (20-30 Quellen, 2-5 Min). NUR wenn User explizit deep research oder tiefenrecherche sagt.", - "parameters": { - "type": "object", - "properties": { - "query": { - "type": "string", - "description": "Die Recherche-Frage" - } - }, - "required": ["query"] - }, - }, - }, -] - - -def _create_thread(): - r = requests.post(f"{DEEP_RESEARCH_URL}/threads", json={}, timeout=10) - r.raise_for_status() - return r.json()["thread_id"] - - -def _start_run(thread_id, query): - payload = { - "assistant_id": ASSISTANT_ID, - "input": {"messages": [{"role": "user", "content": query}]}, - "config": { - "configurable": { - "summarization_model": f"openai:{RESEARCH_MODEL}", - "research_model": f"openai:{RESEARCH_MODEL}", - "compression_model": f"openai:{RESEARCH_MODEL}", - "final_report_model": f"openai:{RESEARCH_MODEL}", - "allow_clarification": False, - } - }, - } - r = requests.post( - f"{DEEP_RESEARCH_URL}/threads/{thread_id}/runs", json=payload, timeout=30 - ) - r.raise_for_status() - return r.json()["run_id"] - - -def _poll_run(thread_id, run_id): - elapsed = 0 - while elapsed < MAX_WAIT: - time.sleep(POLL_INTERVAL) - elapsed += POLL_INTERVAL - try: - r = requests.get( - f"{DEEP_RESEARCH_URL}/threads/{thread_id}/runs/{run_id}", timeout=10 - ) - r.raise_for_status() - data = r.json() - status = data.get("status", "unknown") - log.info("Poll %ds: status=%s", elapsed, status) - if status == "success": - return True, None - if status in ("error", "failed"): - err = data.get("error", "Unbekannter Fehler") - log.error("Run failed: %s", err) - return False, err - if status == "interrupted": - return False, "Research wurde unterbrochen" - except Exception as e: - log.warning("Poll error at %ds: %s", elapsed, e) - return False, f"Timeout nach {MAX_WAIT}s" - - -def _get_result(thread_id): - r = requests.get(f"{DEEP_RESEARCH_URL}/threads/{thread_id}/state", timeout=30) - r.raise_for_status() - state = r.json() - - messages = state.get("values", {}).get("messages", []) - log.info("Messages in result: %d", len(messages)) - for i, msg in enumerate(messages): - content = msg.get("content", "") - clen = len(content) if isinstance(content, str) else 0 - log.info(" msg[%d] type=%s len=%d", i, msg.get("type", "?"), clen) - - for msg in reversed(messages): - content = msg.get("content", "") - if isinstance(content, str) and len(content) > 100: - return content - - return "Kein Report generiert." - - -def _is_price_query(query: str) -> bool: - q = (query or "").lower() - needles = [ - "preis", - "preise", - "kosten", - "teuer", - "guenstig", - "ram", - "ddr4", - "ddr5", - "entwicklung", - ] - return any(n in q for n in needles) - - -def _price_report_quality(report: str): - text = report or "" - links = re.findall(r"https?://\S+", text) - has_percent = bool(re.search(r"[-+]?\d+[\.,]?\d*\s*%", text)) - has_currency = bool(re.search(r"(?:\d+[\.,]?\d*\s?(?:€|eur|\$))|(?:€\s?\d+)", text, re.I)) - has_comparison = bool( - re.search(r"(damals|heute|vor\s+\d+\s+(?:monaten|wochen)|aktuell|delta)", text, re.I) - ) - - missing = [] - if len(links) < 3: - missing.append("mindestens 3 konkrete Quellen-Links") - if not has_percent: - missing.append("Delta in %") - if not has_currency: - missing.append("konkrete Preise mit Waehrung") - if not has_comparison: - missing.append("Preisvergleich damals/heute") - - return len(missing) == 0, missing - - -def _run_research(query: str): - thread_id = _create_thread() - log.info("Thread erstellt: %s", thread_id) - run_id = _start_run(thread_id, query) - log.info("Run gestartet: %s", run_id) - ok, error = _poll_run(thread_id, run_id) - if not ok: - return False, f"Deep Research fehlgeschlagen: {error}" - report = _get_result(thread_id) - log.info("Report erhalten: %d Zeichen", len(report)) - return True, report - - -def handle_deep_research(query: str, **kw): - log.info("deep_research gestartet: %s", query[:120]) - try: - ok, report = _run_research(query) - if not ok: - return report - - # Harte Qualitaetspruefung fuer Preisfragen - if _is_price_query(query): - good, missing = _price_report_quality(report) - if not good: - log.warning("Preisreport zu schwach, starte Retry. Missing: %s", ", ".join(missing)) - stricter_query = ( - query - + "\n\nLIEFERE NUR belastbare Preisdaten im Format:\n" - + "1) Zeitraum (exakt)\n" - + "2) Preis damals -> Preis heute (EUR)\n" - + "3) Delta in %\n" - + "4) 3-5 konkrete Quellen-Links (keine Startseiten).\n" - + "Wenn unklar: explizit keine belastbaren Preisdaten gefunden." - ) - ok2, report2 = _run_research(stricter_query) - if ok2: - good2, missing2 = _price_report_quality(report2) - if good2: - report = report2 - else: - return ( - "keine belastbaren Preisdaten gefunden. " - "Es fehlen: " - + ", ".join(missing2) - + ". Bitte Anfrage enger formulieren (Produktklasse + Region + Zeitraum)." - ) - else: - return report2 - - if len(report) > 6000: - report = report[:6000] + "\n\n[... Report gekuerzt]" - return report - - except requests.ConnectionError: - log.error("CT 121 nicht erreichbar") - return "Deep Research (CT 121) nicht erreichbar. Service laeuft moeglicherweise nicht." - except Exception as e: - log.exception("Deep Research Fehler") - return f"Deep Research Fehler: {e}" - - -HANDLERS = {"deep_research": handle_deep_research} diff --git a/homelab-ai-bot/tools/web_search.py b/homelab-ai-bot/tools/web_search.py index 4cb66d996..6f6235497 100644 --- a/homelab-ai-bot/tools/web_search.py +++ b/homelab-ai-bot/tools/web_search.py @@ -40,7 +40,7 @@ Gut: "DDR5 RAM 32GB preis 2026" Schlecht: "Was kostet aktuell eine Feinunze Gold in Euro?" Nutze web_search bei aktuellen Fakten, Preisen, News, Vergleichen. Mache 2-3 Suchen mit verschiedenen Keywords fuer bessere Ergebnisse. -Nutze deep_research NUR wenn User explizit danach fragt.""" +Bei expliziter Tiefenrecherche (deep research / tiefenrecherche) antwortet das System automatisch via Perplexity Sonar — kein separates Tool.""" def _search_once(base_url: str, query: str):