feat(bot): Qwen3 30B-A3B lokal (Ollama/RTX3090), web_search bevorzugt, Date-Injection, Thinking-Mode Fix

This commit is contained in:
Cursor 2026-03-20 23:40:58 +01:00
parent 6e5a4c9529
commit 981118f940
3 changed files with 44 additions and 34 deletions

View file

@ -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)

View file

@ -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():

View file

@ -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": {