"""U8 GEO 适配层集成测试 验证 YAML 配置文件加载、ConfigDrivenAgent 创建、Custom Handler 路由等。 使用 agentkit 自带的 example_skill.yaml 和内联配置,不依赖 GEO 项目路径。 """ import pytest import yaml from datetime import datetime, timezone from pathlib import Path from unittest.mock import patch from agentkit.core.config_driven import AgentConfig, ConfigDrivenAgent from agentkit.core.protocol import TaskMessage, TaskStatus from agentkit.skills.base import SkillConfig from agentkit.tools.function_tool import FunctionTool from agentkit.tools.registry import ToolRegistry # Use agentkit's own skills directory SKILLS_DIR = Path(__file__).parent.parent.parent / "configs" / "skills" def _make_llm_generate_config() -> dict: """Inline config for llm_generate mode agent.""" return { "name": "test_llm_agent", "agent_type": "test_llm", "task_mode": "llm_generate", "supported_tasks": ["test_llm_task"], "prompt": { "identity": "You are a test assistant.", "instruction": "Respond to the user's request.", }, } def _make_tool_call_config() -> dict: """Inline config for tool_call mode agent.""" return { "name": "test_tool_agent", "agent_type": "test_tool", "task_mode": "tool_call", "supported_tasks": ["test_tool_task"], "tools": ["mock_tool_a", "mock_tool_b"], } def _make_custom_config() -> dict: """Inline config for custom mode agent.""" return { "name": "test_custom_agent", "agent_type": "test_custom", "task_mode": "custom", "supported_tasks": ["test_custom_task"], "custom_handler": "test.handlers.mock_handler", } class TestYAMLConfigLoading: """测试 YAML 配置文件加载(使用内联配置,不依赖 GEO)""" def test_llm_generate_config_structure(self): config = AgentConfig.from_dict(_make_llm_generate_config()) assert config.name == "test_llm_agent" assert config.agent_type == "test_llm" assert config.task_mode == "llm_generate" assert len(config.supported_tasks) > 0 assert config.prompt is not None def test_tool_call_config_structure(self): config = AgentConfig.from_dict(_make_tool_call_config()) assert config.name == "test_tool_agent" assert config.task_mode == "tool_call" assert len(config.tools) == 2 def test_custom_config_structure(self): config = AgentConfig.from_dict(_make_custom_config()) assert config.name == "test_custom_agent" assert config.task_mode == "custom" assert config.custom_handler == "test.handlers.mock_handler" def test_llm_generate_agents_have_prompt(self): config = AgentConfig.from_dict(_make_llm_generate_config()) assert config.prompt, "llm_generate mode requires prompt" assert "identity" in config.prompt def test_custom_agents_have_handler(self): config = AgentConfig.from_dict(_make_custom_config()) assert config.custom_handler, "custom mode requires custom_handler" def test_tool_call_agents_have_tools(self): config = AgentConfig.from_dict(_make_tool_call_config()) assert config.tools, "tool_call mode requires tools list" def test_example_skill_yaml_if_exists(self): """Test loading example_skill.yaml if it exists in configs/skills/.""" example_path = SKILLS_DIR / "example_skill.yaml" if not example_path.exists(): pytest.skip("example_skill.yaml not found in configs/skills/") config = SkillConfig.from_yaml(str(example_path)) assert config.name assert config.agent_type class TestConfigDrivenAgentCreation: """测试从配置创建 ConfigDrivenAgent""" def test_create_llm_generate_agent(self): config = AgentConfig.from_dict(_make_llm_generate_config()) tool_registry = ToolRegistry() agent = ConfigDrivenAgent(config=config, tool_registry=tool_registry) assert agent.name == "test_llm_agent" assert agent.agent_type == "test_llm" assert agent.prompt_template is not None def test_create_tool_call_agent(self): config = AgentConfig.from_dict(_make_tool_call_config()) tool_registry = ToolRegistry() async def mock_func(**kwargs): return {"result": "mock"} tool_registry.register( FunctionTool(name="mock_tool_a", description="mock", func=mock_func) ) tool_registry.register( FunctionTool(name="mock_tool_b", description="mock", func=mock_func) ) agent = ConfigDrivenAgent(config=config, tool_registry=tool_registry) assert agent.name == "test_tool_agent" assert len(agent._tools) == 2 def test_create_custom_agent(self): config = AgentConfig.from_dict(_make_custom_config()) async def mock_handler(task): return {"mock": True} custom_handlers = { "test.handlers.mock_handler": mock_handler, } agent = ConfigDrivenAgent(config=config, custom_handlers=custom_handlers) assert agent.name == "test_custom_agent" def test_create_all_mode_agents(self): """验证三种模式的 Agent 都能成功创建""" configs = [_make_llm_generate_config(), _make_tool_call_config(), _make_custom_config()] for cfg_dict in configs: config = AgentConfig.from_dict(cfg_dict) tool_registry = ToolRegistry() # 为 tool_call 模式注册 mock 工具 for tool_name in config.tools: async def mock_func(**kwargs): return {"mock": True} tool_registry.register( FunctionTool(name=tool_name, description=f"Mock {tool_name}", func=mock_func) ) # 为 custom 模式提供 mock handler custom_handlers = {} if config.custom_handler: async def mock_handler(task): return {"mock": True} custom_handlers[config.custom_handler] = mock_handler agent = ConfigDrivenAgent( config=config, tool_registry=tool_registry, custom_handlers=custom_handlers, ) assert agent.name == config.name class TestCustomHandlerRouting: """测试 Custom Handler 路由""" @pytest.mark.asyncio async def test_custom_handler_routing(self): config = AgentConfig.from_dict(_make_custom_config()) call_log = [] async def mock_handler(task): call_log.append(task.task_type) return {"mock": True, "task_type": task.task_type} custom_handlers = { "test.handlers.mock_handler": mock_handler, } agent = ConfigDrivenAgent(config=config, custom_handlers=custom_handlers) task = TaskMessage( task_id="test-1", agent_name="test_custom_agent", task_type="test_custom_task", priority=0, input_data={"query_id": "test-qid"}, callback_url=None, created_at=datetime.now(timezone.utc), ) result = await agent.execute(task) assert result.status == TaskStatus.COMPLETED assert "test_custom_task" in call_log class TestSkillConfigV2: """测试 SkillConfig v2 字段""" def test_skill_config_from_dict(self): data = { "name": "test_skill", "agent_type": "test", "task_mode": "llm_generate", "supported_tasks": ["test"], "prompt": {"identity": "test"}, "intent": { "keywords": ["test", "demo"], "description": "A test skill", }, "quality_gate": { "required_fields": ["output"], "min_word_count": 10, }, "execution_mode": "react", "max_steps": 3, "evolution": { "enabled": True, "reflect_on_failure": False, }, } config = SkillConfig.from_dict(data) assert config.name == "test_skill" assert config.intent.keywords == ["test", "demo"] assert config.quality_gate.required_fields == ["output"] assert config.execution_mode == "react" assert config.max_steps == 3 assert config.evolution.enabled is True def test_skill_config_backward_compatible(self): """旧 YAML 无 v2 字段时自动填充默认值""" data = { "name": "legacy_skill", "agent_type": "legacy", "task_mode": "llm_generate", "supported_tasks": ["legacy"], "prompt": {"identity": "Legacy skill"}, } config = SkillConfig.from_dict(data) assert config.name == "legacy_skill" assert config.intent.keywords == [] assert config.quality_gate.required_fields == [] assert config.execution_mode == "react" # default assert config.evolution.enabled is False # default class TestToolRegistration: """测试 Tool 注册完整性""" def test_all_referenced_tools_registered(self): registry = ToolRegistry() all_tool_names = {"mock_tool_a", "mock_tool_b", "mock_tool_c"} for tool_name in all_tool_names: async def mock_func(**kwargs): return {"mock": True} registry.register( FunctionTool(name=tool_name, description=f"Mock {tool_name}", func=mock_func) ) for tool_name in all_tool_names: assert registry.has_tool(tool_name), f"Tool '{tool_name}' not registered" class TestAdapterCompatibility: """测试适配层兼容性""" def test_all_agent_names_unique(self): configs = [_make_llm_generate_config(), _make_tool_call_config(), _make_custom_config()] names = [AgentConfig.from_dict(c).name for c in configs] assert len(names) == len(set(names)), f"Duplicate agent names: {names}" def test_all_agent_types_unique(self): configs = [_make_llm_generate_config(), _make_tool_call_config(), _make_custom_config()] types = [AgentConfig.from_dict(c).agent_type for c in configs] assert len(types) == len(set(types)), f"Duplicate agent types: {types}" def test_supported_tasks_no_overlap(self): configs = [_make_llm_generate_config(), _make_tool_call_config(), _make_custom_config()] all_tasks = {} for cfg_dict in configs: config = AgentConfig.from_dict(cfg_dict) for task in config.supported_tasks: if task in all_tasks: assert False, f"Task '{task}' defined in both '{all_tasks[task]}' and '{config.name}'" all_tasks[task] = config.name