diff --git a/homelab-ai-bot/llm.py b/homelab-ai-bot/llm.py index 5f9d6fb4..75c38b1a 100644 --- a/homelab-ai-bot/llm.py +++ b/homelab-ai-bot/llm.py @@ -4,6 +4,7 @@ Neue Datenquelle = eine Datei in tools/ anlegen. Fertig. """ import json +import logging import requests import os import sys @@ -12,11 +13,22 @@ sys.path.insert(0, os.path.dirname(__file__)) from core import config import tool_loader -MODEL = "openai/gpt-4o-mini" +log = logging.getLogger('llm') + +OLLAMA_BASE = "http://100.84.255.83:11434" +OPENROUTER_BASE = "https://openrouter.ai/api/v1" + +MODEL = "qwen3:30b-a3b" VISION_MODEL = "openai/gpt-4o" MAX_TOOL_ROUNDS = 3 -SYSTEM_PROMPT = """Du bist der Hausmeister-Bot fuer ein Homelab. Deutsch, kurz, direkt, operativ. +import datetime as _dt +_TODAY = _dt.date.today() +_3M_AGO = (_TODAY - _dt.timedelta(days=90)) +_DATE_LINE = f'Heutiges Datum: {_TODAY.strftime("%d. %B %Y")}. Wir sind im Jahr {_TODAY.year}. Letzte 3 Monate = {_3M_AGO.strftime("%B %Y")} bis {_TODAY.strftime("%B %Y")}.' + +SYSTEM_PROMPT = _DATE_LINE + """ +Du bist der Hausmeister-Bot fuer ein Homelab. Deutsch, kurz, direkt, operativ. STIL: - So wenig Worte wie moeglich, solange nichts Wichtiges fehlt. @@ -150,7 +162,8 @@ PREISRECHERCHE (PFLICHT): Wenn der User nach Preisen, Kosten oder Preisentwicklung fragt: - Nutze IMMER Tools statt Allgemeinwissen. - Fuer schnelle Preisabfrage: web_search. -- Fuer Preisentwicklung ueber Wochen/Monate: deep_research. +- Mache 2-3 gezielte web_search Aufrufe mit verschiedenen Suchbegriffen. +- deep_research NUR wenn User explizit 'deep research' oder 'tiefenrecherche' sagt. - Gib konkrete Zahlen aus (EUR), nicht nur Tendenzen. - Nenne 3-5 Quellen-Links. - Wenn keine belastbaren Zahlen gefunden werden: klar sagen "keine belastbaren Preisdaten gefunden". @@ -173,9 +186,13 @@ def _get_api_key() -> str: def _call_openrouter(messages: list, api_key: str, use_tools: bool = True, - model: str = None, max_tokens: int = 600) -> dict: + model: str = None, max_tokens: int = 4000) -> dict: + chosen = model or MODEL + use_ollama = (chosen == MODEL) + log.info("LLM-Call: model=%s ollama=%s max_tokens=%d", chosen, use_ollama, max_tokens) + payload = { - "model": model or MODEL, + "model": chosen, "messages": messages, "max_tokens": max_tokens, } @@ -183,12 +200,16 @@ def _call_openrouter(messages: list, api_key: str, use_tools: bool = True, payload["tools"] = TOOLS payload["tool_choice"] = "auto" - r = requests.post( - "https://openrouter.ai/api/v1/chat/completions", - headers={"Authorization": f"Bearer {api_key}"}, - json=payload, - timeout=90, - ) + if use_ollama: + url = f"{OLLAMA_BASE}/v1/chat/completions" + headers = {"Content-Type": "application/json"} + timeout = 180 + else: + url = f"{OPENROUTER_BASE}/chat/completions" + headers = {"Authorization": f"Bearer {api_key}"} + timeout = 90 + + r = requests.post(url, headers=headers, json=payload, timeout=timeout) r.raise_for_status() return r.json() @@ -275,7 +296,10 @@ def ask_with_tools(question: str, tool_handlers: dict, session_id: str = None) - tool_calls = msg.get("tool_calls") if not tool_calls: - return msg.get("content", "Keine Antwort vom LLM.") + content = msg.get("content") or "" + if not content and msg.get("reasoning"): + content = msg.get("reasoning", "") + return content or "Keine Antwort vom LLM." messages.append(msg) @@ -286,6 +310,7 @@ def ask_with_tools(question: str, tool_handlers: dict, session_id: str = None) - except (json.JSONDecodeError, KeyError): fn_args = {} + log.info("Tool-Call: %s args=%s", fn_name, str(fn_args)[:200]) handler = tool_handlers.get(fn_name) if handler: try: @@ -366,7 +391,10 @@ def ask_with_image(image_base64: str, caption: str, tool_handlers: dict, session tool_calls = msg.get("tool_calls") if not tool_calls: - return msg.get("content", "Keine Antwort vom LLM.") + content = msg.get("content") or "" + if not content and msg.get("reasoning"): + content = msg.get("reasoning", "") + return content or "Keine Antwort vom LLM." messages.append(msg) diff --git a/homelab-ai-bot/tools/deep_research.py b/homelab-ai-bot/tools/deep_research.py index 74485032..67ae25e4 100644 --- a/homelab-ai-bot/tools/deep_research.py +++ b/homelab-ai-bot/tools/deep_research.py @@ -16,7 +16,7 @@ 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 wenn der User explizit "recherchiere", "finde heraus", "vergleiche" sagt oder eine komplexe Frage hat. +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). @@ -26,25 +26,7 @@ QUALITAET BEI PREISFRAGEN: - 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": "Startet eine tiefe Web-Recherche zu einem Thema. Durchsucht 20-30 Quellen und erstellt einen ausfuehrlichen Report. Dauert 2-5 Minuten — das ist normal, warte auf das Ergebnis.", - "parameters": { - "type": "object", - "properties": { - "query": { - "type": "string", - "description": "Die Recherche-Frage, moeglichst spezifisch formuliert." - } - }, - "required": ["query"] - }, - }, - }, -] +TOOLS = [] # removed from auto-discovery; use HANDLERS directly def _create_thread(): diff --git a/homelab-ai-bot/tools/web_search.py b/homelab-ai-bot/tools/web_search.py index 98eadbd1..33951cc8 100644 --- a/homelab-ai-bot/tools/web_search.py +++ b/homelab-ai-bot/tools/web_search.py @@ -12,7 +12,7 @@ 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.", + "description": "Schnelle Web-Suche (3-10 Sekunden). STANDARDTOOL fuer alle Fragen zu Preisen, Recherchen, News, Fakten, Vergleichen. Immer zuerst web_search nutzen, mehrfach mit verschiedenen Suchbegriffen.", "parameters": { "type": "object", "properties": {