diff --git a/compose.yaml b/compose.yaml index 2ddd3cf..94c0d73 100644 --- a/compose.yaml +++ b/compose.yaml @@ -470,7 +470,7 @@ services: - ./platform/openwebui/theme/loader.js:/app/build/static/loader.js:ro environment: WEBUI_SECRET_KEY: "${WEBUI_SECRET_KEY:?WEBUI_SECRET_KEY is required}" - DEFAULT_MODELS: qwen-medium + DEFAULT_MODELS: mikeai-medium OLLAMA_BASE_URL: "" OPENAI_API_BASE_URLS: http://router:8081/v1 OPENAI_API_KEYS: "${ROUTER_API_KEY:?ROUTER_API_KEY is required}" diff --git a/docs/DISASTER_RECOVERY.md b/docs/DISASTER_RECOVERY.md index 9b0f468..1e554d1 100644 --- a/docs/DISASTER_RECOVERY.md +++ b/docs/DISASTER_RECOVERY.md @@ -22,8 +22,9 @@ laufen, sondern alle fachlichen Funktionen geprüft wurden. - [ ] llama.cpp entspricht dem festgelegten Commit - [ ] Modelldateien stimmen mit SHA256 überein - [ ] Fast startet mit 76.800 Kontext -- [ ] Medium startet mit 94.208 Kontext -- [ ] Long startet mit 131.072 Kontext +- [ ] Medium startet mit 160.000 Kontext und ist Standard +- [ ] Large startet mit 192.000 Kontext +- [ ] Ultra startet mit 262.144 Kontext und bleibt text-only - [ ] GPU-/CPU-Verteilung entspricht den Profilen - [ ] Fast erreicht den festgelegten Geschwindigkeitstoleranzbereich - [ ] MTP funktioniert und verursacht keine Qualitätsregression @@ -36,7 +37,7 @@ laufen, sondern alle fachlichen Funktionen geprüft wurden. - [ ] geschützte Endpunkte liefern ohne Key 401 - [ ] Router-Key erscheint weder im Upstream noch im Journal - [ ] `/status` meldet den richtigen Upstream -- [ ] `/v1/models` liefert drei virtuelle Modelle +- [ ] `/v1/models` liefert vier virtuelle Modelle - [ ] `/fast`, `/medium`, `/large` und `/ultra` wechseln zuverlässig - [ ] automatischer Wechsel über virtuellen Modellnamen funktioniert - [ ] paralleler Wechsel wird sauber gesperrt @@ -49,6 +50,11 @@ laufen, sondern alle fachlichen Funktionen geprüft wurden. ## Phase D – Web und MCP +- [ ] OpenWebUI zeigt nur die vier MikeAI-Arbeitsbereichsmodelle +- [ ] rohe `qwen-*`-Routermodelle sind ausgeblendet +- [ ] Medium ist die gespeicherte Standardauswahl +- [ ] Filter und Quick Actions sind allen vier Presets zugeordnet + - [ ] SearXNG und TinySearch gesund - [ ] Websuche liefert kompakte, quellengebundene Ergebnisse - [ ] GitHub- und Hugging-Face-Routing geprüft @@ -86,7 +92,8 @@ laufen, sondern alle fachlichen Funktionen geprüft wurden. ## Phase G – Fachlicher Benchmark -Der gespeicherte Standardbenchmark wird mindestens mit Fast und Long ausgeführt: +Der gespeicherte Standardbenchmark wird mindestens mit Fast und Medium sowie +für Kontextgrenzen zusätzlich mit Large und Ultra ausgeführt: - Home-Assistant-Automatisierung analysieren - Logs lesen und Fehlerursache begründen diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md index 848441a..bcf3efb 100644 --- a/docs/INSTALLATION.md +++ b/docs/INSTALLATION.md @@ -95,6 +95,18 @@ Zusätzlich prüfen: Uni-LAN sieht keine KI-Ports; Heimnetz erreicht beide; gestopptes WireGuard lässt KI-Container nicht ins Internet; jeder Profilwechsel startet exakt einen llama-Container; Text, Tool Call, Bild und Sprachausgabe funktionieren. +Nach dem ersten Anlegen des OpenWebUI-Administrators werden Filter, Quick +Actions und die vier Arbeitsbereichsmodelle reproduzierbar eingespielt: + +```bash +sudo /opt/mike-ai/stack/platform/openwebui/install-filters.sh +sudo /opt/mike-ai/stack/platform/openwebui/install-models.sh +``` + +Danach sind nur die vier benannten MikeAI-Presets sichtbar; die rohen +Router-Aliase sind ausgeblendet und Medium ist die Standardauswahl. Beide +Skripte sichern die OpenWebUI-Datenbank vor jeder Änderung. + ## Werkzeug-Container Der Installer startet Websuche automatisch in einem privaten Docker-Netz. @@ -122,11 +134,11 @@ Kein MCP-Port wird auf dem Host veröffentlicht. Externe Clients wie Hermes benötigen später den authentifizierten WireGuard-Gateway und dürfen nicht direkt auf das interne Werkzeugnetz zugreifen. -Die öffentliche Standardkonfiguration nutzt `UD-IQ4_XS`. Das bislang schnellste -Referenzprofil nutzt dagegen die lokal vorhandene `IQ4-MIX`-Datei. Für eine -bitgenaue Migration diese Datei anhand der in `CURRENT_REFERENCE.md` -dokumentierten SHA256 in das Modellverzeichnis kopieren und die drei -`*_MODEL_FILE`-Werte anpassen. Der Installer löscht vorhandene Modelle nicht. +Die Standardkonfiguration lädt IQ4-MIX für Fast und IQ4_XS Pure für Medium, +Large und Ultra aus den dokumentierten Hugging-Face-Repositories. URLs, +Dateinamen und SHA256 stehen vollständig in `config/install.env.example`. +Der Installer lädt jede identische Datei nur einmal und löscht vorhandene +Modelle nicht. Open-WebUI-Daten liegen in einem Docker-Volume und müssen separat gesichert werden. Geheimnisse und Chatdaten gehören nie in Git. diff --git a/docs/OPERATIONS.md b/docs/OPERATIONS.md index e0ba5be..de5fd38 100644 --- a/docs/OPERATIONS.md +++ b/docs/OPERATIONS.md @@ -29,6 +29,22 @@ einer vorherigen Datenbanksicherung installiert beziehungsweise aktualisiert: sudo /opt/mike-ai/stack/platform/openwebui/install-filters.sh ``` +Die sichtbaren Arbeitsbereichsmodelle und die versteckten Router-Aliase werden +separat und ebenfalls mit vorheriger Datenbanksicherung installiert: + +```bash +sudo /opt/mike-ai/stack/platform/openwebui/install-models.sh +``` + +Dadurch erscheinen ausschließlich `MikeAI · Fast`, `MikeAI · Medium`, +`MikeAI · Large` und `MikeAI · Ultra`. Medium ist die Standardauswahl. Alle +vier erhalten die geprüften Filter und Quick Actions sowie Piper-Stimme +`alloy`. Vision ist nur bei Fast, Medium und Large aktiviert; Ultra bleibt +bewusst text-only. Bildgenerierung wird erst als Fähigkeit freigeschaltet, +wenn ein echter Generator-Worker im Stack aktiv ist. Websuche und Codewerkzeuge +bleiben verfügbar, werden aber nicht automatisch jeder Nachricht beigelegt, +damit Werkzeugschemas den Kontext nicht unnötig füllen. + Bei mehreren Administratoren muss der gewünschte Eigentümer explizit über `OPENWEBUI_FILTER_OWNER_ID` gesetzt werden. Das Skript liest oder verändert keine Chats. Es deaktiviert zugleich global die automatisch erzeugten diff --git a/docs/STANDARD_PROFILE_MATRIX.md b/docs/STANDARD_PROFILE_MATRIX.md index 85e19cd..e177cfd 100644 --- a/docs/STANDARD_PROFILE_MATRIX.md +++ b/docs/STANDARD_PROFILE_MATRIX.md @@ -15,7 +15,10 @@ und einer bewussten Aktualisierung dieser Datei. - Medium ist nach Neuinstallation und bewusstem Plattformstart das aktive Standardprofil. -- Open WebUI erhält `qwen-medium` als Standardmodell. +- Open WebUI erhält `mikeai-medium` als Standardmodell. +- Im OpenWebUI-Arbeitsbereich ist dieses auf `qwen-medium` basierende Preset + `mikeai-medium` die sichtbare Standardauswahl; die vier rohen `qwen-*`-Aliase + bleiben ausgeblendet. - Ein explizit gewähltes anderes Modell löst den zugehörigen Containerwechsel aus; es ist immer nur ein Inferenzcontainer aktiv. - Ultra reserviert den verfügbaren Speicher für nativen 256K-Textkontext und @@ -30,4 +33,3 @@ und einer bewussten Aktualisierung dieser Datei. - Large 192K: Pure 86:14 mit MTP3, 75,28 Tok/s. - Ultra 256K: Pure 80:20 mit MTP2, 68,19 Tok/s; 220.190 Tokens erfolgreich verarbeitet und Sentinel korrekt wiedergefunden. - diff --git a/platform/openwebui/install-models.sh b/platform/openwebui/install-models.sh new file mode 100644 index 0000000..d8bd427 --- /dev/null +++ b/platform/openwebui/install-models.sh @@ -0,0 +1,218 @@ +#!/usr/bin/env bash +set -Eeuo pipefail +umask 077 + +CONTAINER=${OPENWEBUI_CONTAINER:-mike-ai-open-webui} +VOLUME=${OPENWEBUI_VOLUME:-mike-ai_open-webui-data} + +die() { printf 'FEHLER: %s\n' "$*" >&2; exit 1; } +[[ $EUID -eq 0 ]] || die "Bitte als root ausführen." + +volume_path=$(docker volume inspect -f '{{.Mountpoint}}' "$VOLUME") +db=$volume_path/webui.db +[[ -s $db ]] || die "OpenWebUI-Datenbank fehlt: $db" + +was_running=false +if [[ $(docker inspect -f '{{.State.Running}}' "$CONTAINER" 2>/dev/null || true) == true ]]; then + was_running=true + docker stop "$CONTAINER" >/dev/null +fi +restart_on_exit() { + if [[ $was_running == true ]]; then + docker start "$CONTAINER" >/dev/null 2>&1 || true + fi +} +trap restart_on_exit EXIT + +stamp=$(date +%Y%m%d-%H%M%S) +backup=$volume_path/webui.db.before-model-install-$stamp +cp -a "$db" "$backup" +rm -f "$volume_path/webui.db-wal" "$volume_path/webui.db-shm" + +python3 - "$db" "${OPENWEBUI_MODEL_OWNER_ID:-}" <<'PY' +import json +import sqlite3 +import sys +import time + +db, requested_owner = sys.argv[1:] +con = sqlite3.connect(db) +columns = {row[1] for row in con.execute("pragma table_info(model)")} +required = { + "id", "user_id", "base_model_id", "name", "params", "meta", + "is_active", "updated_at", "created_at", +} +if not required <= columns: + raise SystemExit("Unbekanntes OpenWebUI-Modellschema; keine Änderung vorgenommen.") + +owner = requested_owner +if not owner: + row = con.execute( + "select user_id from model where id in " + "('mikeai-fast','mikeai-medium','mikeai-large','mikeai-ultra'," + "'mikeai---fast-72k') order by updated_at desc limit 1" + ).fetchone() + if row: + owner = row[0] +if not owner: + admins = con.execute("select id from user where role='admin'").fetchall() + if len(admins) != 1: + raise SystemExit( + "Modelleigentümer ist nicht eindeutig; OPENWEBUI_MODEL_OWNER_ID setzen." + ) + owner = admins[0][0] + +now = int(time.time()) +filter_ids = [ + "reasoning_default_off", + "thinking", + "stability_guard", + "secret_redaction", + "local_performance_metrics", +] + +def capabilities(vision: bool) -> dict: + return { + "file_context": True, + "vision": vision, + "file_upload": True, + "web_search": True, + "image_generation": False, + "code_interpreter": True, + "terminal": True, + "citations": True, + "status_updates": True, + "memory": True, + "builtin_tools": True, + } + +profiles = [ + { + "id": "mikeai-fast", + "base": "qwen-fast", + "name": "MikeAI · Fast · 76K", + "description": ( + "Schnelles Alltags- und Agentenprofil: Qwen3.8-27B IQ4-MIX, " + "76.800 Token, RTX 5080, MTP2 und Bildanalyse." + ), + "vision": True, + "tags": ["MikeAI", "Schnell", "Vision"], + }, + { + "id": "mikeai-medium", + "base": "qwen-medium", + "name": "MikeAI · Medium · 160K · Standard", + "description": ( + "Verbindliches Standardprofil: Qwen3.8-27B IQ4_XS Pure, " + "160.000 Token, RTX 5080 + RTX 3060 (90:10), MTP3 und Bildanalyse." + ), + "vision": True, + "tags": ["MikeAI", "Standard", "Vision"], + }, + { + "id": "mikeai-large", + "base": "qwen-large", + "name": "MikeAI · Large · 192K", + "description": ( + "Großes Agenten- und MCP-Profil: Qwen3.8-27B IQ4_XS Pure, " + "192.000 Token, RTX 5080 + RTX 3060 (86:14), MTP3 und Bildanalyse." + ), + "vision": True, + "tags": ["MikeAI", "Großer Kontext", "Vision"], + }, + { + "id": "mikeai-ultra", + "base": "qwen-ultra", + "name": "MikeAI · Ultra · 256K · Text", + "description": ( + "Maximaler Textkontext: Qwen3.8-27B IQ4_XS Pure, 262.144 Token, " + "RTX 5080 + RTX 3060 (80:20), MTP2; bewusst ohne Bildanalyse." + ), + "vision": False, + "tags": ["MikeAI", "Maximaler Kontext", "Nur Text"], + }, +] + +params = { + "temperature": 0.2, + "top_p": 0.8, + "top_k": 20, + "max_tokens": 32768, +} + +def upsert(model_id, base_model_id, name, model_params, meta, active=True): + existing = con.execute( + "select created_at from model where id=?", (model_id,) + ).fetchone() + created_at = existing[0] if existing else now + con.execute( + """ + insert into model + (id,user_id,base_model_id,name,params,meta,is_active,updated_at,created_at) + values (?,?,?,?,?,?,?,?,?) + on conflict(id) do update set + user_id=excluded.user_id, + base_model_id=excluded.base_model_id, + name=excluded.name, + params=excluded.params, + meta=excluded.meta, + is_active=excluded.is_active, + updated_at=excluded.updated_at + """, + ( + model_id, owner, base_model_id, name, + json.dumps(model_params, ensure_ascii=False), + json.dumps(meta, ensure_ascii=False), + active, now, created_at, + ), + ) + +with con: + # Hide the raw router aliases while keeping them resolvable as bases for + # the user-facing workspace presets. + for raw_id in ("qwen-fast", "qwen-medium", "qwen-large", "qwen-ultra"): + upsert( + raw_id, None, raw_id, {}, + { + "description": "Interner Router-Alias; bitte das zugehörige MikeAI-Profil verwenden.", + "hidden": True, + }, + ) + + for profile in profiles: + meta = { + "profile_image_url": "/static/favicon.png", + "description": profile["description"], + "capabilities": capabilities(profile["vision"]), + "knowledge": None, + "suggestion_prompts": None, + "tags": [{"name": tag} for tag in profile["tags"]], + # Features remain available but are not forced on every request; + # this avoids needlessly injecting large tool schemas. + "defaultFeatureIds": [], + "filterIds": filter_ids, + "actionIds": ["quick_actions"], + "tts": {"voice": "alloy"}, + "hidden": False, + } + upsert( + profile["id"], profile["base"], profile["name"], params, meta + ) + + # Exact legacy entries from the experimental 72K/Long setup. The database + # backup above makes the cleanup fully recoverable. + con.execute( + "delete from model where id in ('mikeai---fast-72k','qwen-long')" + ) + con.execute( + """ + insert into config (key,value,updated_at) values (?,?,?) + on conflict(key) do update set value=excluded.value, updated_at=excluded.updated_at + """, + ("ui.default_models", json.dumps("mikeai-medium"), now), + ) + +print("OpenWebUI-Modelle installiert: Fast, Medium (Standard), Large, Ultra") +PY + +printf 'Datenbanksicherung: %s\n' "$backup"