homelab-brain/homelab-ai-bot/tools/web_search.py

114 lines
3.6 KiB
Python

"""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-10s). STANDARDTOOL fuer Preise, News, Fakten. WICHTIG: Nur kurze Keywords als query (2-5 Woerter), KEINE ganzen Saetze. Mehrfach mit verschiedenen Keywords aufrufen.",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Kurze Keyword-Suchanfrage (2-5 Woerter, KEINE ganzen Saetze). Beispiel: goldpreis euro unze heute"
},
"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).
SUCH-QUERIES MUESSEN kurze Keywords sein (2-5 Woerter), KEINE natuerlichen Saetze!
Gut: "goldpreis euro unze heute"
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."""
def _search_once(base_url: str, query: str):
r = requests.get(
f"{base_url}/search",
params={"q": query, "format": "json", "language": "de"},
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))
_pq = query.lower()
_is_price = any(w in _pq for w in ["preis", "price", "kurs", "kostet",
"gold", "silber", "unze", "ounce"])
if _is_price:
max_results = max(max_results, 5)
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}",
# SearXNG URL nicht als Quelle zeigen
"",
]
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}")
if _is_price:
lines.append("")
lines.append("ACHTUNG EINHEITEN: Goldpreis-Quellen mischen oft Gramm/Unze/Kilo!")
lines.append("1 troy ounce = 31,103 g. goldpreis.de zeigt oft EUR/Gramm, NICHT EUR/Unze.")
lines.append("Rechne ggf. um: Preis/Gramm x 31,103 = Preis/Unze.")
return "\n".join(lines)
HANDLERS = {
"web_search": handle_web_search,
}