Gateway: Deutsch-Direktive fuer IDE-Traffic + web_search-Tool im Web-MCP
1) /v1-Gateway haengt an jede Chat-Anfrage (ausser hermes/Lucy — SOUL.md regelt das selbst) eine System-Direktive an: Antworten auf Deutsch, Code/Bezeichner unveraendert. Abschaltbar via MC_GATEWAY_LANG_DIRECTIVE="". 2) mcp_web.py: neues Tool web_search (ddgs/DuckDuckGo, kein Key), Ergebnis injection-geschuetzt via wrap_untrusted. Damit koennen IDE-Agents (Zed via context_servers) und Hermes im Web suchen; Debug-Log geht auf stderr, stdout bleibt MCP-sauber (verifiziert). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from fastapi import APIRouter, Request
|
from fastapi import APIRouter, Request
|
||||||
from fastapi.responses import JSONResponse, StreamingResponse
|
from fastapi.responses import JSONResponse, StreamingResponse
|
||||||
@@ -9,6 +11,32 @@ from services.routing_policy import load_policy
|
|||||||
|
|
||||||
router = APIRouter(prefix="/v1")
|
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.
|
# 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)"}
|
_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()
|
pol = load_policy()
|
||||||
if pol["fast_no_think"] and alias == pol["fast"] and "chat_template_kwargs" not in body:
|
if pol["fast_no_think"] and alias == pol["fast"] and "chat_template_kwargs" not in body:
|
||||||
body["chat_template_kwargs"] = {"enable_thinking": False}
|
body["chat_template_kwargs"] = {"enable_thinking": False}
|
||||||
|
_inject_language(body, alias)
|
||||||
url = f"{LLAMA_SWAP_URL}{path}"
|
url = f"{LLAMA_SWAP_URL}{path}"
|
||||||
|
|
||||||
if body.get("stream"):
|
if body.get("stream"):
|
||||||
|
|||||||
@@ -60,6 +60,24 @@ def fetch_url(url: str, max_chars: int = 8000) -> str:
|
|||||||
return f"FEHLER: {e}"
|
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()
|
@mcp.tool()
|
||||||
def fetch_urls(urls: list[str], max_chars_each: int = 4000) -> str:
|
def fetch_urls(urls: list[str], max_chars_each: int = 4000) -> str:
|
||||||
"""Lädt mehrere URLs gleichzeitig und gibt die Texte zurück.
|
"""Lädt mehrere URLs gleichzeitig und gibt die Texte zurück.
|
||||||
|
|||||||
Reference in New Issue
Block a user