300 lines
12 KiB
Python
300 lines
12 KiB
Python
"""ExpertTeamRouter 单元测试"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from agentkit.experts.config import ExpertConfig, ExpertTemplate
|
|
from agentkit.experts.registry import ExpertTemplateRegistry
|
|
from agentkit.experts.router import (
|
|
ExpertTeamRouter,
|
|
ExpertTeamRoutingResult,
|
|
TEAM_PREFIX_PATTERN,
|
|
)
|
|
|
|
|
|
# ── 辅助函数 ──────────────────────────────────────────────
|
|
|
|
|
|
def _make_template(
|
|
name: str = "test_template",
|
|
persona: str = "测试专家",
|
|
bound_skills: list[str] | None = None,
|
|
) -> ExpertTemplate:
|
|
"""创建测试用 ExpertTemplate 实例"""
|
|
config = ExpertConfig(
|
|
name=name,
|
|
agent_type="expert",
|
|
persona=persona,
|
|
thinking_style="analytical",
|
|
bound_skills=bound_skills or [],
|
|
task_mode="llm_generate",
|
|
prompt={"identity": persona},
|
|
)
|
|
return ExpertTemplate(
|
|
name=name,
|
|
config=config,
|
|
is_builtin=True,
|
|
description=f"{name} 模板",
|
|
)
|
|
|
|
|
|
def _make_registry_with_templates() -> ExpertTemplateRegistry:
|
|
"""创建包含预注册模板的注册中心"""
|
|
registry = ExpertTemplateRegistry()
|
|
registry.register(_make_template("analyst", persona="数据分析师", bound_skills=["data_query"]))
|
|
registry.register(_make_template("strategist", persona="策略专家", bound_skills=["planning"]))
|
|
registry.register(_make_template("reviewer", persona="代码审查员", bound_skills=["code_review"]))
|
|
return registry
|
|
|
|
|
|
# ── TEAM_PREFIX_PATTERN 正则测试 ──────────────────────────
|
|
|
|
|
|
class TestTeamPrefixPattern:
|
|
"""TEAM_PREFIX_PATTERN 正则匹配测试"""
|
|
|
|
def test_match_team_only(self):
|
|
"""@team 前缀匹配"""
|
|
match = TEAM_PREFIX_PATTERN.match("@team 分析这个数据")
|
|
assert match is not None
|
|
assert match.group(1) is None
|
|
assert match.group(2) == "分析这个数据"
|
|
|
|
def test_match_team_with_experts(self):
|
|
"""@team:expert1,expert2 前缀匹配"""
|
|
match = TEAM_PREFIX_PATTERN.match("@team:analyst,strategist 分析这个数据")
|
|
assert match is not None
|
|
assert match.group(1) == "analyst,strategist"
|
|
assert match.group(2) == "分析这个数据"
|
|
|
|
def test_match_team_single_expert(self):
|
|
"""@team:expert 前缀匹配"""
|
|
match = TEAM_PREFIX_PATTERN.match("@team:analyst 分析数据")
|
|
assert match is not None
|
|
assert match.group(1) == "analyst"
|
|
assert match.group(2) == "分析数据"
|
|
|
|
def test_match_team_no_task(self):
|
|
"""@team 无后续任务内容"""
|
|
match = TEAM_PREFIX_PATTERN.match("@team")
|
|
assert match is not None
|
|
assert match.group(1) is None
|
|
assert match.group(2) == ""
|
|
|
|
def test_no_match_without_prefix(self):
|
|
"""无 @team 前缀不匹配"""
|
|
match = TEAM_PREFIX_PATTERN.match("分析这个数据")
|
|
assert match is None
|
|
|
|
def test_no_match_team_in_middle(self):
|
|
"""@team 在中间不匹配(必须开头)"""
|
|
match = TEAM_PREFIX_PATTERN.match("请 @team 分析数据")
|
|
assert match is None
|
|
|
|
def test_match_team_with_leading_whitespace(self):
|
|
"""@team 前有空白字符时正则不匹配(由 resolve() 中 strip() 处理)"""
|
|
match = TEAM_PREFIX_PATTERN.match(" @team:analyst 任务内容")
|
|
# 正则使用 ^ 锚定,前导空白不匹配
|
|
assert match is None
|
|
|
|
|
|
# ── ExpertTeamRoutingResult 默认值测试 ────────────────────
|
|
|
|
|
|
class TestExpertTeamRoutingResult:
|
|
"""ExpertTeamRoutingResult 数据类测试"""
|
|
|
|
def test_default_values(self):
|
|
"""默认值验证"""
|
|
result = ExpertTeamRoutingResult()
|
|
assert result.matched is False
|
|
assert result.team_mode is False
|
|
assert result.specified_experts == []
|
|
assert result.task_content == ""
|
|
assert result.auto_compose is False
|
|
assert result.complexity == 0.0
|
|
assert result.match_method == ""
|
|
|
|
|
|
# ── ExpertTeamRouter.resolve 测试 ──────────────────────────
|
|
|
|
|
|
class TestExpertTeamRouterResolve:
|
|
"""ExpertTeamRouter.resolve 方法测试"""
|
|
|
|
def test_team_prefix_triggers_team_mode(self):
|
|
"""@team 前缀触发团队模式"""
|
|
router = ExpertTeamRouter()
|
|
result = router.resolve("@team 分析这个数据")
|
|
assert result.matched is True
|
|
assert result.team_mode is True
|
|
assert result.match_method == "explicit_team"
|
|
assert result.task_content == "分析这个数据"
|
|
|
|
def test_team_with_experts_specifies_members(self):
|
|
"""@team:analyst,strategist 指定专家成员"""
|
|
router = ExpertTeamRouter()
|
|
result = router.resolve("@team:analyst,strategist 分析数据")
|
|
assert result.matched is True
|
|
assert result.team_mode is True
|
|
assert result.specified_experts == ["analyst", "strategist"]
|
|
assert result.auto_compose is False
|
|
assert result.match_method == "explicit_team"
|
|
|
|
def test_team_no_experts_auto_compose(self):
|
|
"""@team 无指定专家时 auto_compose=True"""
|
|
router = ExpertTeamRouter()
|
|
result = router.resolve("@team 分析数据")
|
|
assert result.matched is True
|
|
assert result.team_mode is True
|
|
assert result.specified_experts == []
|
|
assert result.auto_compose is True
|
|
|
|
def test_team_with_expert_extracts_task(self):
|
|
"""@team:analyst 正确提取任务内容"""
|
|
router = ExpertTeamRouter()
|
|
result = router.resolve("@team:analyst 请分析这份报告")
|
|
assert result.matched is True
|
|
assert result.specified_experts == ["analyst"]
|
|
assert result.task_content == "请分析这份报告"
|
|
|
|
def test_high_complexity_triggers_team_suggestion(self):
|
|
"""高复杂度 (>=0.7) 触发团队模式建议"""
|
|
router = ExpertTeamRouter()
|
|
result = router.resolve("分析这个复杂系统", complexity=0.8)
|
|
assert result.matched is True
|
|
assert result.team_mode is True
|
|
assert result.auto_compose is True
|
|
assert result.match_method == "complexity_suggestion"
|
|
assert result.complexity == 0.8
|
|
|
|
def test_high_complexity_exact_threshold(self):
|
|
"""复杂度恰好等于阈值 0.7 也触发团队模式"""
|
|
router = ExpertTeamRouter()
|
|
result = router.resolve("任务内容", complexity=0.7)
|
|
assert result.matched is True
|
|
assert result.team_mode is True
|
|
assert result.match_method == "complexity_suggestion"
|
|
|
|
def test_low_complexity_no_team_mode(self):
|
|
"""低复杂度 (<0.7) 不触发团队模式"""
|
|
router = ExpertTeamRouter()
|
|
result = router.resolve("简单问题", complexity=0.3)
|
|
assert result.matched is False
|
|
assert result.team_mode is False
|
|
assert result.task_content == "简单问题"
|
|
assert result.complexity == 0.3
|
|
|
|
def test_no_team_prefix_no_complexity(self):
|
|
"""无 @team 前缀且无复杂度时不触发团队模式"""
|
|
router = ExpertTeamRouter()
|
|
result = router.resolve("普通问题")
|
|
assert result.matched is False
|
|
assert result.team_mode is False
|
|
|
|
def test_team_prefix_takes_priority_over_complexity(self):
|
|
"""@team 前缀优先于复杂度判断"""
|
|
router = ExpertTeamRouter()
|
|
result = router.resolve("@team:analyst 任务", complexity=0.1)
|
|
assert result.matched is True
|
|
assert result.match_method == "explicit_team"
|
|
assert result.specified_experts == ["analyst"]
|
|
|
|
def test_nonexistent_expert_still_included(self):
|
|
"""指定不存在的专家名仍包含在列表中"""
|
|
router = ExpertTeamRouter()
|
|
result = router.resolve("@team:analyst,nonexistent 任务")
|
|
assert result.specified_experts == ["analyst", "nonexistent"]
|
|
|
|
def test_team_with_empty_task_uses_full_content(self):
|
|
"""@team 无任务内容时 task_content 使用原始内容"""
|
|
router = ExpertTeamRouter()
|
|
result = router.resolve("@team")
|
|
assert result.task_content == "@team"
|
|
assert result.auto_compose is True
|
|
|
|
|
|
# ── ExpertTeamRouter.resolve_expert_configs 测试 ───────────
|
|
|
|
|
|
class TestExpertTeamRouterResolveExpertConfigs:
|
|
"""ExpertTeamRouter.resolve_expert_configs 方法测试"""
|
|
|
|
def test_resolve_existing_templates(self):
|
|
"""解析已注册模板返回对应 ExpertConfig"""
|
|
registry = _make_registry_with_templates()
|
|
router = ExpertTeamRouter(template_registry=registry)
|
|
|
|
configs = router.resolve_expert_configs(["analyst", "strategist"])
|
|
assert len(configs) == 2
|
|
assert configs[0].name == "analyst"
|
|
assert configs[0].persona == "数据分析师"
|
|
assert configs[1].name == "strategist"
|
|
assert configs[1].persona == "策略专家"
|
|
|
|
def test_resolve_nonexistent_creates_dynamic_config(self):
|
|
"""解析不存在的名称创建动态 ExpertConfig"""
|
|
registry = _make_registry_with_templates()
|
|
router = ExpertTeamRouter(template_registry=registry)
|
|
|
|
configs = router.resolve_expert_configs(["analyst", "unknown_expert"])
|
|
assert len(configs) == 2
|
|
assert configs[0].name == "analyst"
|
|
assert configs[0].persona == "数据分析师"
|
|
# 动态生成的配置
|
|
assert configs[1].name == "unknown_expert"
|
|
assert configs[1].persona == "Expert in unknown_expert"
|
|
assert configs[1].agent_type == "expert"
|
|
assert configs[1].thinking_style == "analytical"
|
|
assert configs[1].bound_skills == []
|
|
assert configs[1].is_lead is False
|
|
assert configs[1].task_mode == "llm_generate"
|
|
|
|
def test_resolve_all_nonexistent(self):
|
|
"""所有名称都不存在时全部动态生成"""
|
|
router = ExpertTeamRouter()
|
|
configs = router.resolve_expert_configs(["role_a", "role_b"])
|
|
assert len(configs) == 2
|
|
assert configs[0].name == "role_a"
|
|
assert configs[0].persona == "Expert in role_a"
|
|
assert configs[1].name == "role_b"
|
|
assert configs[1].persona == "Expert in role_b"
|
|
|
|
def test_resolve_empty_list(self):
|
|
"""空列表返回空结果"""
|
|
router = ExpertTeamRouter()
|
|
configs = router.resolve_expert_configs([])
|
|
assert configs == []
|
|
|
|
def test_resolve_preserves_template_skills(self):
|
|
"""解析已注册模板保留 bound_skills"""
|
|
registry = _make_registry_with_templates()
|
|
router = ExpertTeamRouter(template_registry=registry)
|
|
|
|
configs = router.resolve_expert_configs(["analyst"])
|
|
assert configs[0].bound_skills == ["data_query"]
|
|
|
|
|
|
# ── ExpertTeamRouter 构造测试 ─────────────────────────────
|
|
|
|
|
|
class TestExpertTeamRouterInit:
|
|
"""ExpertTeamRouter 构造函数测试"""
|
|
|
|
def test_default_registry(self):
|
|
"""无参构造创建默认注册中心"""
|
|
router = ExpertTeamRouter()
|
|
assert router._registry is not None
|
|
|
|
def test_custom_registry(self):
|
|
"""传入自定义注册中心"""
|
|
registry = ExpertTemplateRegistry()
|
|
router = ExpertTeamRouter(template_registry=registry)
|
|
assert router._registry is registry
|
|
|
|
def test_complexity_threshold(self):
|
|
"""复杂度阈值默认为 0.7"""
|
|
router = ExpertTeamRouter()
|
|
assert router.COMPLEXITY_THRESHOLD == 0.7
|