63969b596d
* 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
167 lines
5.6 KiB
Python
167 lines
5.6 KiB
Python
"""Tests for ContextEditingMiddleware integration and compute_context_editing_trigger."""
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from langchain.agents.middleware import ContextEditingMiddleware
|
|
|
|
from EvoScientist.middleware.context_editing import compute_context_editing_trigger
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# compute_context_editing_trigger tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_compute_trigger_with_profile():
|
|
model = MagicMock()
|
|
model.profile = {"max_input_tokens": 200_000}
|
|
assert compute_context_editing_trigger(model) == 100_000 # 50%
|
|
|
|
|
|
def test_compute_trigger_with_1m_profile():
|
|
model = MagicMock()
|
|
model.profile = {"max_input_tokens": 1_000_000}
|
|
assert compute_context_editing_trigger(model) == 500_000 # 50%
|
|
|
|
|
|
def test_compute_trigger_with_context_length_attr():
|
|
model = MagicMock(spec=["context_length", "profile"])
|
|
model.context_length = 1_000_000
|
|
model.profile = None
|
|
assert compute_context_editing_trigger(model) == 500_000 # 50%
|
|
|
|
|
|
def test_compute_trigger_with_num_ctx():
|
|
model = MagicMock(spec=["num_ctx", "profile"])
|
|
model.num_ctx = 32_768
|
|
model.profile = None
|
|
assert compute_context_editing_trigger(model) == 16_384 # 50%
|
|
|
|
|
|
def test_compute_trigger_without_profile():
|
|
model = MagicMock()
|
|
model.profile = None
|
|
assert compute_context_editing_trigger(model) == 100_000 # fallback
|
|
|
|
|
|
def test_compute_trigger_no_profile_attr():
|
|
model = MagicMock(spec=[]) # no attributes at all
|
|
assert compute_context_editing_trigger(model) == 100_000 # fallback
|
|
|
|
|
|
def test_compute_trigger_empty_profile():
|
|
model = MagicMock()
|
|
model.profile = {}
|
|
assert compute_context_editing_trigger(model) == 100_000 # fallback
|
|
|
|
|
|
def test_compute_trigger_custom_fraction():
|
|
model = MagicMock()
|
|
model.profile = {"max_input_tokens": 200_000}
|
|
assert compute_context_editing_trigger(model, fraction=0.30) == 60_000
|
|
|
|
|
|
def test_compute_trigger_custom_fallback():
|
|
model = MagicMock()
|
|
model.profile = None
|
|
assert compute_context_editing_trigger(model, fallback=50_000) == 50_000
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# create_context_editing_middleware tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_create_middleware_configuration():
|
|
from EvoScientist.middleware.context_editing import (
|
|
create_context_editing_middleware,
|
|
)
|
|
|
|
model = MagicMock()
|
|
model.profile = {"max_input_tokens": 200_000}
|
|
mw = create_context_editing_middleware(model)
|
|
edit = mw.edits[0]
|
|
assert edit.trigger == 100_000
|
|
assert edit.keep == 5
|
|
assert "think_tool" in edit.exclude_tools
|
|
|
|
|
|
@patch("EvoScientist.EvoScientist._ensure_chat_model")
|
|
def test_create_middleware_model_none_fallback(mock_model):
|
|
from EvoScientist.middleware.context_editing import (
|
|
create_context_editing_middleware,
|
|
)
|
|
|
|
mock_model.return_value = MagicMock(profile=None)
|
|
mw = create_context_editing_middleware(None)
|
|
edit = mw.edits[0]
|
|
assert edit.trigger == 100_000 # fallback
|
|
mock_model.assert_called_once()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Middleware list integration tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@patch(
|
|
"EvoScientist.middleware.create_tool_selector_middleware",
|
|
return_value=[MagicMock(), MagicMock()],
|
|
)
|
|
@patch("EvoScientist.EvoScientist._ensure_chat_model")
|
|
@patch("EvoScientist.EvoScientist._ensure_config")
|
|
def test_default_middleware_includes_context_editing(mock_config, mock_model, mock_ts):
|
|
mock_model.return_value = MagicMock(profile={"max_input_tokens": 200_000})
|
|
cfg = MagicMock()
|
|
cfg.enable_ask_user = False
|
|
cfg.auto_approve = False
|
|
cfg.auxiliary_model = ""
|
|
cfg.auxiliary_provider = ""
|
|
mock_config.return_value = cfg
|
|
|
|
from EvoScientist.EvoScientist import _get_default_middleware
|
|
|
|
mw = _get_default_middleware()
|
|
# ContextEditingMiddleware is present (its absolute position depends on
|
|
# other leading middlewares like ConfigurableModelMiddleware).
|
|
assert any(isinstance(m, ContextEditingMiddleware) for m in mw)
|
|
|
|
|
|
@patch("EvoScientist.EvoScientist._ensure_chat_model")
|
|
def test_inject_subagent_includes_context_editing(mock_model):
|
|
mock_model.return_value = MagicMock(profile={"max_input_tokens": 200_000})
|
|
|
|
from EvoScientist.EvoScientist import _inject_subagent_middleware
|
|
|
|
subs = [{"name": "test-agent"}]
|
|
_inject_subagent_middleware(subs)
|
|
|
|
middleware_types = [type(m) for m in subs[0]["middleware"]]
|
|
assert ContextEditingMiddleware in middleware_types
|
|
|
|
|
|
@patch(
|
|
"EvoScientist.middleware.create_tool_selector_middleware",
|
|
return_value=[MagicMock(), MagicMock()],
|
|
)
|
|
@patch("EvoScientist.EvoScientist._ensure_chat_model")
|
|
@patch("EvoScientist.EvoScientist._ensure_config")
|
|
def test_context_editing_before_overflow_mapper(mock_config, mock_model, mock_ts):
|
|
mock_model.return_value = MagicMock(profile={"max_input_tokens": 200_000})
|
|
cfg = MagicMock()
|
|
cfg.enable_ask_user = False
|
|
cfg.auto_approve = False
|
|
cfg.auxiliary_model = ""
|
|
cfg.auxiliary_provider = ""
|
|
mock_config.return_value = cfg
|
|
|
|
from EvoScientist.EvoScientist import _get_default_middleware
|
|
|
|
mw = _get_default_middleware()
|
|
type_names = [type(m).__name__ for m in mw]
|
|
|
|
ce_idx = type_names.index("ContextEditingMiddleware")
|
|
co_idx = type_names.index("ContextOverflowMapperMiddleware")
|
|
assert ce_idx < co_idx, (
|
|
"ContextEditingMiddleware should come before ContextOverflowMapperMiddleware"
|
|
)
|