fischer-agentkit/tests/unit/test_u8_geo_integration.py

301 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""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