diff --git a/configs/experts/backend_engineer.yaml b/configs/experts/backend_engineer.yaml new file mode 100644 index 0000000..501eb27 --- /dev/null +++ b/configs/experts/backend_engineer.yaml @@ -0,0 +1,22 @@ +name: backend_engineer +description: "后端工程师 — 系统思维、API 设计、数据库操作" +is_builtin: true +config: + name: backend_engineer + agent_type: expert + persona: | + 你是资深后端工程师,精通 Python、FastAPI、SQLAlchemy、PostgreSQL 等后端技术栈。 + 你以系统思维设计服务,关注 API 设计、数据模型、性能和安全性。 + 你熟悉 RESTful API 设计规范,能编写高效、可维护的后端代码和数据库操作。 + 你的职责是:设计 API 接口、实现业务逻辑、操作数据库、保障系统稳定性。 + thinking_style: "系统思维:从数据流和业务逻辑出发,设计高内聚、低耦合的服务" + speaking_style: "严谨、结构化,善用系统设计术语" + decision_framework: "系统设计 — 评估性能、安全性、可扩展性,遵循 SOLID 原则" + collaboration_strategy: "cooperative" + bound_skills: [] + avatar: "⚙️" + color: "#fa8c16" + is_lead: false + task_mode: llm_generate + prompt: + identity: "Backend Engineer" diff --git a/configs/experts/code_reviewer.yaml b/configs/experts/code_reviewer.yaml new file mode 100644 index 0000000..805257c --- /dev/null +++ b/configs/experts/code_reviewer.yaml @@ -0,0 +1,22 @@ +name: code_reviewer +description: "代码审查员 — 批判性思维、代码规范、安全漏洞" +is_builtin: true +config: + name: code_reviewer + agent_type: expert + persona: | + 你是资深代码审查员,拥有丰富的代码审查和安全审计经验。 + 你以批判性思维审视代码,关注代码规范、设计模式、性能瓶颈和安全漏洞。 + 你熟悉 OWASP Top 10、SOLID 原则、Clean Code 等最佳实践,能提供建设性的改进建议。 + 你的职责是:审查代码质量、识别潜在风险、提出改进建议、确保代码符合规范。 + thinking_style: "批判性思维:从可维护性、安全性、性能多维度审查代码" + speaking_style: "客观、建设性,善用代码审查术语和改进建议" + decision_framework: "代码质量 — 规范性、安全性、可维护性、性能四维评估" + collaboration_strategy: "cooperative" + bound_skills: [] + avatar: "🛡️" + color: "#722ed1" + is_lead: false + task_mode: llm_generate + prompt: + identity: "Code Reviewer" diff --git a/configs/experts/dev_team.yaml b/configs/experts/dev_team.yaml new file mode 100644 index 0000000..26eb7a8 --- /dev/null +++ b/configs/experts/dev_team.yaml @@ -0,0 +1,25 @@ +name: dev_team +description: "默认编程团队模板 — 包含 5 位编程领域专家(技术负责人、前端、后端、QA、代码审查)" +is_builtin: true +config: + name: dev_team + agent_type: expert + persona: "编程团队模板 — 全栈开发专家团" + thinking_style: "流水线协作" + speaking_style: "专业化" + decision_framework: "流水线执行 — 规划→前端→后端→QA→评审" + collaboration_strategy: "cooperative" + # dev_team 模板使用 bound_skills 字段存储成员列表 + # 这是对现有字段的重用,避免新增 schema + bound_skills: + - tech_lead + - frontend_engineer + - backend_engineer + - qa_engineer + - code_reviewer + avatar: "👥" + color: "#1890ff" + is_lead: false + task_mode: llm_generate + prompt: + identity: "Dev Team Template" diff --git a/configs/experts/frontend_engineer.yaml b/configs/experts/frontend_engineer.yaml new file mode 100644 index 0000000..865d493 --- /dev/null +++ b/configs/experts/frontend_engineer.yaml @@ -0,0 +1,22 @@ +name: frontend_engineer +description: "前端工程师 — 组件化思维、UI/UX 敏感、前端测试" +is_builtin: true +config: + name: frontend_engineer + agent_type: expert + persona: | + 你是资深前端工程师,精通 Vue 3、TypeScript、React 等现代前端技术栈。 + 你以组件化思维设计界面,对 UI/UX 细节高度敏感,追求用户友好的交互体验。 + 你熟悉 Ant Design Vue、Element Plus 等组件库,能编写可维护、可测试的前端代码。 + 你的职责是:实现用户界面、处理交互逻辑、编写前端测试、优化前端性能。 + thinking_style: "组件化思维:从用户交互出发,设计可复用、可组合的 UI 组件" + speaking_style: "注重细节、关注用户体验,善用前端术语" + decision_framework: "用户体验优先 — 在技术实现和用户需求之间找到最佳平衡" + collaboration_strategy: "cooperative" + bound_skills: [] + avatar: "🎨" + color: "#52c41a" + is_lead: false + task_mode: llm_generate + prompt: + identity: "Frontend Engineer" diff --git a/configs/experts/qa_engineer.yaml b/configs/experts/qa_engineer.yaml new file mode 100644 index 0000000..d380185 --- /dev/null +++ b/configs/experts/qa_engineer.yaml @@ -0,0 +1,22 @@ +name: qa_engineer +description: "QA 工程师 — 边界思维、测试用例设计、缺陷验证" +is_builtin: true +config: + name: qa_engineer + agent_type: expert + persona: | + 你是资深 QA 工程师,精通 pytest、Playwright、Cypress 等测试框架。 + 你以边界思维审视代码,擅长设计覆盖正常路径、边界条件和异常场景的测试用例。 + 你关注代码质量、测试覆盖率和缺陷预防,能发现隐藏的 bug 和潜在风险。 + 你的职责是:设计测试用例、编写自动化测试、验证功能正确性、报告缺陷。 + thinking_style: "边界思维:从正常路径、边界条件、异常场景三维度设计测试" + speaking_style: "精确、详尽,善用测试术语和缺陷描述" + decision_framework: "质量保障 — 测试覆盖率、边界条件、回归测试三重保障" + collaboration_strategy: "cooperative" + bound_skills: [] + avatar: "🔍" + color: "#eb2f96" + is_lead: false + task_mode: llm_generate + prompt: + identity: "QA Engineer" diff --git a/configs/experts/tech_lead.yaml b/configs/experts/tech_lead.yaml new file mode 100644 index 0000000..c1c78a5 --- /dev/null +++ b/configs/experts/tech_lead.yaml @@ -0,0 +1,22 @@ +name: tech_lead +description: "技术负责人 — 第一性原理、架构决策、任务分解" +is_builtin: true +config: + name: tech_lead + agent_type: expert + persona: | + 你是技术负责人(Tech Lead),拥有 15 年以上全栈开发经验。 + 你擅长从第一性原理出发分析问题,将复杂任务分解为可执行的阶段。 + 你关注系统架构、技术选型和工程实践,能在质量、速度和可维护性之间做出平衡。 + 你的职责是:理解需求、设计架构、分解任务、协调团队成员、把控技术方向。 + thinking_style: "第一性原理:从问题本质出发,推导架构决策和任务分解" + speaking_style: "结构化、清晰、务实,善用技术术语但避免过度抽象" + decision_framework: "架构决策 — 评估可行性、可维护性、扩展性,权衡短期与长期" + collaboration_strategy: "cooperative" + bound_skills: [] + avatar: "🏗️" + color: "#1890ff" + is_lead: true + task_mode: llm_generate + prompt: + identity: "Tech Lead" diff --git a/src/agentkit/experts/router.py b/src/agentkit/experts/router.py index 692a57e..755a9e9 100644 --- a/src/agentkit/experts/router.py +++ b/src/agentkit/experts/router.py @@ -48,8 +48,11 @@ class ExpertTeamRouter: Supports: - @team prefix → trigger team mode (auto-compose members) - @team:analyst,strategist → specify team members by name + - @team:dev_team → use a team template (expands to member list from bound_skills) """ + DEFAULT_TEMPLATE = "dev_team" + def __init__(self, template_registry: ExpertTemplateRegistry | None = None): self._registry = template_registry or ExpertTemplateRegistry() @@ -80,6 +83,18 @@ class ExpertTeamRouter: result.match_method = "explicit_team" if expert_list_str: + # Check if user specified a single team template name (e.g., "dev_team") + # Team templates store their member list in config.bound_skills + stripped = expert_list_str.strip() + if "," not in stripped: + template = self._registry.get(stripped) + if template and template.config.bound_skills: + # This is a team template — expand to its members + result.specified_experts = template.config.bound_skills[:MAX_EXPERTS] + result.auto_compose = False + result.match_method = "explicit_team" + return result + # User specified expert names — validate and limit raw_names = [name.strip() for name in expert_list_str.split(",")] valid_names = [n for n in raw_names if _EXPERT_NAME_RE.match(n)] @@ -97,8 +112,9 @@ class ExpertTeamRouter: f"ExpertTemplate '{name}' not found, will be dynamically generated" ) else: - # No specific experts — auto-compose - result.auto_compose = True + # No specific experts — use default dev_team template + result.specified_experts = self._load_default_template_members() + result.auto_compose = False return result @@ -170,3 +186,26 @@ class ExpertTeamRouter: configs[0].is_lead = True return configs + + def _load_default_template_members(self) -> list[str]: + """Load member list from the default dev_team template. + + The dev_team template stores its member list in config.bound_skills + (reusing the existing field to avoid schema changes). + + Falls back to a hardcoded list if the template is not found. + """ + template = self._registry.get(self.DEFAULT_TEMPLATE) + if template: + members = template.config.bound_skills + if members: + return members[:MAX_EXPERTS] + + # Fallback default members + return [ + "tech_lead", + "frontend_engineer", + "backend_engineer", + "qa_engineer", + "code_reviewer", + ] diff --git a/tests/unit/experts/test_router.py b/tests/unit/experts/test_router.py index 2078cbc..43c8275 100644 --- a/tests/unit/experts/test_router.py +++ b/tests/unit/experts/test_router.py @@ -139,14 +139,21 @@ class TestExpertTeamRouterResolve: assert result.auto_compose is False assert result.match_method == "explicit_team" - def test_team_no_experts_auto_compose(self): - """@team 无指定专家时 auto_compose=True""" + def test_team_no_experts_uses_default_template(self): + """@team 无指定专家时使用默认 dev_team 模板""" 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 + # 默认 dev_team 模板包含 5 个成员 + assert result.specified_experts == [ + "tech_lead", + "frontend_engineer", + "backend_engineer", + "qa_engineer", + "code_reviewer", + ] + assert result.auto_compose is False def test_team_with_expert_extracts_task(self): """@team:analyst 正确提取任务内容""" @@ -176,7 +183,9 @@ class TestExpertTeamRouterResolve: router = ExpertTeamRouter() result = router.resolve("@team") assert result.task_content == "@team" - assert result.auto_compose is True + # 无指定专家时使用默认模板 + assert result.auto_compose is False + assert len(result.specified_experts) == 5 def test_resolve_strips_leading_whitespace(self): """resolve() 对前导空白做 strip()""" @@ -342,3 +351,132 @@ class TestExpertTeamRouterCanHandle: # 内容与任何模板名/描述都不匹配,但有模板存在 → auto-compose 可用 assert router.can_handle("完全无关的内容 xyz123") is True + + +# ── ExpertTeamRouter 团队模板展开测试 (U7) ───────────────── + + +class TestExpertTeamRouterTemplateExpansion: + """ExpertTeamRouter 团队模板展开测试 — @team:dev_team 自动展开为成员列表""" + + def test_team_template_expands_to_members(self): + """@team:dev_team 展开为 dev_team 模板的 bound_skills 成员列表""" + registry = ExpertTemplateRegistry() + # 注册 dev_team 模板(bound_skills 存储成员列表) + registry.register( + _make_template( + "dev_team", + persona="编程团队", + bound_skills=["tech_lead", "frontend_engineer", "backend_engineer"], + ) + ) + router = ExpertTeamRouter(template_registry=registry) + + result = router.resolve("@team:dev_team 实现用户登录功能") + assert result.matched is True + assert result.team_mode is True + assert result.specified_experts == ["tech_lead", "frontend_engineer", "backend_engineer"] + assert result.auto_compose is False + assert result.match_method == "explicit_team" + + def test_team_template_respects_max_experts_limit(self): + """团队模板展开时遵守 MAX_EXPERTS 上限""" + registry = ExpertTemplateRegistry() + # 注册一个 bound_skills 超过 MAX_EXPERTS 的模板 + registry.register( + _make_template( + "dev_team", + persona="编程团队", + bound_skills=[f"expert{i}" for i in range(15)], + ) + ) + router = ExpertTeamRouter(template_registry=registry) + + result = router.resolve("@team:dev_team 任务") + from agentkit.experts.router import MAX_EXPERTS + + assert len(result.specified_experts) == MAX_EXPERTS + + def test_team_template_not_found_falls_back_to_default_members(self): + """@team:unknown_template 时未知模板名作为普通专家处理""" + registry = ExpertTemplateRegistry() + router = ExpertTeamRouter(template_registry=registry) + + result = router.resolve("@team:unknown_template 任务") + # 未知模板名(无 bound_skills)→ 作为普通专家名处理 + assert result.specified_experts == ["unknown_template"] + + def test_no_experts_uses_default_dev_team_template(self): + """@team 无指定专家时从 dev_team 模板加载成员""" + registry = ExpertTemplateRegistry() + registry.register( + _make_template( + "dev_team", + persona="编程团队", + bound_skills=["tech_lead", "frontend_engineer", "backend_engineer"], + ) + ) + router = ExpertTeamRouter(template_registry=registry) + + result = router.resolve("@team 实现功能") + assert result.specified_experts == ["tech_lead", "frontend_engineer", "backend_engineer"] + assert result.auto_compose is False + + def test_no_experts_no_dev_team_template_uses_hardcoded_fallback(self): + """无 dev_team 模板时使用硬编码 fallback 列表""" + registry = ExpertTemplateRegistry() + router = ExpertTeamRouter(template_registry=registry) + + result = router.resolve("@team 任务") + assert result.specified_experts == [ + "tech_lead", + "frontend_engineer", + "backend_engineer", + "qa_engineer", + "code_reviewer", + ] + + def test_team_template_with_empty_bound_skills_treated_as_expert_name(self): + """bound_skills 为空的模板名作为普通专家处理""" + registry = ExpertTemplateRegistry() + registry.register(_make_template("empty_team", persona="空团队", bound_skills=[])) + router = ExpertTeamRouter(template_registry=registry) + + result = router.resolve("@team:empty_team 任务") + # bound_skills 为空 → 不展开,作为普通专家名 + assert result.specified_experts == ["empty_team"] + + def test_default_template_class_attribute(self): + """DEFAULT_TEMPLATE 类属性为 'dev_team'""" + from agentkit.experts.router import ExpertTeamRouter as Router + + assert Router.DEFAULT_TEMPLATE == "dev_team" + + def test_load_default_template_members_with_template(self): + """_load_default_template_members 从 dev_team 模板加载""" + registry = ExpertTemplateRegistry() + registry.register( + _make_template( + "dev_team", + persona="编程团队", + bound_skills=["a", "b", "c"], + ) + ) + router = ExpertTeamRouter(template_registry=registry) + + members = router._load_default_template_members() + assert members == ["a", "b", "c"] + + def test_load_default_template_members_without_template(self): + """_load_default_template_members 无模板时返回硬编码列表""" + registry = ExpertTemplateRegistry() + router = ExpertTeamRouter(template_registry=registry) + + members = router._load_default_template_members() + assert members == [ + "tech_lead", + "frontend_engineer", + "backend_engineer", + "qa_engineer", + "code_reviewer", + ]