Feat: virtuelle Gateway-Lanes 'coding' + 'chat' (Router macht die Arbeit)
Der :9001/v1-Gateway bietet zwei virtuelle Modelle an, die der Router auf echte Modelle abbildet: - coding → coder (Standard) · heavy (riesiger/architektonischer Kontext) · fast (triviale Nicht-Code-Kurzfrage) - chat → fast/heavy (= bisheriges model:auto, weiter als Alias unterstützt) router_logic.choose_for_lane() kapselt die Lane-Logik (Code-Indikatoren DE+EN, damit echte Coding-Anfragen nie auf fast abrutschen). gateway_proxy routet die Lane-Namen und listet sie in /v1/models, sodass IDEs einfach "coding" wählen. Lucy/Hermes (:8642) bleibt unberührt — andere Ebene. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,33 +1,93 @@
|
||||
"""
|
||||
Komplexitäts-Routing für `model: auto` (eingebauter Gateway).
|
||||
Schnell im Alltag (fast), schwer bei Bedarf (heavy) — regelbasiert, sub-ms, ohne Cloud.
|
||||
Lane-Routing für den eingebauten MC2-Gateway (:9001/v1).
|
||||
|
||||
Zwei virtuelle Lanes, die Clients/IDEs auswählen — der Router pickt das echte Modell:
|
||||
- **chat** (= altes `auto`): Alltag → `fast`, schwer/lang → `heavy`.
|
||||
- **coding**: Code-Arbeit → `coder` (Qwen3-Coder-Next); riesiger/architektonischer Kontext → `heavy`;
|
||||
triviale Kurzfrage ohne Code → `fast` (Tempo).
|
||||
|
||||
Regelbasiert, sub-ms, ohne Cloud. Schwellen/Aliases via Env überschreibbar (Phase 2: UI-editierbare
|
||||
Policy-JSON). Lucy läuft NICHT hierüber — die ist der Hermes-Agent (:8642), eigene Ebene.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
# Echte Modell-Aliases hinter den Lanes (Env-überschreibbar).
|
||||
FAST = os.environ.get("MC_ROUTE_FAST", "fast")
|
||||
HEAVY = os.environ.get("MC_ROUTE_HEAVY", "heavy")
|
||||
HEAVY_CHARS = int(os.environ.get("MC_GATEWAY_HEAVY_CHARS", "8000"))
|
||||
# Thinking auf der fast-Spur ausschalten → flotte Alltags-Antworten (Qwen3.6 ist ein
|
||||
# Reasoning-Modell). heavy behält Thinking für harte Aufgaben. Abschaltbar via Env.
|
||||
CODER = os.environ.get("MC_ROUTE_CODER", "coder")
|
||||
|
||||
# Schwellen (Zeichen).
|
||||
HEAVY_CHARS = int(os.environ.get("MC_GATEWAY_HEAVY_CHARS", "8000")) # chat → heavy
|
||||
CODING_HEAVY_CHARS = int(os.environ.get("MC_CODING_HEAVY_CHARS", "24000")) # coding → heavy
|
||||
CODING_TRIVIAL_CHARS = int(os.environ.get("MC_CODING_TRIVIAL_CHARS", "240")) # coding → fast (nur ohne Code)
|
||||
|
||||
# Thinking auf der fast-Spur aus → flotte Alltags-Antworten (Qwen3.6 ist ein Reasoning-Modell).
|
||||
FAST_NO_THINK = os.environ.get("MC_FAST_NO_THINK", "1") not in ("0", "false", "")
|
||||
|
||||
# Virtuelle Lanes, die im Gateway als „Modelle" sichtbar sind.
|
||||
LANES = ["coding", "chat"]
|
||||
LANE_ALIASES = {"auto": "chat"} # Rückwärtskompatibel: model:auto == chat
|
||||
|
||||
_HEAVY_KW = re.compile(
|
||||
r"\b(beweis|prove|theorem|refactor|architect|komplex|complex|schwierig|"
|
||||
r"think\s*hard|reason\s*carefully|tief\s*nachdenk|optimi[sz]e|algorithm|"
|
||||
r"root\s*cause|debug|analy[sz]e\s+deeply|step[-\s]?by[-\s]?step)\b",
|
||||
r"\b(beweis|prove|theorem|komplex|complex|schwierig|"
|
||||
r"think\s*hard|reason\s*carefully|tief\s*nachdenk|optimi[sz]e|"
|
||||
r"root\s*cause|analy[sz]e\s+deeply|step[-\s]?by[-\s]?step)\b",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
# Coding-spezifische „das ist groß/architektonisch" Signale → heavy statt coder.
|
||||
_CODING_HEAVY_KW = re.compile(
|
||||
r"\b(architekt|architect|system[-\s]?design|refactor\s+the\s+(whole|entire)|"
|
||||
r"ganze[ns]?\s+(architektur|codebase|projekt)|migrat\w+\s+(the\s+)?(whole|entire|gesamte)|"
|
||||
r"entwirf\s+(eine\s+)?architektur|plane?\s+(die\s+)?architektur)\b",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
# Code-Indikatoren — verhindert, dass echte Code-Anfragen als „trivial" auf fast abrutschen.
|
||||
# Bewusst breit (inkl. natürlichsprachiger Coding-Begriffe DE+EN): in der coding-Lane soll im Zweifel
|
||||
# `coder` gewinnen; nur echte Nicht-Code-Kürze ("hallo", "wie spät") rutscht auf fast.
|
||||
_CODE_HINT = re.compile(
|
||||
r"```|\bdef \b|\bclass \b|\bimport \b|\bfunction\b|=>|;\s*$|"
|
||||
r"\.(py|ts|tsx|js|jsx|go|rs|java|cpp|c|rb|php|sql)\b|/src/|traceback|stack\s*trace|"
|
||||
r"\b(funktion|function|bug|fix|fehler|error|exception|implementier\w*|schreib\w*|"
|
||||
r"code\w*|coden|test\w*|klasse|method\w*|methode|refactor\w*|kompil\w*|compile|"
|
||||
r"build|deploy|debug|script|skript|api|endpoint|query|regex|json|yaml|"
|
||||
r"npm|pip|git|docker|terminal|shell|command)\b",
|
||||
re.IGNORECASE | re.MULTILINE,
|
||||
)
|
||||
|
||||
|
||||
def choose_model(body: dict) -> tuple[str, str]:
|
||||
"""Wählt fast|heavy für eine Chat-Anfrage. Gibt (alias, begründung) zurück."""
|
||||
def _text_of(body: dict) -> str:
|
||||
msgs = body.get("messages") or []
|
||||
text = "\n".join(str(m.get("content") or "") for m in msgs)
|
||||
n = len(text)
|
||||
return "\n".join(str(m.get("content") or "") for m in msgs)
|
||||
|
||||
|
||||
def _route_chat(text: str, n: int) -> tuple[str, str]:
|
||||
if n > HEAVY_CHARS:
|
||||
return HEAVY, f"langer Kontext ({n} > {HEAVY_CHARS} Zeichen)"
|
||||
if _HEAVY_KW.search(text):
|
||||
return HEAVY, "Komplexitäts-Schlüsselwort erkannt"
|
||||
return FAST, "Standard"
|
||||
|
||||
|
||||
def _route_coding(text: str, n: int) -> tuple[str, str]:
|
||||
if n > CODING_HEAVY_CHARS or _CODING_HEAVY_KW.search(text):
|
||||
return HEAVY, "großer/architektonischer Coding-Kontext"
|
||||
if n < CODING_TRIVIAL_CHARS and not _CODE_HINT.search(text):
|
||||
return FAST, "triviale Kurzfrage (kein Code)"
|
||||
return CODER, "Coding-Standard"
|
||||
|
||||
|
||||
def choose_for_lane(lane: str, body: dict) -> tuple[str, str]:
|
||||
"""Wählt das echte Modell-Alias für eine Lane. Gibt (alias, begründung) zurück."""
|
||||
lane = LANE_ALIASES.get((lane or "chat").lower(), (lane or "chat").lower())
|
||||
text = _text_of(body)
|
||||
n = len(text)
|
||||
if lane == "coding":
|
||||
return _route_coding(text, n)
|
||||
return _route_chat(text, n) # chat + alles Unbekannte
|
||||
|
||||
|
||||
def choose_model(body: dict) -> tuple[str, str]:
|
||||
"""Rückwärtskompatibel: altes `model:auto` == chat-Lane."""
|
||||
return choose_for_lane("chat", body)
|
||||
|
||||
Reference in New Issue
Block a user