"""Sammel-Schreiben der llama-swap-Config (24.09.2026): Ein Modelltausch schrieb die Config bis zu siebenmal, und jedes Mal entlud llama-swap alle Modelle. In llamaswap.sammeln() gibt es genau einen Schreibvorgang — oder keinen, wenn etwas scheitert.""" import threading import pytest from services import llamaswap CONFIG = """\ models: alt: cmd: llama-server -m /srv/models/alt.gguf aliases: - hermes - fast ttl: 0 neu: cmd: llama-server -m /srv/models/neu.gguf ttl: 300 groups: brains: swap: false persistent: true members: - alt """ @pytest.fixture def config(tmp_path, monkeypatch): datei = tmp_path / "config.yaml" datei.write_text(CONFIG, encoding="utf-8") monkeypatch.setattr(llamaswap, "CONFIG_PATH", datei) schreibvorgaenge: list[int] = [] echt = llamaswap._schreiben def zaehlen(cfg): schreibvorgaenge.append(1) echt(cfg) monkeypatch.setattr(llamaswap, "_schreiben", zaehlen) return datei, schreibvorgaenge def test_ohne_sammeln_schreibt_jede_aenderung(config): _, schreibvorgaenge = config llamaswap.set_role("neu", "coder") llamaswap.set_ttl("neu", 600) assert len(schreibvorgaenge) == 2 def test_sammeln_schreibt_einmal_mit_allen_aenderungen(config): datei, schreibvorgaenge = config with llamaswap.sammeln(): llamaswap.set_role("neu", "hermes") llamaswap.add_role("neu", "fast") llamaswap.set_group("brains", ["neu"], swap=False, persist=True) llamaswap.set_ttl("neu", 0) llamaswap.set_ttl("alt", 300) # Innerhalb des Sammelns sieht jede Abfrage den gesammelten Stand … assert "hermes" in next(m for m in llamaswap.list_models() if m["name"] == "neu")["aliases"] # … die Datei aber noch den alten. assert "- hermes" in datei.read_text(encoding="utf-8").split("neu:")[0] assert schreibvorgaenge == [] assert len(schreibvorgaenge) == 1 modelle = {m["name"]: m for m in llamaswap.list_models()} assert {"hermes", "fast"} <= set(modelle["neu"]["aliases"]) and modelle["neu"]["ttl"] == 0 assert "hermes" not in modelle["alt"]["aliases"] and modelle["alt"]["ttl"] == 300 assert llamaswap.list_groups()["brains"]["members"] == ["neu"] def test_sammeln_schreibt_nichts_wenn_etwas_scheitert(config): datei, schreibvorgaenge = config vorher = datei.read_text(encoding="utf-8") with pytest.raises(RuntimeError), llamaswap.sammeln(): llamaswap.set_role("neu", "hermes") raise RuntimeError("Hirn-Wechsel fehlgeschlagen") assert schreibvorgaenge == [] and datei.read_text(encoding="utf-8") == vorher def test_verschachtelt_schreibt_der_aeussere(config): _, schreibvorgaenge = config with llamaswap.sammeln(): llamaswap.set_ttl("neu", 1) with llamaswap.sammeln(): llamaswap.set_ttl("neu", 2) assert schreibvorgaenge == [] assert len(schreibvorgaenge) == 1 assert next(m for m in llamaswap.list_models() if m["name"] == "neu")["ttl"] == 2 def test_andere_threads_sehen_den_gesammelten_stand_nicht(config): gesehen: list[list[str]] = [] with llamaswap.sammeln(): llamaswap.set_role("neu", "coder") t = threading.Thread(target=lambda: gesehen.append( next(m for m in llamaswap._lesen()["models"] if m == "neu") and list(llamaswap._lesen()["models"]["neu"].get("aliases") or []))) t.start() t.join() assert gesehen == [[]] def test_hirn_umstellung_schreibt_einmal(config, monkeypatch): from services import agent _, schreibvorgaenge = config monkeypatch.setattr(agent, "hermes_brain_info", dict) umgestellt: list[str] = [] monkeypatch.setattr(agent, "update_brain_model", lambda alias: umgestellt.append(alias) or True) antwort = agent.set_agent_brain("neu", hermes_umstellen=False) assert antwort["ok"] and antwort["old"] == "alt" assert len(schreibvorgaenge) == 1 and umgestellt == [] modelle = {m["name"]: m for m in llamaswap.list_models()} assert "hermes" in modelle["neu"]["aliases"] and modelle["neu"]["ttl"] == 0 assert llamaswap.list_groups()["brains"]["members"] == ["neu"] agent.set_agent_brain("neu") # schon das Hirn: härtet nur nach, Hermes wird nicht angefasst assert umgestellt == []