feat(2.0): Phase 1 — Engine + Routing (Herzstueck)
Backend-Services: fit/caps/sources (portiert), discover (live HF + Fit + Caps + ranked recommendation), llama-swap write/register + groups (Ko- Residenz swap:false), LiteLLM-Gateway-Config + gateway-Service (model:auto + Fallbacks). Router: discover/fit/register/groups/routing; health zeigt gateway_reachable. Frontend: Modelle&Routing mit Caps-Chips, Fit-Badges, Discover-Tab (live), Routing-View. Lokal verifiziert: Backend-Smoke (alle Endpunkte) + Frontend-Build + Browser (Shell, Discover, Caps/Fit). Box-Verifikation offen. Docs: README + docs/STATUS.md (Phasen-Tracker + Resume-Guide). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from config import VERSION
|
||||
from services import llamaswap
|
||||
from services import gateway, llamaswap
|
||||
|
||||
router = APIRouter(prefix="/api")
|
||||
|
||||
@@ -14,4 +14,5 @@ def health() -> dict:
|
||||
"status": "ok",
|
||||
"version": VERSION,
|
||||
"engine_reachable": llamaswap.engine_reachable(),
|
||||
"gateway_reachable": gateway.gateway_reachable(),
|
||||
}
|
||||
|
||||
@@ -1,13 +1,81 @@
|
||||
"""Modelle-Endpoint (Phase 0: read-only Liste aus der llama-swap config.yaml)."""
|
||||
"""Modelle-Endpoints: Liste (mit Caps), Discover, Fit, Register, Groups."""
|
||||
|
||||
from fastapi import APIRouter
|
||||
import psutil
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
from services import llamaswap
|
||||
from services import discover, llamaswap
|
||||
from services.fit import evaluate_fit, max_ctx_for
|
||||
|
||||
router = APIRouter(prefix="/api")
|
||||
|
||||
|
||||
def _ram_gb() -> float:
|
||||
return psutil.virtual_memory().total / (1024 ** 3)
|
||||
|
||||
|
||||
@router.get("/models")
|
||||
def models() -> dict:
|
||||
items = llamaswap.list_models()
|
||||
return {"models": items, "count": len(items)}
|
||||
|
||||
|
||||
@router.get("/discover")
|
||||
def discover_models(force: bool = False) -> dict:
|
||||
ram = _ram_gb()
|
||||
data = discover.refresh_discover(ram) if force else discover.safe_discover(ram)
|
||||
if not data:
|
||||
raise HTTPException(502, "Modell-Quellen gerade nicht erreichbar — später erneut.")
|
||||
return {**data, "sys_ram_gb": round(ram, 1)}
|
||||
|
||||
|
||||
@router.get("/fit")
|
||||
def fit(params_b: float, quant: str = "Q4_K_M", ctx: int = 8192, name: str = "") -> dict:
|
||||
ram = _ram_gb()
|
||||
return {
|
||||
"fit": evaluate_fit(params_b, quant, ctx, ram, name=name),
|
||||
"optimal_ctx": max_ctx_for(params_b, quant, ram),
|
||||
"sys_ram_gb": round(ram, 1),
|
||||
}
|
||||
|
||||
|
||||
class RegisterReq(BaseModel):
|
||||
model_path: str
|
||||
role: str | None = None
|
||||
ctx: int = 8192
|
||||
ttl: int | None = None
|
||||
mmproj_path: str | None = None
|
||||
jinja: bool = False
|
||||
|
||||
|
||||
@router.post("/models/register")
|
||||
def register(req: RegisterReq) -> dict:
|
||||
try:
|
||||
model_id = llamaswap.register_model(
|
||||
req.model_path, role=req.role, ctx=req.ctx, ttl=req.ttl,
|
||||
mmproj_path=req.mmproj_path, jinja=req.jinja,
|
||||
)
|
||||
except PermissionError as exc:
|
||||
raise HTTPException(500, str(exc))
|
||||
return {"ok": True, "model_id": model_id}
|
||||
|
||||
|
||||
@router.get("/groups")
|
||||
def groups() -> dict:
|
||||
return {"groups": llamaswap.list_groups()}
|
||||
|
||||
|
||||
class GroupReq(BaseModel):
|
||||
group: str
|
||||
members: list[str]
|
||||
swap: bool = False
|
||||
persist: bool = False
|
||||
|
||||
|
||||
@router.put("/groups")
|
||||
def set_group(req: GroupReq) -> dict:
|
||||
try:
|
||||
llamaswap.set_group(req.group, req.members, swap=req.swap, persist=req.persist)
|
||||
except PermissionError as exc:
|
||||
raise HTTPException(500, str(exc))
|
||||
return {"ok": True}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
"""Routing-Endpoints: Gateway-Übersicht (welcher Alias = fast/heavy/…) + Mapping setzen."""
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
from services import gateway
|
||||
|
||||
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}
|
||||
Reference in New Issue
Block a user