feat(config): watch runtime configuration changes (#5026)
This commit is contained in:
@@ -98,6 +98,7 @@ class TestMaxMessagesInit:
|
||||
|
||||
initial = loop.runtime_resolver.runtime
|
||||
assert replay_max_messages_for_context(initial.context_window_tokens) == 327
|
||||
loop.runtime_resolver.invalidate()
|
||||
refreshed = loop.llm_runtime()
|
||||
assert replay_max_messages_for_context(refreshed.context_window_tokens) == FILE_MAX_MESSAGES
|
||||
|
||||
|
||||
@@ -177,12 +177,59 @@ def test_resolver_refreshes_provider_generation_for_next_default_turn() -> None:
|
||||
admitted = resolver.current()
|
||||
|
||||
provider.generation = GenerationSettings(temperature=0.8, max_tokens=512)
|
||||
refreshed = resolver.current(refresh=True)
|
||||
refreshed = resolver.admit()
|
||||
|
||||
assert admitted.generation == GenerationSettings(0.2, 2048, None)
|
||||
assert refreshed.generation == GenerationSettings(0.8, 512, None)
|
||||
|
||||
|
||||
def test_resolver_admission_reloads_config_only_after_invalidation() -> None:
|
||||
initial = _runtime()
|
||||
refreshed_provider = _provider()
|
||||
load_count = 0
|
||||
|
||||
def load_snapshot() -> ProviderSnapshot:
|
||||
nonlocal load_count
|
||||
load_count += 1
|
||||
return ProviderSnapshot(
|
||||
provider=refreshed_provider,
|
||||
model="refreshed-model",
|
||||
context_window_tokens=20_000,
|
||||
signature=("refreshed-model", "auto"),
|
||||
)
|
||||
|
||||
resolver = ModelRuntimeResolver(initial, provider_snapshot_loader=load_snapshot)
|
||||
|
||||
assert resolver.admit() is initial
|
||||
assert load_count == 0
|
||||
|
||||
resolver.invalidate()
|
||||
refreshed = resolver.admit()
|
||||
|
||||
assert refreshed.provider is refreshed_provider
|
||||
assert refreshed.model == "refreshed-model"
|
||||
assert resolver.admit() is refreshed
|
||||
assert load_count == 1
|
||||
|
||||
|
||||
def test_current_refresh_forces_config_reload() -> None:
|
||||
initial = _runtime()
|
||||
load_snapshot = MagicMock(
|
||||
return_value=ProviderSnapshot(
|
||||
provider=_provider(),
|
||||
model="refreshed-model",
|
||||
context_window_tokens=20_000,
|
||||
signature=("refreshed-model", "auto"),
|
||||
)
|
||||
)
|
||||
resolver = ModelRuntimeResolver(initial, provider_snapshot_loader=load_snapshot)
|
||||
|
||||
refreshed = resolver.current(refresh=True)
|
||||
|
||||
assert refreshed.model == "refreshed-model"
|
||||
load_snapshot.assert_called_once_with()
|
||||
|
||||
|
||||
def test_selected_preset_generation_does_not_fall_back_to_provider_defaults() -> None:
|
||||
provider = _provider(temperature=0.1, max_tokens=1024)
|
||||
resolver = ModelRuntimeResolver(
|
||||
@@ -198,7 +245,7 @@ def test_selected_preset_generation_does_not_fall_back_to_provider_defaults() ->
|
||||
selected = resolver.select_preset("creative")
|
||||
|
||||
provider.generation = GenerationSettings(temperature=0.9, max_tokens=64)
|
||||
refreshed = resolver.current(refresh=True)
|
||||
refreshed = resolver.admit()
|
||||
|
||||
assert refreshed is selected
|
||||
assert refreshed.generation == GenerationSettings(0.7, 4096, None)
|
||||
|
||||
@@ -2,6 +2,8 @@ from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from nanobot.agent.loop import AgentLoop
|
||||
from nanobot.bus.queue import MessageBus
|
||||
from nanobot.config.loader import save_config
|
||||
@@ -34,6 +36,7 @@ def test_provider_refresh_updates_only_runtime_resolver(tmp_path: Path) -> None:
|
||||
signature=("new-model",),
|
||||
),
|
||||
)
|
||||
loop.runtime_resolver.invalidate()
|
||||
|
||||
runtime = loop.llm_runtime()
|
||||
|
||||
@@ -90,6 +93,7 @@ def test_llm_runtime_refreshes_provider_snapshot(tmp_path: Path) -> None:
|
||||
signature=("new-model",),
|
||||
),
|
||||
)
|
||||
loop.runtime_resolver.invalidate()
|
||||
|
||||
runtime = loop.llm_runtime()
|
||||
|
||||
@@ -99,6 +103,24 @@ def test_llm_runtime_refreshes_provider_snapshot(tmp_path: Path) -> None:
|
||||
assert not hasattr(loop.runner, "provider")
|
||||
|
||||
|
||||
def test_llm_runtime_surfaces_invalidated_config_errors(tmp_path: Path) -> None:
|
||||
def fail_refresh() -> ProviderSnapshot:
|
||||
raise ValueError("invalid config")
|
||||
|
||||
loop = AgentLoop(
|
||||
bus=MessageBus(),
|
||||
provider=_provider("old-model"),
|
||||
workspace=tmp_path,
|
||||
model="old-model",
|
||||
context_window_tokens=1000,
|
||||
provider_snapshot_loader=fail_refresh,
|
||||
)
|
||||
loop.runtime_resolver.invalidate()
|
||||
|
||||
with pytest.raises(ValueError, match="invalid config"):
|
||||
loop.llm_runtime()
|
||||
|
||||
|
||||
def test_same_snapshot_default_clears_preset_and_publishes_update(tmp_path: Path) -> None:
|
||||
base_provider = _provider("base-model")
|
||||
fast_provider = _provider("fast-model")
|
||||
@@ -122,6 +144,7 @@ def test_same_snapshot_default_clears_preset_and_publishes_update(tmp_path: Path
|
||||
preset_snapshot_loader=lambda _name: fast_snapshot,
|
||||
runtime_model_publisher=lambda model, preset: published.append((model, preset)),
|
||||
)
|
||||
loop.runtime_resolver.invalidate()
|
||||
|
||||
runtime = loop.llm_runtime()
|
||||
|
||||
@@ -173,6 +196,7 @@ def test_settings_context_window_refreshes_runtime_state(
|
||||
loop = AgentLoop.from_config(config, provider_snapshot_loader=loader)
|
||||
|
||||
payload = update_agent_settings({"context_window_tokens": ["262144"]})
|
||||
loop.runtime_resolver.invalidate()
|
||||
loop.llm_runtime()
|
||||
|
||||
assert payload["requires_restart"] is False
|
||||
|
||||
@@ -175,6 +175,7 @@ def test_active_model_preset_survives_unchanged_config_refresh(tmp_path) -> None
|
||||
)
|
||||
|
||||
loop.set_model_preset("fast")
|
||||
loop.runtime_resolver.invalidate()
|
||||
loop.llm_runtime()
|
||||
|
||||
assert loop.model_preset == "fast"
|
||||
@@ -211,6 +212,7 @@ def test_config_model_refresh_clears_active_model_preset(tmp_path) -> None:
|
||||
)
|
||||
|
||||
loop.set_model_preset("fast")
|
||||
loop.runtime_resolver.invalidate()
|
||||
loop.llm_runtime()
|
||||
|
||||
assert loop.model_preset is None
|
||||
|
||||
Reference in New Issue
Block a user