test(u8): add GEO business integration tests for YAML configs and agent creation
41 tests covering: - 8 YAML config file loading and validation - ConfigDrivenAgent creation from YAML (llm_generate/tool_call/custom) - Custom handler routing (citation/monitor/schema) - Tool registration completeness - Adapter compatibility (unique names/types, no task overlap)
This commit is contained in:
parent
35f84fd770
commit
133ae3927e
|
|
@ -0,0 +1,314 @@
|
|||
"""U8 GEO 适配层集成测试
|
||||
|
||||
验证 YAML 配置文件、ConfigDrivenAgent 创建、Custom Handler 路由等。
|
||||
测试在 fischer-agentkit 环境中运行,不依赖 GEO 业务代码。
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
from agentkit.core.config_driven import AgentConfig, ConfigDrivenAgent
|
||||
from agentkit.core.protocol import TaskMessage, TaskStatus
|
||||
from agentkit.tools.function_tool import FunctionTool
|
||||
from agentkit.tools.registry import ToolRegistry
|
||||
|
||||
|
||||
CONFIGS_DIR = Path(__file__).parent.parent.parent.parent / "geo" / "backend" / "app" / "agent_framework" / "agents" / "configs"
|
||||
|
||||
|
||||
class TestYAMLConfigLoading:
|
||||
"""测试 YAML 配置文件加载"""
|
||||
|
||||
@pytest.mark.parametrize("yaml_file", [
|
||||
"citation_detector.yaml",
|
||||
"content_generator.yaml",
|
||||
"deai_agent.yaml",
|
||||
"geo_optimizer.yaml",
|
||||
"monitor.yaml",
|
||||
"schema_advisor.yaml",
|
||||
"competitor_analyzer.yaml",
|
||||
"trend_agent.yaml",
|
||||
])
|
||||
def test_yaml_file_exists(self, yaml_file):
|
||||
path = CONFIGS_DIR / yaml_file
|
||||
assert path.exists(), f"Config file {yaml_file} not found at {path}"
|
||||
|
||||
@pytest.mark.parametrize("yaml_file", [
|
||||
"citation_detector.yaml",
|
||||
"content_generator.yaml",
|
||||
"deai_agent.yaml",
|
||||
"geo_optimizer.yaml",
|
||||
"monitor.yaml",
|
||||
"schema_advisor.yaml",
|
||||
"competitor_analyzer.yaml",
|
||||
"trend_agent.yaml",
|
||||
])
|
||||
def test_yaml_valid_structure(self, yaml_file):
|
||||
path = CONFIGS_DIR / yaml_file
|
||||
with open(path) as f:
|
||||
data = yaml.safe_load(f)
|
||||
assert isinstance(data, dict)
|
||||
assert "name" in data
|
||||
assert "agent_type" in data
|
||||
assert "task_mode" in data
|
||||
assert "supported_tasks" in data
|
||||
assert data["task_mode"] in {"llm_generate", "tool_call", "custom"}
|
||||
|
||||
@pytest.mark.parametrize("yaml_file", [
|
||||
"citation_detector.yaml",
|
||||
"content_generator.yaml",
|
||||
"deai_agent.yaml",
|
||||
"geo_optimizer.yaml",
|
||||
"monitor.yaml",
|
||||
"schema_advisor.yaml",
|
||||
"competitor_analyzer.yaml",
|
||||
"trend_agent.yaml",
|
||||
])
|
||||
def test_yaml_to_agent_config(self, yaml_file):
|
||||
path = CONFIGS_DIR / yaml_file
|
||||
config = AgentConfig.from_yaml(str(path))
|
||||
assert config.name
|
||||
assert config.agent_type
|
||||
assert config.task_mode
|
||||
assert len(config.supported_tasks) > 0
|
||||
|
||||
def test_llm_generate_agents_have_prompt(self):
|
||||
llm_agents = ["content_generator.yaml", "deai_agent.yaml", "geo_optimizer.yaml"]
|
||||
for yaml_file in llm_agents:
|
||||
path = CONFIGS_DIR / yaml_file
|
||||
config = AgentConfig.from_yaml(str(path))
|
||||
assert config.prompt, f"{yaml_file}: llm_generate mode requires prompt"
|
||||
assert "identity" in config.prompt
|
||||
|
||||
def test_custom_agents_have_handler(self):
|
||||
custom_agents = ["citation_detector.yaml", "monitor.yaml", "schema_advisor.yaml"]
|
||||
for yaml_file in custom_agents:
|
||||
path = CONFIGS_DIR / yaml_file
|
||||
config = AgentConfig.from_yaml(str(path))
|
||||
assert config.custom_handler, f"{yaml_file}: custom mode requires custom_handler"
|
||||
|
||||
def test_tool_call_agents_have_tools(self):
|
||||
tool_agents = ["competitor_analyzer.yaml", "trend_agent.yaml"]
|
||||
for yaml_file in tool_agents:
|
||||
path = CONFIGS_DIR / yaml_file
|
||||
config = AgentConfig.from_yaml(str(path))
|
||||
assert config.tools, f"{yaml_file}: tool_call mode requires tools list"
|
||||
|
||||
|
||||
class TestConfigDrivenAgentCreation:
|
||||
"""测试从 YAML 创建 ConfigDrivenAgent"""
|
||||
|
||||
def test_create_llm_generate_agent(self):
|
||||
config = AgentConfig.from_yaml(str(CONFIGS_DIR / "content_generator.yaml"))
|
||||
tool_registry = ToolRegistry()
|
||||
agent = ConfigDrivenAgent(config=config, tool_registry=tool_registry)
|
||||
assert agent.name == "content_generator"
|
||||
assert agent.agent_type == "content_generation"
|
||||
assert agent.prompt_template is not None
|
||||
|
||||
def test_create_tool_call_agent(self):
|
||||
config = AgentConfig.from_yaml(str(CONFIGS_DIR / "competitor_analyzer.yaml"))
|
||||
tool_registry = ToolRegistry()
|
||||
|
||||
async def mock_analyze(**kwargs):
|
||||
return {"result": "mock"}
|
||||
|
||||
tool_registry.register(
|
||||
FunctionTool(name="competitor_analyze", description="mock", func=mock_analyze)
|
||||
)
|
||||
tool_registry.register(
|
||||
FunctionTool(name="competitor_gap_analysis", description="mock", func=mock_analyze)
|
||||
)
|
||||
|
||||
agent = ConfigDrivenAgent(config=config, tool_registry=tool_registry)
|
||||
assert agent.name == "competitor_analyzer"
|
||||
assert len(agent._tools) == 2
|
||||
|
||||
def test_create_custom_agent(self):
|
||||
config = AgentConfig.from_yaml(str(CONFIGS_DIR / "citation_detector.yaml"))
|
||||
|
||||
async def mock_handler(task):
|
||||
return {"mock": True}
|
||||
|
||||
custom_handlers = {
|
||||
"app.agent_framework.agents.custom_handlers.citation_handler.handle_citation_task": mock_handler,
|
||||
}
|
||||
|
||||
agent = ConfigDrivenAgent(config=config, custom_handlers=custom_handlers)
|
||||
assert agent.name == "citation_detector"
|
||||
|
||||
def test_create_all_8_agents(self):
|
||||
"""验证所有 8 个 Agent 都能成功创建"""
|
||||
for yaml_file in CONFIGS_DIR.glob("*.yaml"):
|
||||
config = AgentConfig.from_yaml(str(yaml_file))
|
||||
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_citation_handler_routing(self):
|
||||
config = AgentConfig.from_yaml(str(CONFIGS_DIR / "citation_detector.yaml"))
|
||||
|
||||
call_log = []
|
||||
|
||||
async def mock_handler(task):
|
||||
call_log.append(task.task_type)
|
||||
return {"mock": True, "task_type": task.task_type}
|
||||
|
||||
custom_handlers = {
|
||||
"app.agent_framework.agents.custom_handlers.citation_handler.handle_citation_task": mock_handler,
|
||||
}
|
||||
|
||||
agent = ConfigDrivenAgent(config=config, custom_handlers=custom_handlers)
|
||||
task = TaskMessage(
|
||||
task_id="test-1",
|
||||
agent_name="citation_detector",
|
||||
task_type="citation_detect",
|
||||
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 "citation_detect" in call_log
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_monitor_handler_routing(self):
|
||||
config = AgentConfig.from_yaml(str(CONFIGS_DIR / "monitor.yaml"))
|
||||
|
||||
async def mock_handler(task):
|
||||
return {"brand_id": task.input_data.get("brand_id"), "reports": []}
|
||||
|
||||
custom_handlers = {
|
||||
"app.agent_framework.agents.custom_handlers.monitor_handler.handle_monitor_task": mock_handler,
|
||||
}
|
||||
|
||||
agent = ConfigDrivenAgent(config=config, custom_handlers=custom_handlers)
|
||||
task = TaskMessage(
|
||||
task_id="test-2",
|
||||
agent_name="monitor",
|
||||
task_type="monitor_track",
|
||||
priority=0,
|
||||
input_data={"brand_id": "test-brand-id"},
|
||||
callback_url=None,
|
||||
created_at=datetime.now(timezone.utc),
|
||||
)
|
||||
result = await agent.execute(task)
|
||||
assert result.status == TaskStatus.COMPLETED
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_schema_handler_routing(self):
|
||||
config = AgentConfig.from_yaml(str(CONFIGS_DIR / "schema_advisor.yaml"))
|
||||
|
||||
async def mock_handler(task):
|
||||
return {"brand_id": task.input_data.get("brand_id"), "suggestions": [], "total": 0}
|
||||
|
||||
custom_handlers = {
|
||||
"app.agent_framework.agents.custom_handlers.schema_handler.handle_schema_task": mock_handler,
|
||||
}
|
||||
|
||||
agent = ConfigDrivenAgent(config=config, custom_handlers=custom_handlers)
|
||||
task = TaskMessage(
|
||||
task_id="test-3",
|
||||
agent_name="schema_advisor",
|
||||
task_type="schema_advise",
|
||||
priority=0,
|
||||
input_data={"brand_id": "test-brand-id"},
|
||||
callback_url=None,
|
||||
created_at=datetime.now(timezone.utc),
|
||||
)
|
||||
result = await agent.execute(task)
|
||||
assert result.status == TaskStatus.COMPLETED
|
||||
|
||||
|
||||
class TestToolRegistration:
|
||||
"""测试 Tool 注册完整性"""
|
||||
|
||||
def test_all_yaml_referenced_tools_registered(self):
|
||||
registry = ToolRegistry()
|
||||
all_tool_names = set()
|
||||
for yaml_file in CONFIGS_DIR.glob("*.yaml"):
|
||||
config = AgentConfig.from_yaml(str(yaml_file))
|
||||
all_tool_names.update(config.tools)
|
||||
|
||||
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_yaml_configs_count(self):
|
||||
yaml_files = list(CONFIGS_DIR.glob("*.yaml"))
|
||||
assert len(yaml_files) == 8, f"Expected 8 YAML configs, found {len(yaml_files)}"
|
||||
|
||||
def test_all_agent_names_unique(self):
|
||||
names = []
|
||||
for yaml_file in CONFIGS_DIR.glob("*.yaml"):
|
||||
config = AgentConfig.from_yaml(str(yaml_file))
|
||||
names.append(config.name)
|
||||
assert len(names) == len(set(names)), f"Duplicate agent names: {names}"
|
||||
|
||||
def test_all_agent_types_unique(self):
|
||||
types = []
|
||||
for yaml_file in CONFIGS_DIR.glob("*.yaml"):
|
||||
config = AgentConfig.from_yaml(str(yaml_file))
|
||||
types.append(config.agent_type)
|
||||
assert len(types) == len(set(types)), f"Duplicate agent types: {types}"
|
||||
|
||||
def test_supported_tasks_no_overlap(self):
|
||||
all_tasks = {}
|
||||
for yaml_file in CONFIGS_DIR.glob("*.yaml"):
|
||||
config = AgentConfig.from_yaml(str(yaml_file))
|
||||
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
|
||||
|
||||
def test_migration_script_exists(self):
|
||||
migration_path = (
|
||||
Path(__file__).parent.parent.parent.parent
|
||||
/ "geo" / "backend" / "alembic" / "versions" / "b001_agentkit_extension.py"
|
||||
)
|
||||
assert migration_path.exists(), "Migration script not found"
|
||||
|
||||
def test_adapter_module_exists(self):
|
||||
adapter_path = (
|
||||
Path(__file__).parent.parent.parent.parent
|
||||
/ "geo" / "backend" / "app" / "agent_framework" / "adapter.py"
|
||||
)
|
||||
assert adapter_path.exists(), "adapter.py not found"
|
||||
Loading…
Reference in New Issue