feat(experts): U7 新增 5 个编程专家模板 + dev_team 团队模板 + ExpertTeamRouter 模板展开

This commit is contained in:
chiguyong 2026-06-18 01:50:43 +08:00
parent 0f8ea6e21e
commit ee6d16345c
8 changed files with 319 additions and 7 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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",
]

View File

@ -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",
]