From 6ef208c289866c71a7d11a94352fb3840001665c Mon Sep 17 00:00:00 2001 From: Homelab Cursor Date: Fri, 20 Mar 2026 21:59:33 +0100 Subject: [PATCH] feat: web_search Tool via SearXNG ergaenzen Issue #35 vervollstaendigt mit schneller Web-Suche (3-10s): - neues Tool web_search in tools/web_search.py - SearXNG Endpoint CT121 (intern + Tailscale Fallback) - strukturierte Treffer mit Snippets + Quellenlinks - Prompt-Hinweis fuer Routing: web_search vs deep_research --- homelab-ai-bot/tools/web_search.py | 99 ++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 homelab-ai-bot/tools/web_search.py diff --git a/homelab-ai-bot/tools/web_search.py b/homelab-ai-bot/tools/web_search.py new file mode 100644 index 00000000..98eadbd1 --- /dev/null +++ b/homelab-ai-bot/tools/web_search.py @@ -0,0 +1,99 @@ +"""Web-Suche Tool via SearXNG (CT 121).""" + +import requests + +SEARXNG_URLS = [ + "http://10.10.10.121:8080", + "http://100.74.196.29:8080", +] + +TOOLS = [ + { + "type": "function", + "function": { + "name": "web_search", + "description": "Schnelle Web-Suche (3-10 Sekunden) ueber SearXNG. Nutze fuer aktuelle Fakten, Preise, News und einfache Web-Fragen.", + "parameters": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "Suchanfrage, moeglichst konkret." + }, + "max_results": { + "type": "integer", + "description": "Anzahl Ergebnisse (1-8)", + "default": 5 + } + }, + "required": ["query"] + }, + }, + }, +] + +SYSTEM_PROMPT_EXTRA = """WEB-SUCHE: +Du hast das Tool web_search fuer schnelle Web-Recherche (3-10s). +Nutze web_search bei aktuellen Fakten, Preisen, News, Vergleichen mit wenigen Quellen. +Nutze deep_research nur bei komplexen Themen, die einen langen Report brauchen (2-5 Min). +Wenn web_search genug ist: antworte mit kurzer Zusammenfassung + 2-5 Quellen.""" + + +def _search_once(base_url: str, query: str): + r = requests.get( + f"{base_url}/search", + params={"q": query, "format": "json"}, + timeout=10, + ) + r.raise_for_status() + return r.json() + + +def handle_web_search(query: str, max_results: int = 5, **kw): + if not query or not query.strip(): + return "web_search: query fehlt." + + max_results = max(1, min(int(max_results or 5), 8)) + + last_err = None + data = None + used_url = None + for base in SEARXNG_URLS: + try: + data = _search_once(base, query.strip()) + used_url = base + break + except Exception as e: + last_err = e + + if data is None: + return f"web_search nicht erreichbar: {last_err}" + + results = data.get("results", [])[:max_results] + if not results: + return f"Keine Treffer fuer: {query}" + + lines = [ + f"Web-Suche: {query}", + f"Quelle: {used_url}", + "", + ] + + for idx, item in enumerate(results, 1): + title = (item.get("title") or "(ohne Titel)").strip() + url = (item.get("url") or "").strip() + snippet = (item.get("content") or "").strip().replace("\n", " ") + if len(snippet) > 220: + snippet = snippet[:220] + "..." + + lines.append(f"{idx}. {title}") + if snippet: + lines.append(f" {snippet}") + lines.append(f" {url}") + + return "\n".join(lines) + + +HANDLERS = { + "web_search": handle_web_search, +}