diff --git a/backend/routers/gateway_proxy.py b/backend/routers/gateway_proxy.py index c7997d2..260a2f1 100644 --- a/backend/routers/gateway_proxy.py +++ b/backend/routers/gateway_proxy.py @@ -1,3 +1,5 @@ +import os + import httpx from fastapi import APIRouter, Request from fastapi.responses import JSONResponse, StreamingResponse @@ -9,6 +11,32 @@ from services.routing_policy import load_policy router = APIRouter(prefix="/v1") +# Antwortsprache für IDE-/Lane-Traffic: die Coding-Modelle antworten sonst englisch +# (User-Anforderung 03.07.2026). Leerer String (MC_GATEWAY_LANG_DIRECTIVE="") schaltet ab. +_LANG_DIRECTIVE = os.environ.get( + "MC_GATEWAY_LANG_DIRECTIVE", + "Antworte dem Nutzer grundsätzlich auf Deutsch (Erklärungen, Pläne, Rückfragen, " + "Zusammenfassungen) — auch wenn die Frage oder Tool-Anweisungen englisch sind. " + "Quellcode, Bezeichner und Shell-Befehle bleiben unverändert.") + + +def _inject_language(body: dict, alias: str) -> None: + """Deutsch-Direktive anhängen. An die ERSTE System-Message (viele Chat-Templates + erwarten nur eine), sonst als neue System-Message. `hermes` ausgenommen — Lucys + Persona (SOUL.md) regelt die Sprache selbst.""" + if not _LANG_DIRECTIVE or alias == "hermes": + return + msgs = body.get("messages") + if not isinstance(msgs, list): + return + first_sys = next((m for m in msgs if isinstance(m, dict) and m.get("role") == "system"), None) + if first_sys is None: + msgs.insert(0, {"role": "system", "content": _LANG_DIRECTIVE}) + elif isinstance(first_sys.get("content"), str): + first_sys["content"] = first_sys["content"].rstrip() + "\n\n" + _LANG_DIRECTIVE + elif isinstance(first_sys.get("content"), list): + first_sys["content"].append({"type": "text", "text": _LANG_DIRECTIVE}) + # Virtuelle Lanes, die der Gateway zusätzlich zu den echten Modellen als „Modell" anbietet. _LANE_LABELS = {"coding": "Coding (Router → coder/heavy/fast)", "chat": "Chat (Router → fast/heavy)"} @@ -66,6 +94,7 @@ async def _proxy(path: str, request: Request): pol = load_policy() if pol["fast_no_think"] and alias == pol["fast"] and "chat_template_kwargs" not in body: body["chat_template_kwargs"] = {"enable_thinking": False} + _inject_language(body, alias) url = f"{LLAMA_SWAP_URL}{path}" if body.get("stream"): diff --git a/mcp/mcp_web.py b/mcp/mcp_web.py index bf88213..c03209b 100644 --- a/mcp/mcp_web.py +++ b/mcp/mcp_web.py @@ -60,6 +60,24 @@ def fetch_url(url: str, max_chars: int = 8000) -> str: return f"FEHLER: {e}" +@mcp.tool() +def web_search(query: str, max_results: int = 6) -> str: + """Websuche (DuckDuckGo, kein API-Key). Liefert Titel, URL und Kurzbeschreibung je + Treffer. Nutze dies für aktuelle Informationen, Dokumentation oder Fehlermeldungen — + und lies vielversprechende Treffer danach mit fetch_url im Volltext.""" + try: + from ddgs import DDGS + + rows = list(DDGS().text(query, max_results=max_results) or []) + if not rows: + return "Keine Treffer." + lines = [f"- {r.get('title', '(ohne Titel)')}\n {r.get('href', '')}\n {r.get('body', '')}" + for r in rows] + return wrap_untrusted("\n".join(lines), label=f"WEBSUCHE {query}") + except Exception as e: + return f"FEHLER: {e}" + + @mcp.tool() def fetch_urls(urls: list[str], max_chars_each: int = 4000) -> str: """Lädt mehrere URLs gleichzeitig und gibt die Texte zurück.