Files
mission-control-v2/backend/services/warmer.py
T
Hitonabi 45d635afae Fix: Re-Warm-Waechter + Startup-Warmup folgen dem aktiven Hirn (nicht mehr fix "hermes")
Nach dem Hirn-Wechsel auf fast (Qwen3.6) zeigte der Wächter noch auf "hermes"
(Hermes-4-14B) -> er haette das aus dem Warm-Set entfernte Modell wieder geladen.
warmer._brain_model() liest jetzt Hermes' model.default (Fallback fast); Startup-Warmup
BRAINS default "fast". Passt sich kuenftigen Hirn-Wechseln automatisch an.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 18:42:26 +02:00

78 lines
2.8 KiB
Python

"""
Hält das Agent-Hirn (Rolle `hermes`) dauerhaft warm.
Hintergrund: llama-swap ist EIN-Gruppen-resident — lädt ein on-demand-Modell außerhalb
der `brains`-Gruppe, wird die ganze Gruppe (inkl. Hirn) verdrängt. `persist: true`
verhindert nur Idle-Unload, NICHT die Gruppen-Verdrängung; neu vorgewärmt wird sonst erst
beim nächsten llama-swap-(Re)Start. Dieser Wächter schließt die Lücke: ist die Box idle
(nichts geladen), pingt er das Hirn vor. Während aktiver Last (irgendetwas geladen) hält
er sich raus, verdrängt also nie ein gerade genutztes Modell.
Abschaltbar/justierbar via Env: MC_REWARM_ENABLED=0, MC_REWARM_INTERVAL, MC_REWARM_MODEL.
"""
import asyncio
import logging
import os
import httpx
from config import LLAMA_SWAP_URL
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
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")
data = r.json() or {}
return not (data.get("running") or [])
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,
"messages": [{"role": "user", "content": "ping"}],
})
async def rewarm_loop() -> None:
"""Endlos-Schleife (Hintergrund-Task): prüft periodisch, wärmt bei Idle vor."""
await asyncio.sleep(START_DELAY) # Box/Engine nach MC-Start setzen lassen
while True:
try:
if await _running_empty():
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)