diff --git a/backend/app.py b/backend/app.py index 9f94416..a964cb8 100644 --- a/backend/app.py +++ b/backend/app.py @@ -34,7 +34,7 @@ async def lifespan(app: FastAPI): """Hintergrund-Tasks an den App-Lebenszyklus binden: Re-Warm-Wächter fürs Agent-Hirn.""" task = asyncio.create_task(warmer.rewarm_loop()) if warmer.ENABLED else None if task: - log.info("Hirn-Re-Warm-Wächter aktiv (Intervall %ss, Modell '%s')", warmer.INTERVAL, warmer.MODEL) + log.info("Hirn-Re-Warm-Wächter aktiv (Intervall %ss, Hirn dynamisch aus Hermes-Config)", warmer.INTERVAL) try: yield finally: diff --git a/backend/services/warmer.py b/backend/services/warmer.py index 1d249db..9a36f2c 100644 --- a/backend/services/warmer.py +++ b/backend/services/warmer.py @@ -23,10 +23,31 @@ log = logging.getLogger(__name__) ENABLED = os.environ.get("MC_REWARM_ENABLED", "1") != "0" INTERVAL = int(os.environ.get("MC_REWARM_INTERVAL", "90")) # Sekunden zwischen Checks -MODEL = os.environ.get("MC_REWARM_MODEL", "hermes") # Alias der Rolle START_DELAY = int(os.environ.get("MC_REWARM_START_DELAY", "25")) +def _brain_model() -> str: + """Aktives Agent-Hirn = Hermes' model.default (sonst model.model). So wärmt der Wächter + immer das WIRKLICH genutzte Hirn (passt sich Hirn-Wechseln an). Override: MC_REWARM_MODEL.""" + forced = os.environ.get("MC_REWARM_MODEL") + if forced: + return forced + try: + from ruamel.yaml import YAML + from config import HERMES_HOME + p = HERMES_HOME / "config.yaml" + if p.exists(): + with p.open(encoding="utf-8") as f: + cfg = YAML().load(f) or {} + m = (cfg.get("model") or {}) if isinstance(cfg, dict) else {} + v = m.get("default") or m.get("model") + if v: + return str(v) + except Exception: + log.debug("rewarm: Hirn-Lookup fehlgeschlagen", exc_info=True) + return "fast" + + async def _running_empty() -> bool: async with httpx.AsyncClient(timeout=8.0) as c: r = await c.get(f"{LLAMA_SWAP_URL}/running") @@ -34,10 +55,10 @@ async def _running_empty() -> bool: return not (data.get("running") or []) -async def _warm() -> None: +async def _warm(model: str) -> None: async with httpx.AsyncClient(timeout=180.0) as c: await c.post(f"{LLAMA_SWAP_URL}/v1/chat/completions", json={ - "model": MODEL, "max_tokens": 1, + "model": model, "max_tokens": 1, "messages": [{"role": "user", "content": "ping"}], }) @@ -48,8 +69,9 @@ async def rewarm_loop() -> None: while True: try: if await _running_empty(): - log.info("rewarm: Box idle → Hirn '%s' wird vorgewärmt", MODEL) - await _warm() + model = _brain_model() + log.info("rewarm: Box idle → Hirn '%s' wird vorgewärmt", model) + await _warm(model) except Exception: log.debug("rewarm: Tick fehlgeschlagen", exc_info=True) await asyncio.sleep(INTERVAL) diff --git a/deploy/warmup.sh b/deploy/warmup.sh index 5ba1ae1..2dd7465 100644 --- a/deploy/warmup.sh +++ b/deploy/warmup.sh @@ -1,13 +1,13 @@ #!/usr/bin/env bash -# Lädt die "brains" (hermes/fast/vision) nach einem llama-swap-(Re)Start vor, -# damit die ERSTE Anfrage nicht kalt ist (llama-swap lädt sonst lazy bei Bedarf). +# Lädt das warme "brains"-Set nach einem llama-swap-(Re)Start vor, damit die ERSTE +# Anfrage nicht kalt ist (llama-swap lädt sonst lazy bei Bedarf). # Eingehängt als ExecStartPost=-/usr/local/bin/llama-swap-warmup.sh in llama-swap. # -# Selbst-detachend: der ExecStartPost-Aufruf kehrt sofort zurück (blockiert den -# Service-Start NICHT); die eigentliche Aufwärmung läuft entkoppelt im Hintergrund. +# `fast` = das Agent-Hirn (Qwen3.6-35B-A3B); Pingen eines brains-Gruppen-Mitglieds lädt die +# ganze (ko-residente) Gruppe. Selbst-detachend: blockiert den Service-Start nicht. set -u URL="${MC_LLAMA_SWAP_URL:-http://127.0.0.1:8080}" -BRAINS="${MC_WARMUP_MODELS:-hermes fast vision}" +BRAINS="${MC_WARMUP_MODELS:-fast}" if [ "${1:-}" != "--inner" ]; then setsid "$0" --inner >/dev/null 2>&1 &