feat(bot): Qwen3 30B-A3B lokal (Ollama/RTX3090), web_search bevorzugt, Date-Injection, Thinking-Mode Fix
This commit is contained in:
parent
6e5a4c9529
commit
981118f940
3 changed files with 44 additions and 34 deletions
|
|
@ -4,6 +4,7 @@ Neue Datenquelle = eine Datei in tools/ anlegen. Fertig.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
import requests
|
import requests
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
@ -12,11 +13,22 @@ sys.path.insert(0, os.path.dirname(__file__))
|
||||||
from core import config
|
from core import config
|
||||||
import tool_loader
|
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"
|
VISION_MODEL = "openai/gpt-4o"
|
||||||
MAX_TOOL_ROUNDS = 3
|
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:
|
STIL:
|
||||||
- So wenig Worte wie moeglich, solange nichts Wichtiges fehlt.
|
- So wenig Worte wie moeglich, solange nichts Wichtiges fehlt.
|
||||||
|
|
@ -150,7 +162,8 @@ PREISRECHERCHE (PFLICHT):
|
||||||
Wenn der User nach Preisen, Kosten oder Preisentwicklung fragt:
|
Wenn der User nach Preisen, Kosten oder Preisentwicklung fragt:
|
||||||
- Nutze IMMER Tools statt Allgemeinwissen.
|
- Nutze IMMER Tools statt Allgemeinwissen.
|
||||||
- Fuer schnelle Preisabfrage: web_search.
|
- 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.
|
- Gib konkrete Zahlen aus (EUR), nicht nur Tendenzen.
|
||||||
- Nenne 3-5 Quellen-Links.
|
- Nenne 3-5 Quellen-Links.
|
||||||
- Wenn keine belastbaren Zahlen gefunden werden: klar sagen "keine belastbaren Preisdaten gefunden".
|
- 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,
|
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 = {
|
payload = {
|
||||||
"model": model or MODEL,
|
"model": chosen,
|
||||||
"messages": messages,
|
"messages": messages,
|
||||||
"max_tokens": max_tokens,
|
"max_tokens": max_tokens,
|
||||||
}
|
}
|
||||||
|
|
@ -183,12 +200,16 @@ def _call_openrouter(messages: list, api_key: str, use_tools: bool = True,
|
||||||
payload["tools"] = TOOLS
|
payload["tools"] = TOOLS
|
||||||
payload["tool_choice"] = "auto"
|
payload["tool_choice"] = "auto"
|
||||||
|
|
||||||
r = requests.post(
|
if use_ollama:
|
||||||
"https://openrouter.ai/api/v1/chat/completions",
|
url = f"{OLLAMA_BASE}/v1/chat/completions"
|
||||||
headers={"Authorization": f"Bearer {api_key}"},
|
headers = {"Content-Type": "application/json"}
|
||||||
json=payload,
|
timeout = 180
|
||||||
timeout=90,
|
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()
|
r.raise_for_status()
|
||||||
return r.json()
|
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")
|
tool_calls = msg.get("tool_calls")
|
||||||
if not 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)
|
messages.append(msg)
|
||||||
|
|
||||||
|
|
@ -286,6 +310,7 @@ def ask_with_tools(question: str, tool_handlers: dict, session_id: str = None) -
|
||||||
except (json.JSONDecodeError, KeyError):
|
except (json.JSONDecodeError, KeyError):
|
||||||
fn_args = {}
|
fn_args = {}
|
||||||
|
|
||||||
|
log.info("Tool-Call: %s args=%s", fn_name, str(fn_args)[:200])
|
||||||
handler = tool_handlers.get(fn_name)
|
handler = tool_handlers.get(fn_name)
|
||||||
if handler:
|
if handler:
|
||||||
try:
|
try:
|
||||||
|
|
@ -366,7 +391,10 @@ def ask_with_image(image_base64: str, caption: str, tool_handlers: dict, session
|
||||||
|
|
||||||
tool_calls = msg.get("tool_calls")
|
tool_calls = msg.get("tool_calls")
|
||||||
if not 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)
|
messages.append(msg)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ MAX_WAIT = 600
|
||||||
|
|
||||||
SYSTEM_PROMPT_EXTRA = """DEEP RESEARCH:
|
SYSTEM_PROMPT_EXTRA = """DEEP RESEARCH:
|
||||||
Du hast Zugriff auf deep_research — eine KI-gestuetzte Tiefenrecherche die 20-30 Quellen durchsucht.
|
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.
|
NICHT fuer einfache Fakten oder Homelab-Fragen.
|
||||||
WICHTIG: deep_research dauert 2-5 Minuten. Das ist normal. Warte auf das Ergebnis.
|
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).
|
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.
|
- Zeige Zeitraum, Preis damals/heute, Delta in % und Quellen.
|
||||||
- Wenn keine belastbaren Daten vorhanden sind, sage es explizit."""
|
- Wenn keine belastbaren Daten vorhanden sind, sage es explizit."""
|
||||||
|
|
||||||
TOOLS = [
|
TOOLS = [] # removed from auto-discovery; use HANDLERS directly
|
||||||
{
|
|
||||||
"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"]
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def _create_thread():
|
def _create_thread():
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ TOOLS = [
|
||||||
"type": "function",
|
"type": "function",
|
||||||
"function": {
|
"function": {
|
||||||
"name": "web_search",
|
"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": {
|
"parameters": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue