feat(rollen): echte Modellnamen als API-ID + Rollen als Tags/Alias

Modelle erscheinen jetzt mit ihrem ECHTEN Namen (= API-Name, den man in Zed/
OpenCode angibt) statt unter "coder"/"vision". Die Rolle wird ein zusaetzlicher,
sprechender Name (llama-swap-`aliases`) -> in der API funktionieren BEIDE.

- llamaswap.py: model_id_from_path (Repo-Ordner ohne -GGUF) + set_role_alias
  (Rolle als eindeutiger Alias, beim Setzen bei anderen Modellen entfernt) +
  ROLE_IDS (vision/coder/scout/reviewer/manager).
- models.py: register schreibt Key=Modellname, Rolle als Alias; /status liefert
  role + aliases + api_ids; neuer POST /set_role. Rueckwaertskompatibel: Legacy-
  Eintraege (coder/vision ohne Alias) behalten ihren Key als Rolle.
- cookbook.py: install-recipe/-model nutzen dasselbe Schema (Name + Rollen-Alias).
- models.js: Zeile zeigt echten Namen + Rollen-Tag + API-Name (klick=kopieren);
  neuer "Rolle"-Dialog mit 5 Schnellwahl-Rollen + freier Eingabe.
- cookbook.js: Profi-Download-Feld ist jetzt optionale Rolle (Name kommt vom
  Modell), Pflichtfeld-Pruefung entsprechend gelockert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-06-22 16:58:07 +02:00
parent 0c16fb28c2
commit 4077d06b2d
5 changed files with 169 additions and 17 deletions
+41
View File
@@ -7,11 +7,52 @@ Helfer rund um llama-swap und dessen config.yaml.
"""
import os
import re
import httpx
from config import CONFIG_PATH, LLAMA_SWAP_URL, yaml
# Kanonische Rollen (Tags auf den Modellen). Bewusst eine kleine, feste Palette fuer die
# UI-Schnellwahl — frei waehlbare Rollen sind trotzdem erlaubt.
ROLE_IDS = {"vision", "coder", "scout", "reviewer", "manager"}
def model_id_from_path(model_path: str) -> str:
"""Sprechende Modell-ID (= llama-swap-Modellname, den man in der API angibt) aus dem
GGUF-Pfad ableiten: der Repo-Ordnername ohne '-GGUF'. Fallback: Dateiname ohne
Quant-Suffix/.gguf. So steht in der Modell-Liste der echte Name statt 'coder'."""
d = os.path.basename(os.path.dirname(model_path))
name = re.sub(r"[-_]?GGUF$", "", d, flags=re.I).strip("-_")
if not name:
fn = re.sub(r"\.gguf$", "", os.path.basename(model_path), flags=re.I)
fn = re.sub(r"-\d+-of-\d+$", "", fn)
name = re.sub(r"[-_](Q\d[\w]*|IQ\d[\w]*|F16|BF16|FP16|F32)$", "", fn, flags=re.I)
return name or "modell"
def set_role_alias(cfg: dict, model_id: str, role: str | None) -> None:
"""Rolle als llama-swap-`aliases` auf ein Modell legen (damit der Rollenname in der API
ebenfalls funktioniert). Haelt den Alias EINDEUTIG: derselbe Rollen-Alias wird vorher
bei allen anderen Modellen entfernt. role=None/leer entfernt den Alias."""
models = cfg.get("models") or {}
role = (role or "").strip().lower()
if role:
for mid, spec in models.items():
if mid == model_id or not isinstance(spec, dict):
continue
al = [a for a in (spec.get("aliases") or []) if str(a).lower() != role]
if al:
spec["aliases"] = al
else:
spec.pop("aliases", None)
spec = models.get(model_id)
if isinstance(spec, dict):
if role and role != model_id.lower():
spec["aliases"] = [role]
else:
spec.pop("aliases", None)
def _swap_get(path: str):
with httpx.Client(timeout=5.0) as c: