Files
EvoScientist-Multi/tests/test_auxiliary_model.py
T
Xi Zhang 63969b596d Release/v0.1.4 (#266)
* feat(middleware): reposition code interpreter middleware in the stack

* feat(models): add qwen3.7-plus model entry and update context window comment

* feat(models): add qwen3.7-max and qwen3.7-plus model entries for DashScope

* feat(auxiliary): implement auxiliary model support for background tasks and tool selection

- Added auxiliary model configuration to EvoScientistConfig.
- Introduced _ensure_auxiliary_chat_model function to manage auxiliary model instances.
- Updated onboarding steps to include auxiliary model selection.
- Modified middleware to route tool selection to the auxiliary model when applicable.
- Enhanced tests to cover auxiliary model functionality and configuration.

* feat(steps): update UI backend selection options and descriptions

* Refactor code structure for improved readability and maintainability

* feat(patches): implement OpenRouter response reasoning item stripping to prevent multi-turn errors

* feat: update version to v0.1.4 in badges, README, and pyproject.toml; adjust skill counts in steps.py

* feat(config): add auxiliary model and provider environment variables to test setup
2026-06-07 00:52:59 +01:00

160 lines
5.8 KiB
Python

"""Tests for the auxiliary-model resolver and its middleware scoping.
Covers ``EvoScientist.EvoScientist._ensure_auxiliary_chat_model`` (fallback to
the main model when unset) and the wiring in ``_get_default_middleware`` that
routes the main agent's tool selector to the auxiliary model while keeping
context editing — and async sub-agents — on the main model.
"""
from types import SimpleNamespace
from unittest.mock import MagicMock, patch
import pytest
import EvoScientist.EvoScientist as E
@pytest.fixture(autouse=True)
def _reset_model_caches(monkeypatch):
"""Isolate the module-level model caches per test."""
monkeypatch.setattr(E, "_chat_model", None, raising=False)
monkeypatch.setattr(E, "_chat_model_key", None, raising=False)
monkeypatch.setattr(E, "_auxiliary_chat_model", None, raising=False)
monkeypatch.setattr(E, "_auxiliary_chat_model_key", None, raising=False)
def _cfg(**over):
base = {
"model": "main-m",
"provider": "main-p",
"auxiliary_model": "",
"auxiliary_provider": "",
}
base.update(over)
return SimpleNamespace(**base)
class TestAuxiliaryResolver:
def test_empty_returns_main_instance(self, monkeypatch):
main = object()
monkeypatch.setattr(E, "_ensure_config", lambda config=None: _cfg())
monkeypatch.setattr(E, "_ensure_chat_model", lambda: main)
assert E._ensure_auxiliary_chat_model() is main
def test_aux_equal_to_main_reuses_main_instance(self, monkeypatch):
main = object()
monkeypatch.setattr(
E,
"_ensure_config",
lambda config=None: _cfg(
auxiliary_model="main-m", auxiliary_provider="main-p"
),
)
monkeypatch.setattr(E, "_ensure_chat_model", lambda: main)
assert E._ensure_auxiliary_chat_model() is main
def test_set_builds_auxiliary(self, monkeypatch):
fake = object()
get_chat_model = MagicMock(return_value=fake)
monkeypatch.setattr(
E,
"_ensure_config",
lambda config=None: _cfg(
auxiliary_model="aux-m", auxiliary_provider="aux-p"
),
)
monkeypatch.setattr("EvoScientist.llm.get_chat_model", get_chat_model)
assert E._ensure_auxiliary_chat_model() is fake
get_chat_model.assert_called_once_with(model="aux-m", provider="aux-p")
def test_empty_provider_falls_back_to_main_provider(self, monkeypatch):
get_chat_model = MagicMock(return_value=object())
monkeypatch.setattr(
E,
"_ensure_config",
lambda config=None: _cfg(auxiliary_model="aux-m", auxiliary_provider=""),
)
monkeypatch.setattr("EvoScientist.llm.get_chat_model", get_chat_model)
E._ensure_auxiliary_chat_model()
get_chat_model.assert_called_once_with(model="aux-m", provider="main-p")
def test_set_chat_model_resets_aux_cache(self, monkeypatch):
monkeypatch.setattr(E, "_auxiliary_chat_model", object(), raising=False)
monkeypatch.setattr(E, "_auxiliary_chat_model_key", ("x", "y"), raising=False)
monkeypatch.setattr(
"EvoScientist.llm.get_chat_model", MagicMock(return_value=object())
)
E.set_chat_model("new-m", "new-p")
assert E._auxiliary_chat_model is None
assert E._auxiliary_chat_model_key is None
def _mock_cfg():
cfg = MagicMock()
cfg.enable_ask_user = False
cfg.auto_mode = False
cfg.auto_approve = False
cfg.model_fallbacks = None
cfg.auxiliary_model = ""
cfg.auxiliary_provider = ""
return cfg
class TestAuxiliaryMiddlewareScope:
"""``_get_default_middleware`` routes only the right components to aux."""
def _capture(self):
cap: dict[str, object] = {}
def fake_tool_selector(*args, model=None, **kwargs):
cap["tool_selector"] = model
return [MagicMock()]
def fake_context_editing(model=None, *args, **kwargs):
cap["context_editing"] = model
return MagicMock()
return cap, fake_tool_selector, fake_context_editing
def test_main_agent_tool_selector_aux_context_editing_main(self):
cap, fake_ts, fake_ce = self._capture()
main_model, aux_model = object(), object()
with (
patch.object(E, "_ensure_config", return_value=_mock_cfg()),
patch.object(E, "_ensure_chat_model", return_value=main_model),
patch.object(E, "_ensure_auxiliary_chat_model", return_value=aux_model),
patch(
"EvoScientist.middleware.create_tool_selector_middleware",
side_effect=fake_ts,
),
patch(
"EvoScientist.middleware.create_context_editing_middleware",
side_effect=fake_ce,
),
):
E._get_default_middleware()
assert cap["tool_selector"] is aux_model
assert cap["context_editing"] is main_model
def test_async_subagent_tool_selector_stays_main(self):
cap, fake_ts, fake_ce = self._capture()
main_model, aux_model = object(), object()
with (
patch.object(E, "_ensure_config", return_value=_mock_cfg()),
patch.object(E, "_ensure_chat_model", return_value=main_model),
patch.object(E, "_ensure_auxiliary_chat_model", return_value=aux_model),
patch(
"EvoScientist.middleware.create_tool_selector_middleware",
side_effect=fake_ts,
),
patch(
"EvoScientist.middleware.create_context_editing_middleware",
side_effect=fake_ce,
),
):
E._get_default_middleware(for_async_subagent=True)
assert cap["tool_selector"] is main_model
assert cap["context_editing"] is main_model