box-wart: Hirn gezielt pruefen, Laden meldet das echte Ergebnis, Radar-Suche als Auftrag
- Waechter (pruefe_kern): prueft jetzt gezielt das Modell mit der Rolle hermes und seinen
Zustand in llama-swap (/running, v257 listet auch ladende Modelle), statt das Hirn fuer
bereit zu halten, sobald irgendein Modell laeuft. Ein Ladevorgang ist kein Ausfall, aber
nur bis 10 Minuten (MC_WAECHTER_HIRN_LADEN_S), sonst bliebe ein immer neu startendes Hirn
unbemerkt. Sonst die ueblichen Takte (FAIL_AFTER); Knopf "Protokoll des Motors".
- llamaswap: modell_zustaende() und hirn_zustand(); lade_modell() meldet ok false mit
deutschem Grund und der Meldung der Engine, "laedt noch" nach der Wartezeit ist kein Fehler,
ein geladenes Modell ohne Chat (embed) zaehlt als geladen. POST /api/models/{id}/load
reicht das Ergebnis durch (Router duenn).
- Radar "Jetzt suchen": startet die Suche als Auftrag (Job-Engine, Gruppe radar, hoechstens
einer, radar_lauf.py --nur-suche) und antwortet sofort mit der Auftrags-ID. Die Ansicht
zeigt "Sucht ..." bis der Auftrag fertig ist, liest danach das Radar neu und nennt eine
gescheiterte Suche; die Auftragskarte zeigt den Stand.
- Tests: Hirn-Zustaende und Frist, Laden gegen eine llama-swap-Attrappe, Radar-Auftrag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
40d2840b16
commit
93f6613146
@@ -659,3 +659,104 @@ def get_running_models() -> list[str]:
|
||||
except Exception:
|
||||
log.warning("get_running_models fehlgeschlagen", exc_info=True)
|
||||
return []
|
||||
|
||||
|
||||
def modell_zustaende() -> dict[str, str] | None:
|
||||
"""Zustand je Modell aus /running: Name → "ready" | "starting" | "stopping". Wer fehlt, ist nicht geladen.
|
||||
llama-swap v257 listet dort jedes Modell, das weder „stopped“ noch „shutdown“ ist (internal/router/base.go,
|
||||
RunningModels) — also auch eines, das gerade lädt. Ältere Fassungen lieferten nur Namen; die gelten als bereit.
|
||||
None = /running nicht lesbar."""
|
||||
try:
|
||||
with httpx.Client(timeout=3.0) as c:
|
||||
r = c.get(f"{LLAMA_SWAP_URL}/running")
|
||||
if r.status_code != 200:
|
||||
return None
|
||||
eintraege = r.json().get("running") or []
|
||||
except Exception:
|
||||
log.warning("modell_zustaende: /running nicht lesbar", exc_info=True)
|
||||
return None
|
||||
zustaende: dict[str, str] = {}
|
||||
for x in eintraege:
|
||||
if isinstance(x, dict):
|
||||
if x.get("model"):
|
||||
zustaende[str(x["model"])] = str(x.get("state") or "ready")
|
||||
elif x:
|
||||
zustaende[str(x)] = "ready"
|
||||
return zustaende
|
||||
|
||||
|
||||
def hirn_zustand() -> dict:
|
||||
"""Wie steht Lucys Hirn (Modell mit dem Alias/der Rolle „hermes“) in llama-swap? Gezielt dieses eine Modell —
|
||||
bis 24.09.2026 galt das Hirn als bereit, sobald IRGENDEIN Modell lief (ein abgestürztes Hirn neben einem
|
||||
geladenen Coder blieb unbemerkt).
|
||||
Rückgabe {"modell": Name oder None, "zustand": "bereit" | "laedt" | "fehlt" | "unbekannt"};
|
||||
„unbekannt“ heißt: /running antwortet nicht. Trägt kein Modell die Rolle, ist modell None und zustand "fehlt"."""
|
||||
name = brain_model_name()
|
||||
zustaende = modell_zustaende()
|
||||
if zustaende is None:
|
||||
return {"modell": name, "zustand": "unbekannt"}
|
||||
zustand = zustaende.get(name or "")
|
||||
if zustand == "ready":
|
||||
return {"modell": name, "zustand": "bereit"}
|
||||
if zustand == "starting":
|
||||
return {"modell": name, "zustand": "laedt"}
|
||||
return {"modell": name, "zustand": "fehlt"}
|
||||
|
||||
|
||||
# Wie lange „Jetzt laden“ auf die Engine wartet. Große Modelle brauchen eine Weile (Lesen + Hochladen in den Speicher);
|
||||
# was danach noch lädt, meldet lade_modell als „lädt noch“ statt als Fehler.
|
||||
LADEN_WARTE_S = float(os.environ.get("MC_LADEN_WARTE_S", "90"))
|
||||
|
||||
|
||||
def _engine_grund(r: httpx.Response) -> str:
|
||||
"""Die Fehlermeldung der Engine aus ihrer Antwort (OpenAI-Format {"error": {"message"}}, {"error": "…"},
|
||||
{"detail": "…"} oder reiner Text), auf eine Zeile gekürzt."""
|
||||
text = ""
|
||||
try:
|
||||
daten = r.json()
|
||||
except ValueError:
|
||||
daten = None
|
||||
if isinstance(daten, dict):
|
||||
fehler = daten.get("error")
|
||||
if isinstance(fehler, dict):
|
||||
text = str(fehler.get("message") or "")
|
||||
elif fehler:
|
||||
text = str(fehler)
|
||||
elif daten.get("detail"):
|
||||
text = str(daten["detail"])
|
||||
text = " ".join((text or r.text or "").split())
|
||||
return text[:200] or "ohne Begründung"
|
||||
|
||||
|
||||
def lade_modell(model_id: str, warte_s: float | None = None) -> dict:
|
||||
"""Ein Modell laden und das ECHTE Ergebnis melden (seit 24.09.2026 — vorher hieß jede Antwort der Engine „ok“).
|
||||
llama-swap lädt ein Modell mit der ersten Anfrage; dafür reicht eine Mini-Anfrage mit einem Token. Ob das Modell
|
||||
danach wirklich geladen ist, entscheidet /running — so zählen auch Modelle, die gar nicht chatten (embed,
|
||||
reranker), und ein Fehler der Anfrage selbst macht ein geladenes Modell nicht zum Fehlschlag.
|
||||
Rückgabe: {"ok": True, "text": …} (bei "laedt": True lädt es nach der Wartezeit noch) oder
|
||||
{"ok": False, "detail": deutscher Grund, mit der Meldung der Engine}."""
|
||||
warte = LADEN_WARTE_S if warte_s is None else warte_s
|
||||
anfrage = {"model": model_id, "messages": [{"role": "user", "content": "ping"}], "max_tokens": 1}
|
||||
try:
|
||||
with httpx.Client(timeout=warte) as c:
|
||||
r = c.post(f"{LLAMA_SWAP_URL}/v1/chat/completions", json=anfrage)
|
||||
except (httpx.ConnectError, httpx.ConnectTimeout) as exc:
|
||||
return {"ok": False, "detail": f"{model_id} ließ sich nicht laden: Die Engine (llama-swap) ist nicht "
|
||||
f"erreichbar ({exc.__class__.__name__})."}
|
||||
except httpx.TimeoutException:
|
||||
if (modell_zustaende() or {}).get(model_id) == "starting":
|
||||
return {"ok": True, "laedt": True,
|
||||
"text": f"{model_id} lädt noch. Große Modelle brauchen etwas; der Stand erscheint gleich unter Modelle."}
|
||||
return {"ok": False, "detail": f"{model_id} ließ sich nicht laden: Die Engine hat nach {warte:.0f} Sekunden "
|
||||
"nicht geantwortet, und das Modell lädt auch nicht."}
|
||||
except httpx.HTTPError as exc:
|
||||
return {"ok": False, "detail": f"{model_id} ließ sich nicht laden: {exc.__class__.__name__}: {exc}"[:300]}
|
||||
if r.status_code == 200:
|
||||
return {"ok": True, "text": f"{model_id} ist geladen."}
|
||||
zustand = (modell_zustaende() or {}).get(model_id)
|
||||
if zustand == "ready":
|
||||
return {"ok": True, "text": f"{model_id} ist geladen."}
|
||||
if zustand == "starting":
|
||||
return {"ok": True, "laedt": True, "text": f"{model_id} lädt noch."}
|
||||
return {"ok": False, "detail": f"{model_id} ließ sich nicht laden. Die Engine meldet (HTTP {r.status_code}): "
|
||||
f"{_engine_grund(r)}"}
|
||||
|
||||
Reference in New Issue
Block a user