175 lines
6.9 KiB
Python
175 lines
6.9 KiB
Python
"""U5: SkillConfig 扩展 + 专业 Agent 执行模式路由测试"""
|
|
|
|
import os
|
|
import pytest
|
|
from datetime import datetime, timezone
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
import yaml
|
|
|
|
from agentkit.skills.base import SkillConfig
|
|
from agentkit.core.exceptions import ConfigValidationError
|
|
from agentkit.core.protocol import TaskMessage
|
|
|
|
|
|
def _make_task(**overrides):
|
|
defaults = dict(
|
|
task_id="t1",
|
|
agent_name="test",
|
|
task_type="test",
|
|
priority=1,
|
|
input_data={"query": "test"},
|
|
callback_url=None,
|
|
created_at=datetime.now(timezone.utc),
|
|
)
|
|
defaults.update(overrides)
|
|
return TaskMessage(**defaults)
|
|
|
|
|
|
class TestSkillConfigExecutionModes:
|
|
"""SkillConfig.VALID_EXECUTION_MODES 扩展测试"""
|
|
|
|
def test_rewoo_is_valid_mode(self):
|
|
config = SkillConfig(name="test_rewoo", agent_type="test", execution_mode="rewoo",
|
|
prompt={"identity": "test", "instructions": "test"})
|
|
assert config.execution_mode == "rewoo"
|
|
|
|
def test_plan_exec_is_valid_mode(self):
|
|
config = SkillConfig(name="test_plan_exec", agent_type="test", execution_mode="plan_exec",
|
|
prompt={"identity": "test", "instructions": "test"})
|
|
assert config.execution_mode == "plan_exec"
|
|
|
|
def test_reflexion_is_valid_mode(self):
|
|
config = SkillConfig(name="test_reflexion", agent_type="test", execution_mode="reflexion",
|
|
prompt={"identity": "test", "instructions": "test"})
|
|
assert config.execution_mode == "reflexion"
|
|
|
|
def test_existing_modes_still_valid(self):
|
|
for mode in ("react", "direct", "custom"):
|
|
config = SkillConfig(name=f"test_{mode}", agent_type="test", execution_mode=mode,
|
|
prompt={"identity": "test", "instructions": "test"})
|
|
assert config.execution_mode == mode
|
|
|
|
def test_invalid_mode_raises_error(self):
|
|
with pytest.raises(ConfigValidationError):
|
|
SkillConfig(name="test_invalid", agent_type="test", execution_mode="nonexistent",
|
|
prompt={"identity": "test", "instructions": "test"})
|
|
|
|
def test_all_six_modes_in_valid_set(self):
|
|
expected = {"react", "direct", "custom", "rewoo", "plan_exec", "reflexion"}
|
|
assert SkillConfig.VALID_EXECUTION_MODES == expected
|
|
|
|
|
|
class TestYAMLConfigLoading:
|
|
"""专业 Agent YAML 配置加载测试"""
|
|
|
|
YAML_DIR = "/Users/Chiguyong/Code/Fischer/fischer-agentkit/configs/skills"
|
|
|
|
def _load_yaml(self, filename):
|
|
path = os.path.join(self.YAML_DIR, filename)
|
|
with open(path) as f:
|
|
return yaml.safe_load(f)
|
|
|
|
def test_rewoo_agent_yaml_loads(self):
|
|
data = self._load_yaml("rewoo_agent.yaml")
|
|
config = SkillConfig(**data)
|
|
assert config.execution_mode == "rewoo"
|
|
assert config.agent_type == "parallel_data_fetch"
|
|
|
|
def test_plan_exec_agent_yaml_loads(self):
|
|
data = self._load_yaml("plan_exec_agent.yaml")
|
|
config = SkillConfig(**data)
|
|
assert config.execution_mode == "plan_exec"
|
|
assert config.agent_type == "structured_planning"
|
|
|
|
def test_reflexion_agent_yaml_loads(self):
|
|
data = self._load_yaml("reflexion_agent.yaml")
|
|
config = SkillConfig(**data)
|
|
assert config.execution_mode == "reflexion"
|
|
assert config.agent_type == "high_precision"
|
|
|
|
def test_react_agent_yaml_loads(self):
|
|
data = self._load_yaml("react_agent.yaml")
|
|
config = SkillConfig(**data)
|
|
assert config.execution_mode == "react"
|
|
assert config.agent_type == "dynamic_tool_chain"
|
|
|
|
def test_direct_agent_yaml_loads(self):
|
|
data = self._load_yaml("direct_agent.yaml")
|
|
config = SkillConfig(**data)
|
|
assert config.execution_mode == "direct"
|
|
assert config.agent_type == "simple_generation"
|
|
|
|
def test_different_models_per_agent(self):
|
|
direct_data = self._load_yaml("direct_agent.yaml")
|
|
assert direct_data["llm"]["model"] == "openai/gpt-4o-mini"
|
|
|
|
plan_data = self._load_yaml("plan_exec_agent.yaml")
|
|
assert plan_data["llm"]["model"] == "anthropic/claude-opus-4-20250514"
|
|
|
|
react_data = self._load_yaml("react_agent.yaml")
|
|
assert react_data["llm"]["model"] == "anthropic/claude-sonnet-4-20250514"
|
|
|
|
def test_direct_agent_has_no_tools(self):
|
|
data = self._load_yaml("direct_agent.yaml")
|
|
assert data["tools"] == []
|
|
|
|
def test_capabilities_parsed(self):
|
|
data = self._load_yaml("react_agent.yaml")
|
|
config = SkillConfig(**data)
|
|
cap_tags = [c.tag if hasattr(c, 'tag') else c for c in config.capabilities]
|
|
assert "dynamic_adaptation" in cap_tags
|
|
|
|
|
|
class TestConfigDrivenAgentRouting:
|
|
"""ConfigDrivenAgent execution_mode 路由测试"""
|
|
|
|
def _make_agent(self, execution_mode):
|
|
from agentkit.core.config_driven import ConfigDrivenAgent
|
|
from agentkit.llm.gateway import LLMGateway
|
|
|
|
config = SkillConfig(
|
|
name=f"test_{execution_mode}",
|
|
agent_type="test",
|
|
execution_mode=execution_mode,
|
|
prompt={"identity": "test", "instructions": "test"},
|
|
)
|
|
|
|
llm_gateway = MagicMock(spec=LLMGateway)
|
|
llm_gateway.chat = AsyncMock()
|
|
|
|
agent = ConfigDrivenAgent(config=config, llm_gateway=llm_gateway)
|
|
return agent
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_rewoo_routes_to_handle_rewoo(self):
|
|
agent = self._make_agent("rewoo")
|
|
with patch.object(agent, '_handle_rewoo', new_callable=AsyncMock, return_value={"content": "rewoo result"}) as mock:
|
|
result = await agent.handle_task(_make_task())
|
|
mock.assert_called_once()
|
|
assert result == {"content": "rewoo result"}
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_plan_exec_routes_to_handle_plan_exec(self):
|
|
agent = self._make_agent("plan_exec")
|
|
with patch.object(agent, '_handle_plan_exec', new_callable=AsyncMock, return_value={"content": "plan_exec result"}) as mock:
|
|
result = await agent.handle_task(_make_task())
|
|
mock.assert_called_once()
|
|
assert result == {"content": "plan_exec result"}
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_reflexion_routes_to_handle_reflexion(self):
|
|
agent = self._make_agent("reflexion")
|
|
with patch.object(agent, '_handle_reflexion', new_callable=AsyncMock, return_value={"content": "reflexion result"}) as mock:
|
|
result = await agent.handle_task(_make_task())
|
|
mock.assert_called_once()
|
|
assert result == {"content": "reflexion result"}
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_react_still_routes_correctly(self):
|
|
agent = self._make_agent("react")
|
|
with patch.object(agent, '_handle_react', new_callable=AsyncMock, return_value={"content": "react result"}) as mock:
|
|
result = await agent.handle_task(_make_task())
|
|
mock.assert_called_once()
|
|
assert result == {"content": "react result"}
|