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