feat(llm): Flotten-Katalog + modell-bewusstes LLM-Routing (Stage C)

ARIA nutzt lokale LLMs auf mehreren ai-boxen; jede Box (llama-swap) kann
mehrere Modelle fahren. Auswahl nach MODELL, Box wird automatisch gewaehlt.

- xtts/llm-adapter/adapter.py: fragt beim Connect llama-swap GET /v1/models ab
  und meldet die Modell-Liste in worker_hello (models:[...]), Fallback [LLM_MODEL].
- bridge/aria_bridge.py: Worker-Registry speichert models[]; _pick_worker(service,
  model=) beruecksichtigt nur Boxen, die das Modell fahren koennen (nachsichtig:
  keine → None → Broadcast/Claude-Fallback); Round-Robin je service+model
  verteilt mehrere Projekte auf mehrere Boxen; _local_llm reicht das Modell durch;
  _worker_list traegt models[].
- diagnostic/server.js: workers-Map + workerList um models[] erweitert.
- diagnostic/index.html: Compute-Flotte zeigt bei llm die Modell-Liste; das
  "Lokales Modell"-Dropdown wird LIVE aus den angemeldeten Boxen gebaut
  (Vereinigung + kuratierte Namen aus local_models.json, "· N Box(en)"/"offline"),
  darunter eine kompakte LLM-Box-Liste (Node→Modelle→Health). Speisung aus dem
  vorhandenen worker_update-Broadcast.

Kein Brain-/App-Eingriff. Routing greift nur bei aktivem Lokal-Schalter.
Deploy: diagnostic + bridge + llm-Boxen neu bauen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-09-19 02:49:30 +02:00
co-authored by Claude Opus 4.8
parent 99e4b63a06
commit 39426dc70d
4 changed files with 114 additions and 26 deletions
+19
View File
@@ -133,14 +133,33 @@ async def _emit_llm_status(ws, state: str, model: str, **extra) -> None:
{"service": "llm", "state": state, "model": model, **extra})
async def _fetch_available_models() -> list:
"""Fragt llama-swap ab, welche Modelle diese Box fahren kann (GET /v1/models,
OpenAI-kompatibel → {data:[{id},...]}). Das sind die config.yaml-Keys.
Defensiv: bei Fehler Fallback auf [LLM_MODEL]."""
try:
async with httpx.AsyncClient(timeout=10) as client:
r = await client.get(f"{LLAMA_URL}/v1/models")
r.raise_for_status()
data = r.json()
ids = [m.get("id") for m in (data.get("data") or []) if m.get("id")]
return ids or [LLM_MODEL]
except Exception as e:
logger.warning("llama-swap /v1/models nicht abfragbar (%s) — Fallback [%s]", e, LLM_MODEL)
return [LLM_MODEL]
async def _worker_register(ws) -> None:
"""Meldet diesen Worker bei der aria-bridge an (worker_hello) und haelt die
Flotten-Registry per periodischem worker_ping (mit busy-Status) frisch."""
try:
models = await _fetch_available_models()
await _send(ws, "worker_hello", {
"instanceId": INSTANCE_ID, "service": WORKER_SERVICE,
"node": NODE_NAME, "gpus": GPU_IDS, "model": LLM_MODEL,
"models": models, # welche Modelle diese Box fahren kann (llama-swap-Keys)
})
logger.info("worker_hello: models=%s", models)
while True:
await asyncio.sleep(WORKER_PING_INTERVAL_S)
await _send(ws, "worker_ping",