Fix: Modell-Upgrades nur bei ECHTEM Upgrade (gleiche Familie + neuere Gen/groesser)
model_upgrades verletzte das Prinzip: schlug Generations-Downgrades (Qwen2.5-VL ueber Qwen3-VL), Groessen-Downgrades (Qwen3-Coder-Next ~84B -> 30B; Params aus Name als 7B fehlgeschaetzt) und Fremd-Familien-Swaps (gpt-oss als Qwen-"Upgrade") vor. - _params_of: groessen-bewusste Params (max aus Name + Dateigroesse). - _gen_key: Familie+Subtyp+Generation aus dem Namen (qwen-vl 3.0 vs 2.5 etc.). - Guard: Upgrade nur bei gleicher erkennbarer Familie UND (neuere Generation ODER deutlich groesser in gleicher Gen). Sonst keine "Bessere Version"-Anzeige. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -154,48 +154,77 @@ def _components_cached() -> list[dict]:
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def _params_of(m: dict) -> float:
|
||||||
|
"""Größen-bewusste Parameterzahl eines installierten Modells: max aus Namens-Schätzung
|
||||||
|
und Dateigröße (fängt namenlose wie 'Qwen3-Coder-Next' UND Split-GGUFs ab)."""
|
||||||
|
from services.fit import QUANT_BYTES_PER_PARAM
|
||||||
|
caps = m.get("capabilities") or {}
|
||||||
|
bpp = QUANT_BYTES_PER_PARAM.get((m.get("quant") or "Q4_K_M").upper(), 0.55)
|
||||||
|
size_gb = (m.get("size_bytes") or 0) / (1024 ** 3)
|
||||||
|
pb_size = (size_gb / bpp) if size_gb > 1.0 else 0.0
|
||||||
|
return max(float(caps.get("params_b") or 0), pb_size, 0.0)
|
||||||
|
|
||||||
|
|
||||||
|
# Familien-Subtyp + Generations-Version aus dem Modellnamen (für „echtes Upgrade?").
|
||||||
|
_FAM_PATS = (("qwen", r"qwen(\d+(?:\.\d+)?)"), ("gemma", r"gemma[-_ ]?(\d+(?:\.\d+)?)"),
|
||||||
|
("llama", r"llama[-_ ]?(\d+(?:\.\d+)?)"), ("phi", r"phi[-_ ]?(\d+(?:\.\d+)?)"),
|
||||||
|
("mistral", r"mistral"), ("hermes", r"hermes[-_ ]?(\d+(?:\.\d+)?)"))
|
||||||
|
|
||||||
|
|
||||||
|
def _gen_key(name: str):
|
||||||
|
"""(Familie+Subtyp, Generations-Version) oder None. Z.B. 'Qwen3-VL-2B' → ('qwen-vl', 3.0),
|
||||||
|
'Qwen2.5-VL-7B' → ('qwen-vl', 2.5). Nur gleiche Familie ist sinnvoll vergleichbar."""
|
||||||
|
low = (name or "").lower()
|
||||||
|
sub = "-vl" if any(k in low for k in ("-vl", "vl-", "vision", "llava", "pixtral")) else \
|
||||||
|
"-coder" if ("coder" in low or "-code" in low) else ""
|
||||||
|
for fam, pat in _FAM_PATS:
|
||||||
|
m = re.search(pat, low)
|
||||||
|
if m:
|
||||||
|
ver = float(m.group(1)) if (m.groups() and m.group(1)) else 0.0
|
||||||
|
return (fam + sub, ver)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def model_upgrades() -> list[dict]:
|
def model_upgrades() -> list[dict]:
|
||||||
"""Dynamisch: je discover-Kategorie das empfohlene Modell, das NOCH NICHT installiert ist,
|
"""Je Rolle ein ECHTES Upgrade-Vorschlag — nur wenn die Empfehlung wirklich besser ist:
|
||||||
aber NUR wenn für diese Rolle bereits irgendein Modell konfiguriert ist.
|
gleiche Familie UND (neuere Generation ODER größer in gleicher Generation). Verhindert
|
||||||
→ Upgrade-Vorschlag für diese Rolle. Self-updating (kein Hardcode wie v1)."""
|
Generations-Downgrades (Qwen2.5-VL über Qwen3-VL), Größen-Downgrades (Coder-Next → 30B)
|
||||||
|
und Fremd-Familien-Swaps (gpt-oss als „Upgrade" von Qwen)."""
|
||||||
disc = discover.safe_discover(_ram_gb())
|
disc = discover.safe_discover(_ram_gb())
|
||||||
if not disc:
|
if not disc:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
installed = llamaswap.list_models()
|
installed = llamaswap.list_models()
|
||||||
active_roles = {m["role"] for m in installed if m.get("role")}
|
inst_by_role = {m["role"]: m for m in installed if m.get("role")}
|
||||||
# Größtes installiertes Modell je Rolle (params_b) — Basis für „kein Downgrade".
|
|
||||||
inst_params: dict[str, float] = {}
|
|
||||||
for m in installed:
|
|
||||||
r, pb = m.get("role"), (m.get("capabilities") or {}).get("params_b")
|
|
||||||
if r and pb:
|
|
||||||
inst_params[r] = max(inst_params.get(r, 0.0), float(pb))
|
|
||||||
|
|
||||||
cmds = " ".join(str(s.get("cmd", "")).lower()
|
cmds = " ".join(str(s.get("cmd", "")).lower()
|
||||||
for s in (llamaswap.read_config().get("models") or {}).values())
|
for s in (llamaswap.read_config().get("models") or {}).values())
|
||||||
out = []
|
out = []
|
||||||
|
|
||||||
# Discover-Rollen == Serving-Rollen (fast/heavy/coder/vision/scout) → kein Mapping mehr.
|
|
||||||
for c in disc.get("categories", []):
|
for c in disc.get("categories", []):
|
||||||
role = c["role"]
|
role = c["role"]
|
||||||
if role not in active_roles: # nur Rollen, die bereits ein Modell haben
|
im = inst_by_role.get(role)
|
||||||
|
if im is None: # nur Rollen mit bereits installiertem Modell
|
||||||
continue
|
continue
|
||||||
|
|
||||||
rec = c.get("recommended")
|
rec = c.get("recommended")
|
||||||
if not rec:
|
if not rec:
|
||||||
continue
|
continue
|
||||||
# KEIN Downgrade: Empfehlung nur, wenn sie mind. so „groß"/fähig ist wie das
|
|
||||||
# installierte Modell der Rolle (verhindert z.B. fast 35B-A3B → 4B).
|
|
||||||
rec_model = next((x for x in c.get("models", []) if x.get("repo") == rec), None)
|
rec_model = next((x for x in c.get("models", []) if x.get("repo") == rec), None)
|
||||||
rec_params = float((rec_model or {}).get("params_b") or 0.0)
|
rec_params = float((rec_model or {}).get("params_b") or 0.0)
|
||||||
cur = inst_params.get(role, 0.0)
|
inst_params = _params_of(im)
|
||||||
if cur and rec_params and rec_params < cur * 0.95:
|
|
||||||
continue
|
ig, rg = _gen_key(im["name"]), _gen_key(rec)
|
||||||
|
# Upgrade nur bei GLEICHER, erkennbarer Familie:
|
||||||
|
if not (ig and rg) or ig[0] != rg[0]:
|
||||||
|
continue # Fremd-Familie / unbekannt → kein „Upgrade"
|
||||||
|
if rg[1] < ig[1] - 1e-6:
|
||||||
|
continue # ältere Generation → niemals
|
||||||
|
if abs(rg[1] - ig[1]) < 1e-6 and rec_params and rec_params < inst_params * 1.05:
|
||||||
|
continue # gleiche Gen, nicht (deutlich) größer
|
||||||
|
|
||||||
base = rec.split("/")[-1].lower()
|
base = rec.split("/")[-1].lower()
|
||||||
stem = base[:-5] if base.endswith("-gguf") else base
|
stem = base[:-5] if base.endswith("-gguf") else base
|
||||||
if base in cmds or (stem and stem in cmds):
|
if base in cmds or (stem and stem in cmds):
|
||||||
continue
|
continue # schon installiert
|
||||||
out.append({"role": role, "title": c["title"], "repo": rec})
|
out.append({"role": role, "title": c["title"], "repo": rec})
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user