9e432dbd2d
Teil 1: VoiceLatencyCard auf dem Dashboard (GET /api/voice/metrics, C2) — zeigt STT/Vision/Chat-TTFB/TTS mit p50/p95/last + count. Teil 2: UI-editierbare Routing-Policy. Neuer routing_policy.py (hot-reload JSON unter MODELS_DIR/mc2-routing.json, Env=Defaults, atomarer Write, Validierung). router_logic, gateway_proxy und gateway.routing_summary lesen jetzt live via load_policy(); routing_summary ist lane-bewusst (chat/coding statt altem auto). Neue Endpoints GET/PUT /api/routing/policy. Teil 3: LaneEditor.tsx als ZONE im Cockpit (chat/coding-Aliase + Schwellen + fast_no_think, Speichern/Default-je-Feld); Gateway-Node zeigt die Lanes. Verifiziert: npm run build (tsc strict) clean, FastAPI TestClient (GET/PUT, Validierung, Persistenz, Hot-reload durch die API), venv-Smoke (Routing). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
44 lines
1.4 KiB
Python
44 lines
1.4 KiB
Python
"""Routing-Endpoints: Lane-Summary (chat/coding) + UI-editierbare Policy (hot-reload)."""
|
|
|
|
from fastapi import APIRouter, HTTPException
|
|
from pydantic import BaseModel
|
|
|
|
from services import gateway
|
|
from services.routing_policy import policy_meta, save_policy
|
|
|
|
router = APIRouter(prefix="/api")
|
|
|
|
|
|
@router.get("/routing")
|
|
def routing() -> dict:
|
|
return {**gateway.routing_summary(), "gateway_reachable": gateway.gateway_reachable()}
|
|
|
|
|
|
@router.get("/routing/policy")
|
|
def get_policy() -> dict:
|
|
"""Aktuelle Policy + Defaults (für „Zurücksetzen“) + Feld-Spezifikation für den Editor."""
|
|
return policy_meta()
|
|
|
|
|
|
class PolicyPatch(BaseModel):
|
|
fast: str | None = None
|
|
heavy: str | None = None
|
|
coder: str | None = None
|
|
coder_lite: str | None = None
|
|
heavy_chars: int | None = None
|
|
coding_escalate_chars: int | None = None
|
|
fast_no_think: bool | None = None
|
|
|
|
|
|
@router.put("/routing/policy")
|
|
def put_policy(patch: PolicyPatch) -> dict:
|
|
"""Teil-Update der Routing-Policy. Validiert, persistiert atomar, sofort wirksam (hot-reload)."""
|
|
fields = {k: v for k, v in patch.model_dump().items() if v is not None}
|
|
if not fields:
|
|
raise HTTPException(status_code=400, detail="Keine Felder zum Aktualisieren.")
|
|
try:
|
|
new_policy = save_policy(fields)
|
|
except (ValueError, TypeError) as e:
|
|
raise HTTPException(status_code=400, detail=f"Ungültige Policy: {e}")
|
|
return {"policy": new_policy}
|