diff --git a/recipes.py b/recipes.py index 07b33fa..91a17d3 100644 --- a/recipes.py +++ b/recipes.py @@ -1,81 +1,82 @@ """ -Kuratierte Use-Case-"Setups" (Stacks) fuers Cookbook 2.0. +Kuratierte Use-Case-„Setups" (Stacks) fuers Cookbook 2.0. Stand: Juni 2026. -Idee: Der Nutzer waehlt *wofuer* er es braucht — nicht *welches Modell*. Jedes Setup ist eine -Kombination aus Rollen (z.B. Haupt-Coder + schneller Coder + Vision). Hardware-Fit + optimaler -Kontext werden zur Laufzeit aus der echten RAM-Groesse berechnet (hw_math), nicht hier hartkodiert. - -Daten, kein Code — bewusst getrennt (KISS/SoC). Repo/Datei sind unsloth-GGUFs auf HuggingFace. +Daten, kein Code (KISS/SoC). Pro Modell nur Repo + Groesse/Quant — die konkrete GGUF-Datei wird +beim Installieren dynamisch aus dem Repo aufgeloest (robust gegen Datei-Umbenennungen). Hardware-Fit ++ optimaler Kontext kommen zur Laufzeit aus hw_math. """ +# Aktuelle, lokal gut laufende GGUF-Repos (Juni 2026). Bevorzugt unsloth (zuverlaessige Quants). RECIPES = [ { "id": "coding", "title": "Coden & Programmieren", "icon": "code", - "desc": "Ein starker Haupt-Coder, ein flinker für Autovervollständigung und Vision für Screenshots.", + "desc": "Aktueller Top-Coder (MoE, nur 3B aktiv → schnell), ein flinker Helfer und Vision für Screenshots.", "models": [ - {"role": "coder", "name": "Qwen2.5 Coder 32B", "repo": "unsloth/Qwen2.5-Coder-32B-Instruct-GGUF", - "file": "Qwen2.5-Coder-32B-Instruct-Q4_K_M.gguf", "params_b": 32, "quant": "Q4_K_M", - "why": "Hauptmodell — versteht große Codebasen und komplexe Aufgaben."}, - {"role": "coder-fast", "name": "Qwen2.5 Coder 7B", "repo": "unsloth/Qwen2.5-Coder-7B-Instruct-GGUF", - "file": "Qwen2.5-Coder-7B-Instruct-Q4_K_M.gguf", "params_b": 7, "quant": "Q4_K_M", - "why": "Schnelle Autovervollständigung & einfache Edits."}, - {"role": "vision", "name": "Llama 3.2 Vision 11B", "repo": "unsloth/Llama-3.2-11B-Vision-Instruct-GGUF", - "file": "Llama-3.2-11B-Vision-Instruct-Q4_K_M.gguf", "params_b": 11, "quant": "Q4_K_M", - "why": "Liest Screenshots & Fehlerbilder für die Analyse."}, + {"role": "coder", "name": "Qwen3-Coder 30B-A3B", "repo": "unsloth/Qwen3-Coder-30B-A3B-Instruct-GGUF", + "params_b": 30, "quant": "Q4_K_M", "why": "Aktuelles Top-Coder-Modell — versteht große Codebasen, läuft dank MoE flott."}, + {"role": "coder-fast", "name": "Qwen3 8B", "repo": "unsloth/Qwen3-8B-GGUF", + "params_b": 8, "quant": "Q4_K_M", "why": "Schneller Helfer für einfache Edits & Autovervollständigung."}, + {"role": "vision", "name": "Qwen2.5-VL 7B", "repo": "unsloth/Qwen2.5-VL-7B-Instruct-GGUF", + "params_b": 7, "quant": "Q4_K_M", "why": "Liest Screenshots & Fehlerbilder für die Analyse."}, ], }, { "id": "vision", "title": "Bilder verstehen", "icon": "eye", "desc": "Ein multimodales Modell, das Bilder und Text gemeinsam versteht.", "models": [ - {"role": "vision", "name": "Llama 3.2 Vision 11B", "repo": "unsloth/Llama-3.2-11B-Vision-Instruct-GGUF", - "file": "Llama-3.2-11B-Vision-Instruct-Q4_K_M.gguf", "params_b": 11, "quant": "Q4_K_M", - "why": "Beschreibt Bilder, liest Diagramme, analysiert Screenshots."}, + {"role": "vision", "name": "Qwen2.5-VL 7B", "repo": "unsloth/Qwen2.5-VL-7B-Instruct-GGUF", + "params_b": 7, "quant": "Q4_K_M", "why": "Beschreibt Bilder, liest Diagramme, analysiert Screenshots."}, ], }, { "id": "chat", "title": "Allrounder & Chat", "icon": "compass", - "desc": "Ein vielseitiges Modell für alltägliche Fragen, Texte und Brainstorming.", + "desc": "Vielseitige Modelle für Alltag, Texte und Brainstorming.", "models": [ - {"role": "scout", "name": "Qwen2.5 7B", "repo": "unsloth/Qwen2.5-7B-Instruct-GGUF", - "file": "Qwen2.5-7B-Instruct-Q4_K_M.gguf", "params_b": 7, "quant": "Q4_K_M", - "why": "Schneller, kluger Allrounder — guter Standard."}, - {"role": "scout-pro", "name": "Qwen2.5 14B", "repo": "unsloth/Qwen2.5-14B-Instruct-GGUF", - "file": "Qwen2.5-14B-Instruct-Q4_K_M.gguf", "params_b": 14, "quant": "Q4_K_M", - "why": "Mehr Tiefe, wenn die Antworten besser sein sollen."}, + {"role": "scout", "name": "Qwen3 8B", "repo": "unsloth/Qwen3-8B-GGUF", + "params_b": 8, "quant": "Q4_K_M", "why": "Schneller, kluger Allrounder — guter Standard."}, + {"role": "scout-pro", "name": "Qwen3 30B-A3B", "repo": "unsloth/Qwen3-30B-A3B-Instruct-2507-GGUF", + "params_b": 30, "quant": "Q4_K_M", "why": "Mehr Tiefe (MoE), wenn die Antworten besser sein sollen."}, ], }, { "id": "longdoc", "title": "Lange Dokumente", "icon": "file", "desc": "Großes Modell mit großem Kontext — für lange Texte, Verträge, Bücher.", "models": [ - {"role": "reader", "name": "Qwen2.5 14B", "repo": "unsloth/Qwen2.5-14B-Instruct-GGUF", - "file": "Qwen2.5-14B-Instruct-Q4_K_M.gguf", "params_b": 14, "quant": "Q4_K_M", - "why": "Solide Qualität bei großem Kontextfenster (auto-optimiert)."}, + {"role": "reader", "name": "Qwen3 30B-A3B", "repo": "unsloth/Qwen3-30B-A3B-Instruct-2507-GGUF", + "params_b": 30, "quant": "Q4_K_M", "why": "Starke Qualität bei großem Kontextfenster (auto-optimiert)."}, ], }, { "id": "agents", "title": "Agenten & Tool-Use", "icon": "layers", - "desc": "Modelle, die gut mit Werkzeugen/Funktionen umgehen — für autonome Agenten.", + "desc": "Modelle, die gut mit Werkzeugen/Funktionen umgehen — für autonome Agenten (MCP).", "models": [ - {"role": "agent", "name": "Qwen2.5 32B", "repo": "unsloth/Qwen2.5-32B-Instruct-GGUF", - "file": "Qwen2.5-32B-Instruct-Q4_K_M.gguf", "params_b": 32, "quant": "Q4_K_M", - "why": "Stark im Function-Calling und mehrstufigem Denken."}, - {"role": "agent-fast", "name": "Qwen2.5 7B", "repo": "unsloth/Qwen2.5-7B-Instruct-GGUF", - "file": "Qwen2.5-7B-Instruct-Q4_K_M.gguf", "params_b": 7, "quant": "Q4_K_M", - "why": "Schneller Helfer für einfache Tool-Schritte."}, + {"role": "agent", "name": "Qwen3 30B-A3B", "repo": "unsloth/Qwen3-30B-A3B-Instruct-2507-GGUF", + "params_b": 30, "quant": "Q4_K_M", "why": "Stark im Function-Calling und mehrstufigem Denken."}, + {"role": "agent-fast", "name": "Qwen3 8B", "repo": "unsloth/Qwen3-8B-GGUF", + "params_b": 8, "quant": "Q4_K_M", "why": "Schneller Helfer für einfache Tool-Schritte."}, ], }, { "id": "fast", "title": "Schnell & sparsam", "icon": "bolt", "desc": "Kleine, flinke Modelle — ideal bei wenig Speicher oder für schnelle Antworten.", "models": [ - {"role": "mini", "name": "Qwen2.5 3B", "repo": "unsloth/Qwen2.5-3B-Instruct-GGUF", - "file": "Qwen2.5-3B-Instruct-Q4_K_M.gguf", "params_b": 3, "quant": "Q4_K_M", - "why": "Winzig & sehr schnell für einfache Aufgaben."}, - {"role": "coder-fast", "name": "Qwen2.5 Coder 7B", "repo": "unsloth/Qwen2.5-Coder-7B-Instruct-GGUF", - "file": "Qwen2.5-Coder-7B-Instruct-Q4_K_M.gguf", "params_b": 7, "quant": "Q4_K_M", - "why": "Flinker Coder, falls es doch mal Code sein soll."}, + {"role": "mini", "name": "Qwen3 4B", "repo": "Qwen/Qwen3-4B-GGUF", + "params_b": 4, "quant": "Q4_K_M", "why": "Winzig & sehr schnell für einfache Aufgaben."}, + {"role": "scout", "name": "Qwen3 8B", "repo": "unsloth/Qwen3-8B-GGUF", + "params_b": 8, "quant": "Q4_K_M", "why": "Etwas mehr Können, immer noch flott."}, ], }, ] + +# Upgrade-Map (Phase 3.2): wenn der Nutzer ein „altes" Modell hat, schlagen wir das neue vor. +UPGRADES = [ + {"match": ["qwen2.5-coder", "qwen2_5-coder"], "name": "Qwen3-Coder 30B-A3B", + "repo": "unsloth/Qwen3-Coder-30B-A3B-Instruct-GGUF", "params_b": 30, "quant": "Q4_K_M", + "why": "Nachfolger deines Coders: MoE mit nur 3B aktiven Parametern → schneller und stärker auf SWE-bench."}, + {"match": ["qwen2.5-7b", "qwen2_5-7b", "qwen2.5-instruct"], "name": "Qwen3 8B", + "repo": "unsloth/Qwen3-8B-GGUF", "params_b": 8, "quant": "Q4_K_M", + "why": "Neuere Generation deines Allrounders — besser im Reasoning und bei Tool-Use."}, + {"match": ["llama-3.2-11b-vision", "llama3.2", "llama-3.2-vision"], "name": "Qwen2.5-VL 7B", + "repo": "unsloth/Qwen2.5-VL-7B-Instruct-GGUF", "params_b": 7, "quant": "Q4_K_M", + "why": "Modernes Vision-Modell — kleiner und meist treffsicherer beim Bildverständnis."}, +] diff --git a/routers/cookbook.py b/routers/cookbook.py index 4cbcc7d..1522bb1 100644 --- a/routers/cookbook.py +++ b/routers/cookbook.py @@ -32,6 +32,7 @@ class EvaluateRequest(BaseModel): class InstallRecipeReq(BaseModel): recipe_id: str + hf_token: str | None = None def extract_params_b(repo_id: str) -> float: """Extrahiert die Parametergröße (in Milliarden) aus dem Repo-Namen.""" @@ -138,20 +139,41 @@ def install_recipe(req: InstallRecipeReq): if not recipe: raise HTTPException(404, "Setup nicht gefunden.") ram_gb = psutil.virtual_memory().total / (1024 ** 3) + env = {"HF_XET_HIGH_PERFORMANCE": "1"} + if req.hf_token: + env["HF_TOKEN"] = req.hf_token cfg = read_config() job_ids = [] for m in recipe["models"]: + file = _pick_gguf(m["repo"], m.get("quant", "Q4_K_M")) + if not file: + continue # kein GGUF im Repo gefunden -> Modell ueberspringen (Rest installiert trotzdem) target = MODELS_DIR / m["repo"].split("/")[-1] target.mkdir(parents=True, exist_ok=True) - args = ["hf", "download", m["repo"], m["file"], "--local-dir", str(target)] - jid = start_job(args, f"download {m['name']}", env={"HF_XET_HIGH_PERFORMANCE": "1"}) - JOBS[jid]["result_path"] = str(target / m["file"]) + args = ["hf", "download", m["repo"], file, "--local-dir", str(target)] + jid = start_job(args, f"download {m['name']}", env=env) + JOBS[jid]["result_path"] = str(target / file) job_ids.append(jid) # Eintrag jetzt schon schreiben — optimaler Kontext, aber gedeckelt fuer schnellen Erststart. ctx = min(max_ctx_for(m["params_b"], m["quant"], ram_gb), 32768) - path = str(target / m["file"]) + path = str(target / file) cmd = CMD_TEMPLATE.replace("{model}", path).replace("{ctx}", str(ctx)) cfg["models"][m["role"]] = {"cmd": LiteralScalarString(cmd + "\n"), "ttl": DEFAULT_TTL} write_config(cfg) - return {"job_ids": job_ids, "count": len(recipe["models"])} + return {"job_ids": job_ids, "count": len(job_ids)} + + +def _pick_gguf(repo: str, quant: str = "Q4_K_M") -> str | None: + """Beste GGUF-Datei eines Repos auflösen: bevorzugt gewünschten Quant, keine Split-Teile.""" + try: + with httpx.Client(timeout=10.0) as c: + tree = c.get(f"https://huggingface.co/api/models/{repo}/tree/main").json() + except Exception: # noqa: BLE001 + return None + ggufs = [f["path"] for f in tree if isinstance(f, dict) and str(f.get("path", "")).endswith(".gguf")] + if not ggufs: + return None + pref = [g for g in ggufs if quant.lower() in g.lower() and "-of-" not in g] + nosplit = [g for g in ggufs if "-of-" not in g] + return (pref or nosplit or ggufs)[0] diff --git a/routers/models.py b/routers/models.py index 364d908..2a65fa0 100644 --- a/routers/models.py +++ b/routers/models.py @@ -31,6 +31,7 @@ class DownloadReq(BaseModel): repo: str file: str subdir: str | None = None + hf_token: str | None = None class RegisterReq(BaseModel): @@ -139,8 +140,10 @@ def download(req: DownloadReq): target = MODELS_DIR / sub target.mkdir(parents=True, exist_ok=True) args = ["hf", "download", req.repo, req.file, "--local-dir", str(target)] - job_id = start_job(args, f"download {req.repo}/{req.file}", - env={"HF_XET_HIGH_PERFORMANCE": "1"}) + env = {"HF_XET_HIGH_PERFORMANCE": "1"} + if req.hf_token: + env["HF_TOKEN"] = req.hf_token + job_id = start_job(args, f"download {req.repo}/{req.file}", env=env) JOBS[job_id]["result_path"] = str(target / req.file) return {"job_id": job_id, "expected_path": str(target / req.file)} diff --git a/static/index.html b/static/index.html index d5a99ae..c6e8125 100644 --- a/static/index.html +++ b/static/index.html @@ -100,6 +100,11 @@