feat(2.0): Phase 6c — eingebauter OpenAI-Gateway (model:auto) statt LiteLLM

LiteLLM baut auf Python 3.14 nicht (orjson-Pin ohne cp314-Wheel). Stattdessen
eingebauter Gateway in MC2: routers/gateway_proxy.py (/v1/chat/completions,
/completions, /models) + services/router_logic.py (Komplexitaets-Routing
fast<->heavy, Streaming-Passthrough). gateway.py/routing.py/connect.py auf
builtin umgestellt (Endpunkt = MC :PORT/v1). Gleicher OpenAI-Vertrag,
spaeter gegen LiteLLM austauschbar.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-06-25 12:54:19 +02:00
parent db1f62227b
commit ceca2ae8e3
10 changed files with 143 additions and 99 deletions
+58
View File
@@ -0,0 +1,58 @@
"""
Eingebauter Routing-Gateway (OpenAI-kompatibel) — EIN Endpunkt für Hermes + IDEs.
`model: auto` → Komplexitäts-Routing fast↔heavy; jeder andere Name geht als
llama-swap-Alias durch (das lädt das Modell bei Bedarf). Streaming wird
durchgereicht. Ersetzt LiteLLM (das auf Python 3.14 nicht baut) — gleicher
Vertrag, später austauschbar.
"""
import httpx
from fastapi import APIRouter, Request
from fastapi.responses import JSONResponse, StreamingResponse
from config import LLAMA_SWAP_URL
from services.router_logic import choose_model
router = APIRouter(prefix="/v1")
@router.get("/models")
async def models():
async with httpx.AsyncClient(timeout=10) as c:
r = await c.get(f"{LLAMA_SWAP_URL}/v1/models")
return JSONResponse(r.json(), status_code=r.status_code)
async def _proxy(path: str, request: Request):
body = await request.json()
requested = str(body.get("model") or "auto")
if requested == "auto":
alias, reason = choose_model(body)
body["model"] = alias
routed = {"x-mc-routed-to": alias, "x-mc-route-reason": reason}
else:
routed = {"x-mc-routed-to": requested}
url = f"{LLAMA_SWAP_URL}{path}"
if body.get("stream"):
async def gen():
async with httpx.AsyncClient(timeout=None) as c:
async with c.stream("POST", url, json=body) as r:
async for chunk in r.aiter_raw():
yield chunk
return StreamingResponse(gen(), media_type="text/event-stream", headers=routed)
async with httpx.AsyncClient(timeout=600) as c:
r = await c.post(url, json=body)
return JSONResponse(r.json(), status_code=r.status_code, headers=routed)
@router.post("/chat/completions")
async def chat_completions(request: Request):
return await _proxy("/v1/chat/completions", request)
@router.post("/completions")
async def completions(request: Request):
return await _proxy("/v1/completions", request)
+2 -18
View File
@@ -1,7 +1,6 @@
"""Routing-Endpoints: Gateway-Übersicht (welcher Alias = fast/heavy/…) + Mapping setzen."""
"""Routing-Endpoint: zeigt den eingebauten Gateway (model:auto fastheavy)."""
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from fastapi import APIRouter
from services import gateway
@@ -11,18 +10,3 @@ router = APIRouter(prefix="/api")
@router.get("/routing")
def routing() -> dict:
return {**gateway.routing_summary(), "gateway_reachable": gateway.gateway_reachable()}
class RouteReq(BaseModel):
name: str # Gateway-Modellname, z.B. "fast" / "heavy" / "vision" / "coder"
target_alias: str # llama-swap-Alias, der bedient wird
api_base: str = "http://127.0.0.1:8080/v1"
@router.put("/routing/route")
def set_route(req: RouteReq) -> dict:
try:
gateway.set_route(req.name, req.target_alias, api_base=req.api_base)
except Exception as exc: # noqa: BLE001
raise HTTPException(500, str(exc))
return {"ok": True}