phase2e: Modelltausch schreibt die llama-swap-Config einmal statt bis zu siebenmal
- llamaswap.sammeln(): Aenderungen lesen dieselbe Config aus dem Speicher, geschrieben wird einmal am Ende, bei einem Fehler gar nicht - Radar-Tausch und Hirn-Umstellung nutzen es; Hermes wird erst nach dem Schreiben umgestellt - Test-Attrappe fuer update_brain_model: der Radar-Test faesst auf der Box nie die echte Hermes-Config an - Doku: Ziel-Modell, strukturierter Verlauf, Sammel-Schreiben Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
589c14d5e9
commit
a47489fa85
+21
-15
@@ -107,12 +107,14 @@ def agent_status() -> dict:
|
||||
}
|
||||
|
||||
|
||||
def set_agent_brain(model_id: str) -> dict:
|
||||
def set_agent_brain(model_id: str, hermes_umstellen: bool = True) -> dict:
|
||||
"""Setzt ein (bereits installiertes) Modell als Agent-Hirn — WARM-bewusst:
|
||||
1) vergibt den 'hermes'-Alias (das Agent-Hirn-Slot),
|
||||
2) tauscht es in die residente brains-Gruppe (altes Hirn raus, fast/vision bleiben),
|
||||
3) zeigt die Hermes-Config auf den 'hermes'-Alias + Gateway-Restart.
|
||||
So bleibt das neue Hirn warm und der Agent nutzt es sofort."""
|
||||
So bleibt das neue Hirn warm und der Agent nutzt es sofort.
|
||||
hermes_umstellen=False: Schritt 3 macht der Aufrufer selbst — etwa das Radar, das erst nach dem
|
||||
einen gesammelten Schreibvorgang der llama-swap-Config Hermes neu startet."""
|
||||
from services import llamaswap
|
||||
models = {m["name"]: m for m in llamaswap.list_models()}
|
||||
if model_id not in models:
|
||||
@@ -122,26 +124,30 @@ def set_agent_brain(model_id: str) -> dict:
|
||||
# Idempotent härten: auch wenn schon Hirn, warm (brains) + ttl 0 sicherstellen.
|
||||
try:
|
||||
from services.llamaswap import set_ttl
|
||||
brains = (llamaswap.list_groups().get("brains") or {}).get("members") or []
|
||||
if model_id not in brains:
|
||||
llamaswap.set_group("brains", brains + [model_id], swap=False, persist=True)
|
||||
set_ttl(model_id, 0)
|
||||
with llamaswap.sammeln():
|
||||
brains = (llamaswap.list_groups().get("brains") or {}).get("members") or []
|
||||
if model_id not in brains:
|
||||
llamaswap.set_group("brains", brains + [model_id], swap=False, persist=True)
|
||||
set_ttl(model_id, 0)
|
||||
except PermissionError as exc:
|
||||
return {"ok": False, "reason": str(exc)}
|
||||
return {"ok": True, "old": old, "new": model_id, "note": "ist bereits das Agent-Hirn"}
|
||||
try:
|
||||
llamaswap.set_role(model_id, "hermes") # 1) Alias
|
||||
brains = (llamaswap.list_groups().get("brains") or {}).get("members") or []
|
||||
new_members = [x for x in brains if x not in (old, model_id)] + [model_id]
|
||||
llamaswap.set_group("brains", new_members, swap=False, persist=True) # 2) warm
|
||||
# 2b) TTL härten: neues Hirn nie auto-entladen; altes Hirn auf Default entspannen.
|
||||
from services.llamaswap import DEFAULT_TTL, set_ttl
|
||||
set_ttl(model_id, 0)
|
||||
if old:
|
||||
set_ttl(old, DEFAULT_TTL)
|
||||
# Alias, Gruppe und ttl als EIN Schreibvorgang (24.09.2026) — jeder einzelne entlud alle Modelle.
|
||||
with llamaswap.sammeln():
|
||||
llamaswap.set_role(model_id, "hermes") # 1) Alias
|
||||
brains = (llamaswap.list_groups().get("brains") or {}).get("members") or []
|
||||
new_members = [x for x in brains if x not in (old, model_id)] + [model_id]
|
||||
llamaswap.set_group("brains", new_members, swap=False, persist=True) # 2) warm
|
||||
# 2b) TTL härten: neues Hirn nie auto-entladen; altes Hirn auf Default entspannen.
|
||||
set_ttl(model_id, 0)
|
||||
if old:
|
||||
set_ttl(old, DEFAULT_TTL)
|
||||
except PermissionError as exc:
|
||||
return {"ok": False, "reason": str(exc)}
|
||||
update_brain_model("hermes") # 3) Config + Restart
|
||||
if hermes_umstellen:
|
||||
update_brain_model("hermes") # 3) Config + Restart
|
||||
# Weiche Budget-Warnung (kein Hard-Block): passt Hirn + größtes on-demand zusammen ins GTT?
|
||||
warning = None
|
||||
try:
|
||||
|
||||
@@ -93,6 +93,29 @@ def config_sperre():
|
||||
datei.close()
|
||||
|
||||
|
||||
# Sammel-Schreiben (24.09.2026): Jeder Schreibvorgang lässt llama-swap neu laden und ALLE Modelle
|
||||
# entladen. Ein Modelltausch bestand aus bis zu sieben Schreibvorgängen (Eintragen, Befehl, Zwilling,
|
||||
# Alias, Gruppe, ttl …). Innerhalb von sammeln() lesen alle Änderungen dieselbe Config aus dem
|
||||
# Speicher, und geschrieben wird einmal am Ende — oder gar nicht, wenn etwas scheitert.
|
||||
_SAMMEL = threading.local()
|
||||
|
||||
|
||||
@contextmanager
|
||||
def sammeln():
|
||||
with config_sperre():
|
||||
if getattr(_SAMMEL, "aktiv", False): # verschachtelt: der äußere schreibt
|
||||
yield
|
||||
return
|
||||
_SAMMEL.aktiv, _SAMMEL.cfg = True, None
|
||||
try:
|
||||
yield
|
||||
cfg = _SAMMEL.cfg
|
||||
finally:
|
||||
_SAMMEL.aktiv, _SAMMEL.cfg = False, None
|
||||
if cfg is not None:
|
||||
_schreiben(cfg)
|
||||
|
||||
|
||||
def _mit_sperre(fn):
|
||||
@functools.wraps(fn)
|
||||
def huelle(*args, **kwargs):
|
||||
@@ -103,6 +126,14 @@ def _mit_sperre(fn):
|
||||
|
||||
# --- Lesen -------------------------------------------------------------------
|
||||
def read_config() -> dict:
|
||||
if getattr(_SAMMEL, "aktiv", False):
|
||||
if _SAMMEL.cfg is None:
|
||||
_SAMMEL.cfg = _lesen()
|
||||
return _SAMMEL.cfg
|
||||
return _lesen()
|
||||
|
||||
|
||||
def _lesen() -> dict:
|
||||
if not CONFIG_PATH.exists():
|
||||
return {"models": {}}
|
||||
from ruamel.yaml import YAML
|
||||
@@ -260,7 +291,15 @@ def _augment_vision(cmd: str, model_path: str, mmproj_path: str | None) -> str:
|
||||
|
||||
def write_config(cfg: dict) -> None:
|
||||
"""Atomar schreiben (tmp + os.replace), damit llama-swap mit -watch-config nie
|
||||
eine halbe Datei sieht. Fehlende Schreibrechte → klare Meldung."""
|
||||
eine halbe Datei sieht. Fehlende Schreibrechte → klare Meldung. Innerhalb von sammeln()
|
||||
wird nur vorgemerkt; geschrieben wird am Ende einmal."""
|
||||
if getattr(_SAMMEL, "aktiv", False):
|
||||
_SAMMEL.cfg = cfg
|
||||
return
|
||||
_schreiben(cfg)
|
||||
|
||||
|
||||
def _schreiben(cfg: dict) -> None:
|
||||
try:
|
||||
CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
tmp = CONFIG_PATH.with_name(CONFIG_PATH.name + ".tmp")
|
||||
|
||||
+25
-19
@@ -1383,8 +1383,9 @@ def _tausche_ein(k: dict) -> dict:
|
||||
else:
|
||||
ziel = quelle # schon verschoben (früherer, abgebrochener Versuch)
|
||||
pfade = _lokale_pfade(k, ziel)
|
||||
# Eintragen, Befehl ersetzen und Zwilling anlegen unter EINER Config-Sperre (24.09.2026).
|
||||
with llamaswap.config_sperre():
|
||||
# Eintragen, Befehl, Zwilling, Rollen, Gruppe und ttl als EIN Schreibvorgang (24.09.2026): Vorher
|
||||
# schrieb ein Tausch die Config bis zu siebenmal, und jedes Mal entlud llama-swap alle Modelle.
|
||||
with llamaswap.sammeln():
|
||||
modell_id = llamaswap.register_model(pfade["gguf"], role=ALIAS[rolle],
|
||||
ctx=int(k.get("ctx") or CTX_ROLLE[rolle]),
|
||||
mmproj_path=None, jinja=True, set_alias=False)
|
||||
@@ -1396,27 +1397,32 @@ def _tausche_ein(k: dict) -> dict:
|
||||
if pfade.get("mmproj"):
|
||||
_zwilling_eintragen(cfg, rolle, modell_id, pfade, k)
|
||||
llamaswap.write_config(cfg)
|
||||
antwort: dict = {}
|
||||
if rolle == "hirn":
|
||||
from services.agent import set_agent_brain
|
||||
# hermes-Alias, brains-Gruppe, ttl 0 — Hermes selbst erst nach dem Schreiben (unten).
|
||||
antwort = set_agent_brain(modell_id, hermes_umstellen=False)
|
||||
if not antwort.get("ok"):
|
||||
raise RuntimeError(antwort.get("reason") or "Hirn-Wechsel fehlgeschlagen")
|
||||
# Das Hirn ist heute hermes UND fast (Chat-Spur, warmup.sh, Oberfläche). Bliebe fast am alten
|
||||
# Modell, lüde warmup.sh es nachts neben das neue Hirn.
|
||||
if alt and "fast" in {str(a).lower() for a in alt.get("aliases") or []}:
|
||||
llamaswap.add_role(modell_id, "fast")
|
||||
else:
|
||||
if not llamaswap.set_role(modell_id, "coder"):
|
||||
raise RuntimeError(f"Die Rolle coder ließ sich nicht auf {modell_id} setzen.")
|
||||
# heavy ist heute derselbe Coder (OpenCode plant damit). Bliebe es am alten Modell, lägen zwei
|
||||
# Coder bereit und der alte ließe sich nie löschen.
|
||||
if alt and "heavy" in {str(a).lower() for a in alt.get("aliases") or []}:
|
||||
llamaswap.add_role(modell_id, "heavy")
|
||||
if alt and alt.get("ttl") is not None:
|
||||
llamaswap.set_ttl(modell_id, int(alt["ttl"]))
|
||||
hinweis = None
|
||||
if rolle == "hirn":
|
||||
from services.agent import set_agent_brain
|
||||
antwort = set_agent_brain(modell_id) # hermes-Alias, brains-Gruppe, ttl 0, Hermes-Config
|
||||
if not antwort.get("ok"):
|
||||
raise RuntimeError(antwort.get("reason") or "Hirn-Wechsel fehlgeschlagen")
|
||||
# Das Hirn ist heute hermes UND fast (Chat-Spur, warmup.sh, Oberfläche). Bliebe fast am alten
|
||||
# Modell, lüde warmup.sh es nachts neben das neue Hirn.
|
||||
if alt and "fast" in {str(a).lower() for a in alt.get("aliases") or []}:
|
||||
llamaswap.add_role(modell_id, "fast")
|
||||
from services.agent import update_brain_model
|
||||
update_brain_model("hermes") # Hermes-Config + Neustart, jetzt mit fertiger Config
|
||||
hinweis = " ".join(h for h in (antwort.get("warning"), _warmset_umstellen((alt or {}).get("name"), modell_id))
|
||||
if h) or None
|
||||
else:
|
||||
if not llamaswap.set_role(modell_id, "coder"):
|
||||
raise RuntimeError(f"Die Rolle coder ließ sich nicht auf {modell_id} setzen.")
|
||||
# heavy ist heute derselbe Coder (OpenCode plant damit). Bliebe es am alten Modell, lägen zwei Coder
|
||||
# bereit und der alte ließe sich nie löschen.
|
||||
if alt and "heavy" in {str(a).lower() for a in alt.get("aliases") or []}:
|
||||
llamaswap.add_role(modell_id, "heavy")
|
||||
if alt and alt.get("ttl") is not None:
|
||||
llamaswap.set_ttl(modell_id, int(alt["ttl"]))
|
||||
return {"modell_id": modell_id, "pfad": str(ziel), "alt": (alt or {}).get("name"), "hinweis": hinweis}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user