Fix: Rollen-Scorer prinzipien-konform (fast!=groesstes, Hirn=Hermes/moderat)
Erster Wurf empfahl fast->122B und hermes->122B: ein globaler Wissen=Groesse-Term ueberstimmte die Rollen-Absicht. Jetzt getrennt: - _capability_suit: harte Gates (Vision braucht Vision; Coder-Modell Pflicht; Hirn bevorzugt Hermes-Familie/Tools; dedizierte VL > Omni). - _pref: rollengerechte Groessen-/Tempo-Praeferenz (fast=Tempo&klein, heavy=gross, hermes=7-24B, vision=klein, scout=moderat). - _catalog_role_match: Cookbook-Eintrag fuer die Rolle = starker Anker-Bonus. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+56
-22
@@ -9,7 +9,7 @@ und fit.evaluate_fit — dieselbe Mathematik wie Install-Automatik und Auto-ctx-
|
|||||||
|
|
||||||
import psutil
|
import psutil
|
||||||
|
|
||||||
from services import budget, llamaswap
|
from services import budget, catalog, llamaswap
|
||||||
from services.fit import evaluate_fit
|
from services.fit import evaluate_fit
|
||||||
|
|
||||||
|
|
||||||
@@ -17,53 +17,86 @@ def _ram_gb() -> float:
|
|||||||
return psutil.virtual_memory().total / (1024 ** 3)
|
return psutil.virtual_memory().total / (1024 ** 3)
|
||||||
|
|
||||||
|
|
||||||
def _suitability(role: str, caps: dict, params: float, name: str) -> float:
|
def _capability_suit(role: str, caps: dict, name: str) -> float:
|
||||||
"""0..1 — wie gut passt die Capability eines Modells zur Rolle. Harte Anforderungen
|
"""0..1 — Capability-Eignung (HARTE Gates). 0 = grundsätzlich falsch für die Rolle.
|
||||||
(Vision braucht Vision) geben 0 bei Nichterfüllung; weiche Präferenzen skalieren."""
|
Größe/Tempo bewertet getrennt _pref(), damit z.B. 'fast' nicht das größte Modell zieht."""
|
||||||
role = (role or "").lower()
|
role = (role or "").lower()
|
||||||
low = (name or "").lower()
|
low = (name or "").lower()
|
||||||
vision = bool(caps.get("vision"))
|
vision = bool(caps.get("vision"))
|
||||||
coder = bool(caps.get("coder"))
|
coder = bool(caps.get("coder"))
|
||||||
tools = caps.get("tools") != "no"
|
tools = caps.get("tools") != "no"
|
||||||
moe = bool(caps.get("moe"))
|
|
||||||
|
|
||||||
if role == "vision":
|
if role == "vision":
|
||||||
return 1.0 if vision else 0.0 # harte Anforderung
|
if not vision:
|
||||||
|
return 0.0 # harte Anforderung
|
||||||
|
return 1.0 if ("vl" in low or "llava" in low or "pixtral" in low) else 0.7 # dediziert > omni
|
||||||
if role == "coder":
|
if role == "coder":
|
||||||
return 1.0 if coder else 0.45 # Coder bevorzugt, andere notfalls
|
return 1.0 if coder else 0.4 # Coder-Modell Pflicht für Empfehlung
|
||||||
if role == "hermes":
|
if role == "hermes":
|
||||||
# Agent-Hirn: natives Tool-Calling Pflicht; Hermes-Familie am robustesten.
|
# Agent-Hirn: Hermes-Familie am robustesten; sonst natives Tool-Calling Pflicht.
|
||||||
if "hermes" in low:
|
if "hermes" in low:
|
||||||
return 1.0
|
return 1.0
|
||||||
return 0.85 if tools else 0.15
|
return 0.6 if tools else 0.1
|
||||||
if role == "fast":
|
if role == "fast":
|
||||||
# schnelles Alltags-Hirn: klein/MoE bevorzugt (niedrige aktive Params = Tempo).
|
# Alltags-Hirn braucht zuverlässige Tools; Größe/Tempo macht _pref.
|
||||||
return 1.0 if (moe or params <= 40) else 0.5
|
return 1.0 if tools else 0.6
|
||||||
if role == "heavy":
|
if role == "heavy":
|
||||||
# schweres Reasoning: Wissen = Gesamt-Params (groß bevorzugt).
|
return 1.0
|
||||||
return min(params / 70.0, 1.0)
|
|
||||||
if role == "scout":
|
if role == "scout":
|
||||||
# Multimodal-Allrounder: Vision ein Plus, sonst solide Basis.
|
|
||||||
return 0.9 if vision else 0.7
|
return 0.9 if vision else 0.7
|
||||||
return 0.5
|
return 0.5
|
||||||
|
|
||||||
|
|
||||||
def _reason(role: str, caps: dict, fit: dict, suit: float, fits: bool, incomplete: bool) -> str:
|
def _pref(role: str, params: float, tps: float) -> float:
|
||||||
|
"""0..1 — rollengerechte GRÖSSEN-/TEMPO-Präferenz. 'fast' belohnt Tempo & Kleinheit,
|
||||||
|
'heavy' Größe (Wissen), 'hermes' moderate Größe (muss warm + ko-resident bleiben)."""
|
||||||
|
role = (role or "").lower()
|
||||||
|
if role == "fast":
|
||||||
|
speed = min(tps / 25.0, 1.0)
|
||||||
|
size_ok = 1.0 if params <= 50 else 50.0 / params
|
||||||
|
return speed * size_ok
|
||||||
|
if role == "heavy":
|
||||||
|
return min(params / 120.0, 1.0)
|
||||||
|
if role == "hermes":
|
||||||
|
return 1.0 if params <= 24 else max(0.15, 24.0 / params) # 7–24B ideal als Hirn
|
||||||
|
if role == "vision":
|
||||||
|
return 1.0 if params <= 12 else 0.7 # klein/günstig bevorzugt
|
||||||
|
if role == "coder":
|
||||||
|
return 0.5 + 0.5 * min(params / 80.0, 1.0)
|
||||||
|
if role == "scout":
|
||||||
|
return 1.0 if params <= 40 else 0.5
|
||||||
|
return 0.5
|
||||||
|
|
||||||
|
|
||||||
|
def _catalog_role_match(role: str, name: str) -> bool:
|
||||||
|
"""Ist dieses Modell im kuratierten Katalog (Cookbook) genau für DIESE Rolle gelistet?
|
||||||
|
Dann ist es der prinzipien-konforme Pick → starker Bonus."""
|
||||||
|
meta = catalog.meta_for_name(name)
|
||||||
|
return bool(meta and (meta.get("role") or "").lower() == (role or "").lower())
|
||||||
|
|
||||||
|
|
||||||
|
def _reason(role: str, caps: dict, name: str, fit: dict, fits: bool,
|
||||||
|
incomplete: bool, cat_match: bool) -> str:
|
||||||
if incomplete:
|
if incomplete:
|
||||||
return "Download unvollständig"
|
return "Download unvollständig"
|
||||||
if role == "vision" and not caps.get("vision"):
|
if role == "vision" and not caps.get("vision"):
|
||||||
return "keine Vision-Fähigkeit"
|
return "keine Vision-Fähigkeit"
|
||||||
if role == "hermes" and caps.get("tools") == "no":
|
if role == "coder" and not caps.get("coder"):
|
||||||
|
return "kein Coder-Modell"
|
||||||
|
if role == "hermes" and "hermes" not in (name or "").lower() and caps.get("tools") == "no":
|
||||||
return "kein natives Tool-Calling"
|
return "kein natives Tool-Calling"
|
||||||
if not fits:
|
if not fits:
|
||||||
return "passt nicht ins Budget (OOM)"
|
return "passt nicht ins Budget (OOM)"
|
||||||
bits = []
|
bits = []
|
||||||
|
if cat_match:
|
||||||
|
bits.append("Katalog-Pick ✓")
|
||||||
if role == "vision":
|
if role == "vision":
|
||||||
bits.append("Vision ✓")
|
bits.append("Vision ✓")
|
||||||
if role == "coder" and caps.get("coder"):
|
if role == "coder" and caps.get("coder"):
|
||||||
bits.append("Coder ✓")
|
bits.append("Coder ✓")
|
||||||
if role == "hermes":
|
if role == "hermes":
|
||||||
bits.append("Tools ✓" if caps.get("tools") != "no" else "ohne Tools")
|
bits.append("Hermes" if "hermes" in (name or "").lower()
|
||||||
|
else ("Tools ✓" if caps.get("tools") != "no" else "ohne Tools"))
|
||||||
if caps.get("moe"):
|
if caps.get("moe"):
|
||||||
bits.append("MoE")
|
bits.append("MoE")
|
||||||
bits.append(f"{fit['text']}, ~{fit['tps']:.0f} t/s")
|
bits.append(f"{fit['text']}, ~{fit['tps']:.0f} t/s")
|
||||||
@@ -84,13 +117,14 @@ def recommend_for_role(role: str) -> dict:
|
|||||||
fit = evaluate_fit(params, quant, ctx, ram, name=m["name"])
|
fit = evaluate_fit(params, quant, ctx, ram, name=m["name"])
|
||||||
incomplete = bool(m.get("incomplete"))
|
incomplete = bool(m.get("incomplete"))
|
||||||
fits = (fit["level"] != "too_tight") and not incomplete
|
fits = (fit["level"] != "too_tight") and not incomplete
|
||||||
suit = _suitability(role, caps, params, m["name"])
|
tps = fit["tps"] or 0
|
||||||
|
suit = _capability_suit(role, caps, m["name"])
|
||||||
|
cat_match = _catalog_role_match(role, m["name"])
|
||||||
suitable = suit >= 0.5 and fits
|
suitable = suit >= 0.5 and fits
|
||||||
|
|
||||||
fit_term = {"perfect": 1.0, "marginal": 0.3}.get(fit["level"], -2.0)
|
fit_term = {"perfect": 1.0, "marginal": 0.3}.get(fit["level"], -2.0)
|
||||||
score = (2.0 * suit) + fit_term \
|
# Eignung dominiert (×2), rollengerechte Größe/Tempo (_pref), Katalog-Anker, dann Fit.
|
||||||
+ min((fit["tps"] or 0) / 80.0, 1.0) * 0.5 \
|
score = (2.0 * suit) + _pref(role, params, tps) + (0.6 if cat_match else 0.0) + fit_term
|
||||||
+ min(params / 120.0, 1.0) * 0.5
|
|
||||||
if not fits:
|
if not fits:
|
||||||
score -= 5.0
|
score -= 5.0
|
||||||
|
|
||||||
@@ -99,7 +133,7 @@ def recommend_for_role(role: str) -> dict:
|
|||||||
"params_b": round(params, 1), "quant": quant,
|
"params_b": round(params, 1), "quant": quant,
|
||||||
"fit": fit, "suitable": suitable, "incomplete": incomplete,
|
"fit": fit, "suitable": suitable, "incomplete": incomplete,
|
||||||
"score": round(score, 3),
|
"score": round(score, 3),
|
||||||
"reason": _reason(role, caps, fit, suit, fits, incomplete),
|
"reason": _reason(role, caps, m["name"], fit, fits, incomplete, cat_match),
|
||||||
})
|
})
|
||||||
|
|
||||||
out.sort(key=lambda x: -x["score"])
|
out.sort(key=lambda x: -x["score"])
|
||||||
|
|||||||
Reference in New Issue
Block a user