c683f6e739
Docker / build (push) Has been cancelled
Build / build (push) Has been cancelled
Lint / ruff (push) Has been cancelled
Test / pytest (ubuntu-latest, 3.11) (push) Has been cancelled
Test / pytest (ubuntu-latest, 3.12) (push) Has been cancelled
Test / pytest (windows-latest, 3.11) (push) Has been cancelled
Test / pytest (windows-latest, 3.12) (push) Has been cancelled
Add bounded document ingestion, controlled web search, recoverable session support, subagent timeouts, and the native sandbox runtime contract. Unify package versioning and add release-focused regression coverage.
196 lines
6.8 KiB
Python
196 lines
6.8 KiB
Python
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from EvoScientist.llm.contracts import EvoRuntimeError
|
|
from EvoScientist.web_runtime import _ToolRegistryFenceMiddleware
|
|
|
|
|
|
def test_web_registry_describes_tavily_as_controlled_live_search(monkeypatch):
|
|
monkeypatch.setenv("TAVILY_API_KEY", "test-key")
|
|
|
|
from EvoScientist.web_runtime import web_tool_registry_manifest
|
|
|
|
manifest, _revision = web_tool_registry_manifest()
|
|
tavily = next(item for item in manifest if item["name"] == "tavily_search")
|
|
|
|
assert "live public web" in tavily["description"].lower()
|
|
assert "execute" in tavily["description"].lower()
|
|
|
|
|
|
def test_base_kwargs_install_tavily_on_main_agent(monkeypatch):
|
|
import EvoScientist.EvoScientist as agent_module
|
|
|
|
monkeypatch.setenv("TAVILY_API_KEY", "test-key")
|
|
monkeypatch.setattr(agent_module, "_ensure_general_purpose_subagent", lambda _subs: None)
|
|
monkeypatch.setattr(agent_module, "_inject_subagent_middleware", lambda *_args, **_kwargs: None)
|
|
monkeypatch.setattr(agent_module, "_maybe_swap_async_subagents", lambda subs, *_args, **_kwargs: subs)
|
|
monkeypatch.setattr(agent_module, "_configured_system_prompt", lambda _cfg: "prompt")
|
|
monkeypatch.setattr("EvoScientist.utils.load_subagents", lambda *_args, **_kwargs: [])
|
|
|
|
kwargs = agent_module._build_base_kwargs(
|
|
object(),
|
|
[],
|
|
cfg=object(),
|
|
chat_model=object(),
|
|
workspace_dir="/workspace",
|
|
)
|
|
|
|
assert "tavily_search" in {getattr(tool, "name", "") for tool in kwargs["tools"]}
|
|
|
|
|
|
def _stub_agent_build(monkeypatch, agent_module, subagents=None):
|
|
monkeypatch.setattr(agent_module, "_ensure_general_purpose_subagent", lambda _subs: None)
|
|
monkeypatch.setattr(agent_module, "_inject_subagent_middleware", lambda *_args, **_kwargs: None)
|
|
monkeypatch.setattr(agent_module, "_maybe_swap_async_subagents", lambda subs, *_args, **_kwargs: subs)
|
|
monkeypatch.setattr(agent_module, "_configured_system_prompt", lambda _cfg: "prompt")
|
|
monkeypatch.setattr(
|
|
"EvoScientist.utils.load_subagents",
|
|
lambda *_args, **_kwargs: list(subagents or []),
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"reserved_name",
|
|
[
|
|
"skill_manager",
|
|
"execute",
|
|
"start_async_task",
|
|
"check_async_task",
|
|
"update_async_task",
|
|
"cancel_async_task",
|
|
"list_async_tasks",
|
|
],
|
|
)
|
|
def test_mcp_cannot_override_reserved_tool(monkeypatch, reserved_name):
|
|
from types import SimpleNamespace
|
|
|
|
import EvoScientist.EvoScientist as agent_module
|
|
|
|
monkeypatch.delenv("TAVILY_API_KEY", raising=False)
|
|
monkeypatch.setattr(
|
|
agent_module,
|
|
"_load_mcp_tools_cached",
|
|
lambda **_kwargs: {"main": [SimpleNamespace(name=reserved_name)]},
|
|
)
|
|
_stub_agent_build(monkeypatch, agent_module)
|
|
|
|
with pytest.raises(EvoRuntimeError, match="TOOL_REGISTRY_CONFLICT"):
|
|
agent_module.load_mcp_and_build_kwargs(
|
|
object(),
|
|
[],
|
|
cfg=object(),
|
|
chat_model=object(),
|
|
workspace_dir="/workspace",
|
|
)
|
|
|
|
|
|
def test_mcp_cannot_duplicate_existing_subagent_tool(monkeypatch):
|
|
from types import SimpleNamespace
|
|
|
|
import EvoScientist.EvoScientist as agent_module
|
|
|
|
existing = SimpleNamespace(name="shared_search")
|
|
injected = SimpleNamespace(name="shared_search")
|
|
monkeypatch.delenv("TAVILY_API_KEY", raising=False)
|
|
monkeypatch.setattr(
|
|
agent_module,
|
|
"_load_mcp_tools_cached",
|
|
lambda **_kwargs: {"research-agent": [injected]},
|
|
)
|
|
_stub_agent_build(
|
|
monkeypatch,
|
|
agent_module,
|
|
subagents=[{"name": "research-agent", "tools": [existing]}],
|
|
)
|
|
|
|
with pytest.raises(EvoRuntimeError, match="TOOL_REGISTRY_CONFLICT"):
|
|
agent_module.load_mcp_and_build_kwargs(
|
|
object(),
|
|
[],
|
|
cfg=object(),
|
|
chat_model=object(),
|
|
workspace_dir="/workspace",
|
|
)
|
|
|
|
|
|
def test_mcp_cannot_override_builtin_tavily(monkeypatch):
|
|
from types import SimpleNamespace
|
|
|
|
import EvoScientist.EvoScientist as agent_module
|
|
|
|
monkeypatch.setenv("TAVILY_API_KEY", "test-key")
|
|
monkeypatch.setattr(
|
|
agent_module,
|
|
"_load_mcp_tools_cached",
|
|
lambda **_kwargs: {"main": [SimpleNamespace(name="tavily_search")]},
|
|
)
|
|
monkeypatch.setattr(agent_module, "_ensure_general_purpose_subagent", lambda _subs: None)
|
|
monkeypatch.setattr(agent_module, "_inject_subagent_middleware", lambda *_args, **_kwargs: None)
|
|
monkeypatch.setattr(agent_module, "_maybe_swap_async_subagents", lambda subs, *_args, **_kwargs: subs)
|
|
monkeypatch.setattr(agent_module, "_configured_system_prompt", lambda _cfg: "prompt")
|
|
monkeypatch.setattr("EvoScientist.utils.load_subagents", lambda *_args, **_kwargs: [])
|
|
|
|
with pytest.raises(EvoRuntimeError, match="TOOL_REGISTRY_CONFLICT"):
|
|
agent_module.load_mcp_and_build_kwargs(
|
|
object(),
|
|
[],
|
|
cfg=object(),
|
|
chat_model=object(),
|
|
workspace_dir="/workspace",
|
|
)
|
|
|
|
|
|
def test_same_mcp_tool_can_be_routed_to_multiple_agents(monkeypatch):
|
|
from types import SimpleNamespace
|
|
|
|
import EvoScientist.EvoScientist as agent_module
|
|
|
|
shared_main = SimpleNamespace(name="shared_search")
|
|
shared_research = SimpleNamespace(name="shared_search")
|
|
monkeypatch.delenv("TAVILY_API_KEY", raising=False)
|
|
monkeypatch.setattr(
|
|
agent_module,
|
|
"_load_mcp_tools_cached",
|
|
lambda **_kwargs: {
|
|
"main": [shared_main],
|
|
"research-agent": [shared_research],
|
|
},
|
|
)
|
|
monkeypatch.setattr(agent_module, "_ensure_general_purpose_subagent", lambda _subs: None)
|
|
monkeypatch.setattr(agent_module, "_inject_subagent_middleware", lambda *_args, **_kwargs: None)
|
|
monkeypatch.setattr(agent_module, "_maybe_swap_async_subagents", lambda subs, *_args, **_kwargs: subs)
|
|
monkeypatch.setattr(agent_module, "_configured_system_prompt", lambda _cfg: "prompt")
|
|
monkeypatch.setattr(
|
|
"EvoScientist.utils.load_subagents",
|
|
lambda *_args, **_kwargs: [
|
|
{"name": "research-agent", "tools": []}
|
|
],
|
|
)
|
|
|
|
kwargs = agent_module.load_mcp_and_build_kwargs(
|
|
object(),
|
|
[],
|
|
cfg=object(),
|
|
chat_model=object(),
|
|
workspace_dir="/workspace",
|
|
)
|
|
|
|
assert shared_main in kwargs["tools"]
|
|
research = next(
|
|
subagent for subagent in kwargs["subagents"]
|
|
if subagent["name"] == "research-agent"
|
|
)
|
|
assert shared_research in research["tools"]
|
|
|
|
|
|
def test_tool_dispatch_fence_rejects_changed_registry(monkeypatch):
|
|
monkeypatch.setattr(
|
|
"EvoScientist.web_runtime.web_tool_registry_manifest",
|
|
lambda: ((), "registry-v2"),
|
|
)
|
|
middleware = _ToolRegistryFenceMiddleware("registry-v1")
|
|
|
|
with pytest.raises(EvoRuntimeError, match="TOOL_REGISTRY_STALE"):
|
|
middleware._require_current()
|