fischer-agentkit/tests/unit/experts/test_router.py

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