feat(admin): Dropdown „Zentrales KI-Modell" — erprobte Presets ruck-zuck umstellen
Neue Karte in Admin › System › Status: ein Dropdown kuratierter, erprobter
Modelle, das Provider + Modell in einem Klick als Laufzeit-Override umstellt
(wirkt sofort, kein Neustart).
- Presets in config/models.yaml (Label, optional note, set:{key:value} aus
RUNTIME_SETTABLE, default:true = Config-Standard). Loader app/model_presets.py
(nur erlaubte LLM-Keys: default_llm_provider, openrouter_llm_model,
local_llm_model).
- Endpunkte: GET /api/admin/models (Presets + aktuell aktives LLM + active_index),
POST /api/admin/models/select (Default -> Overrides löschen, sonst Override-Keys
setzen) — nutzt set_/delete_config_override + invalidate_cache, mit Audit-Log.
- Frontend: Karte zeigt Dropdown + „Aktiv: <provider> · <modell>"; Auswahl wirkt
sofort und lädt den Status neu. Bump app.js v=52 -> v=53.
- Startliste: Mistral Small 24B (Standard), DeepSeek V4 Flash/Pro, Ollama Gemma 3.
Tests: Laden/Default-aktiv/Select-setzt-Override/Default-löscht/404/Auth.
263 Offline-Tests grün. Live via Headless-Chrome verifiziert (Auswahl ->
Override gesetzt, Provider-Karte spiegelt das Modell).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
2ccfd7f6d6
commit
18ad442c94
6 changed files with 243 additions and 2 deletions
80
tests/test_model_presets.py
Normal file
80
tests/test_model_presets.py
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
"""Erprobte KI-Modell-Presets (Admin-Dropdown).
|
||||
|
||||
Getestet: Laden der Presets, GET /api/admin/models (aktives Preset),
|
||||
POST /api/admin/models/select (Override setzen bzw. Default = Overrides löschen).
|
||||
"""
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
import app.dependencies as deps
|
||||
from app.config import settings
|
||||
from app.main import app
|
||||
from app.model_presets import LLM_KEYS, load_presets
|
||||
from app.runtime_config import invalidate_cache
|
||||
|
||||
client = TestClient(app)
|
||||
ADM_HDR = {"X-Admin-Key": "test-admin-key"}
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _setup(monkeypatch):
|
||||
monkeypatch.setattr(settings, "admin_api_key", "test-admin-key")
|
||||
monkeypatch.setattr(settings, "auth_enabled", False)
|
||||
invalidate_cache()
|
||||
yield
|
||||
# Aufräumen: alle LLM-Overrides entfernen, Cache leeren.
|
||||
store = deps.get_store()
|
||||
for k in LLM_KEYS:
|
||||
store.delete_config_override(k)
|
||||
invalidate_cache()
|
||||
|
||||
|
||||
def test_load_presets_has_default_and_sets():
|
||||
presets = load_presets()
|
||||
assert presets, "config/models.yaml liefert Presets"
|
||||
assert any(p["is_default"] for p in presets) # genau ein Standard
|
||||
ds = next(p for p in presets if "DeepSeek V4 Flash" in p["label"])
|
||||
assert ds["set"]["openrouter_llm_model"] == "deepseek/deepseek-v4-flash"
|
||||
assert ds["set"]["default_llm_provider"] == "openrouter"
|
||||
|
||||
|
||||
def test_get_models_default_active_without_overrides():
|
||||
r = client.get("/api/admin/models", headers=ADM_HDR)
|
||||
assert r.status_code == 200
|
||||
body = r.json()
|
||||
default_idx = next(p["index"] for p in body["presets"] if p["is_default"])
|
||||
assert body["active_index"] == default_idx # ohne Override ist der Standard aktiv
|
||||
|
||||
|
||||
def test_select_sets_override_and_marks_active():
|
||||
presets = client.get("/api/admin/models", headers=ADM_HDR).json()["presets"]
|
||||
ds_idx = next(p["index"] for p in presets if "DeepSeek V4 Flash" in p["label"])
|
||||
r = client.post("/api/admin/models/select", headers=ADM_HDR, json={"index": ds_idx})
|
||||
assert r.status_code == 200
|
||||
# Override ist gesetzt …
|
||||
ov = deps.get_store().get_config_overrides()
|
||||
assert ov.get("openrouter_llm_model") == "deepseek/deepseek-v4-flash"
|
||||
# … und das Preset wird als aktiv gemeldet.
|
||||
assert client.get("/api/admin/models", headers=ADM_HDR).json()["active_index"] == ds_idx
|
||||
|
||||
|
||||
def test_select_default_clears_overrides():
|
||||
presets = client.get("/api/admin/models", headers=ADM_HDR).json()["presets"]
|
||||
ds_idx = next(p["index"] for p in presets if "DeepSeek V4 Flash" in p["label"])
|
||||
def_idx = next(p["index"] for p in presets if p["is_default"])
|
||||
client.post("/api/admin/models/select", headers=ADM_HDR, json={"index": ds_idx})
|
||||
assert "openrouter_llm_model" in deps.get_store().get_config_overrides()
|
||||
# Standard wählen -> Overrides weg.
|
||||
client.post("/api/admin/models/select", headers=ADM_HDR, json={"index": def_idx})
|
||||
ov = deps.get_store().get_config_overrides()
|
||||
assert not any(k in ov for k in LLM_KEYS)
|
||||
|
||||
|
||||
def test_select_unknown_index_404():
|
||||
r = client.post("/api/admin/models/select", headers=ADM_HDR, json={"index": 999})
|
||||
assert r.status_code == 404
|
||||
|
||||
|
||||
def test_models_requires_admin():
|
||||
r = client.get("/api/admin/models") # ohne Admin-Key
|
||||
assert r.status_code in (401, 403)
|
||||
Loading…
Add table
Add a link
Reference in a new issue