Files
aiagent/backend/app/api/teams.py
renjianbo f612248f9e feat: add education-training and platform-engineering team templates
- Add 2 new team templates: 教育培训团队 (4 roles) and 天工平台工程团队 (5 roles)
- Fix orchestrator to support multi-template workflow types (remove hardcoded PM planner)
- Add _resolve_planner_role() to auto-detect planner based on team.config.workflow
- Add frontend buttons and API clients for new templates
- Merge 14 preset roles from 3 template families in get_preset_roles()
- Add creation guide and platform engineering usage docs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-17 00:09:54 +08:00

300 lines
9.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Team API — 虚拟团队管理 + 项目执行
"""
from fastapi import APIRouter, Depends, Query, HTTPException
from fastapi.responses import StreamingResponse
from sqlalchemy.orm import Session
from pydantic import BaseModel, Field
from typing import List, Optional, Dict, Any
import json
import logging
from app.core.database import get_db
from app.api.auth import get_current_user
from app.models.user import User
from app.services.team_service import TeamService, get_preset_roles
from app.services.team_orchestrator import TeamOrchestrator
logger = logging.getLogger(__name__)
router = APIRouter(
prefix="/api/v1/teams",
tags=["teams"],
responses={
401: {"description": "未授权"},
404: {"description": "资源不存在"},
400: {"description": "请求参数错误"},
},
)
# ──────────────────────────── Schemas ────────────────────────────
class TeamCreate(BaseModel):
name: str
description: str = ""
workspace_id: Optional[str] = None
config: Optional[Dict[str, Any]] = None
class TeamUpdate(BaseModel):
name: Optional[str] = None
description: Optional[str] = None
config: Optional[Dict[str, Any]] = None
status: Optional[str] = None
class MemberAdd(BaseModel):
agent_id: str
role: str
position: int = 0
is_lead: bool = False
class ProjectExecute(BaseModel):
project_description: str = Field(..., min_length=1, description="项目描述")
auto_approve_files: bool = Field(default=True, description="是否自动批准文件写入(跳过确认弹窗)")
# ──────────────────────────── Endpoints ────────────────────────────
@router.get("/preset-roles")
def preset_roles():
"""获取预置角色定义PM/设计师/开发/测试/DevOps"""
return {"roles": get_preset_roles()}
@router.post("")
def create_team(
body: TeamCreate,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""创建团队。"""
svc = TeamService(db)
team = svc.create_team(
name=body.name,
user_id=current_user.id,
description=body.description,
workspace_id=body.workspace_id,
config=body.config,
)
return {"data": team.to_dict()}
@router.post("/template/software-company")
def create_software_company_template(
workspace_id: Optional[str] = Query(None),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""一键创建「软件公司虚拟团队」模板。
自动创建 5 个角色 AgentPM/设计师/开发/测试/DevOps并组建团队。
若用户已有同名 Agent 则复用。
"""
svc = TeamService(db)
result = svc.create_software_company_template(
user_id=current_user.id,
workspace_id=workspace_id,
)
return {"data": result}
@router.post("/template/education-training")
def create_education_training_template(
workspace_id: Optional[str] = Query(None),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""一键创建「教育培训团队」模板。
自动创建 4 个角色 Agent课程设计/主讲讲师/作业助教/教务管理)并组建团队。
若用户已有同名 Agent 则复用。
"""
svc = TeamService(db)
result = svc.create_education_training_template(
user_id=current_user.id,
workspace_id=workspace_id,
)
return {"data": result}
@router.post("/template/platform-engineering")
def create_platform_engineering_template(
workspace_id: Optional[str] = Query(None),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""一键创建「天工平台工程团队」模板。
自动创建 5 个角色 Agent全栈开发/前端体验/平台运维/质量保障/产品负责人)并组建团队。
专用于天工智能体平台的自我维护与迭代升级。
若用户已有同名 Agent 则复用。
"""
svc = TeamService(db)
result = svc.create_platform_engineering_template(
user_id=current_user.id,
workspace_id=workspace_id,
)
return {"data": result}
@router.get("")
def list_teams(
workspace_id: Optional[str] = Query(None),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""列出当前用户/工作区的所有团队。"""
svc = TeamService(db)
teams = svc.list_teams(
user_id=current_user.id,
workspace_id=workspace_id,
)
return {"data": [t.to_dict() for t in teams]}
@router.get("/{team_id}")
def get_team(
team_id: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""获取团队详情(含成员列表)。"""
svc = TeamService(db)
result = svc.get_team_with_members(team_id)
if not result:
raise HTTPException(status_code=404, detail="团队不存在")
return {"data": result}
@router.put("/{team_id}")
def update_team(
team_id: str,
body: TeamUpdate,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""更新团队信息。"""
svc = TeamService(db)
team = svc.update_team(team_id, **body.model_dump(exclude_none=True))
if not team:
raise HTTPException(status_code=404, detail="团队不存在")
return {"data": team.to_dict()}
@router.delete("/{team_id}")
def delete_team(
team_id: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""删除团队及其所有成员。"""
svc = TeamService(db)
ok = svc.delete_team(team_id)
if not ok:
raise HTTPException(status_code=404, detail="团队不存在")
return {"message": "团队已删除"}
@router.post("/{team_id}/members")
def add_member(
team_id: str,
body: MemberAdd,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""为团队添加/替换角色成员。"""
svc = TeamService(db)
member = svc.add_member(
team_id=team_id,
agent_id=body.agent_id,
role=body.role,
position=body.position,
is_lead=body.is_lead,
)
if not member:
raise HTTPException(status_code=404, detail="团队不存在")
return {"data": member.to_dict()}
@router.delete("/{team_id}/members/{member_id}")
def remove_member(
team_id: str,
member_id: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""从团队移除成员。"""
svc = TeamService(db)
ok = svc.remove_member(team_id, member_id)
if not ok:
raise HTTPException(status_code=404, detail="成员不存在")
return {"message": "成员已移除"}
@router.get("/{team_id}/members")
def list_members(
team_id: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""列出团队成员。"""
svc = TeamService(db)
members = svc.get_members(team_id)
return {"data": [m.to_dict() for m in members]}
@router.post("/{team_id}/execute")
async def execute_project(
team_id: str,
body: ProjectExecute,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""执行团队项目(非流式)。
PM 先分解计划各角色按阶段顺序执行QA 最终审查。
"""
orch = TeamOrchestrator(db, team_id, current_user.id)
try:
result = await orch.execute(body.project_description, auto_approve_files=body.auto_approve_files)
return {"data": result}
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error("团队执行失败: %s", e, exc_info=True)
raise HTTPException(status_code=500, detail=f"团队执行失败: {e}")
@router.post("/{team_id}/execute/stream")
async def execute_project_stream(
team_id: str,
body: ProjectExecute,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""执行团队项目SSE 流式)。"""
async def event_generator():
orch = TeamOrchestrator(db, team_id, current_user.id)
try:
async for event in orch.execute_stream(body.project_description, auto_approve_files=body.auto_approve_files):
yield f"data: {json.dumps(event, ensure_ascii=False)}\n\n"
except ValueError as e:
yield f"data: {json.dumps({'type': 'error', 'content': str(e)}, ensure_ascii=False)}\n\n"
except Exception as e:
logger.error("团队流式执行失败: %s", e, exc_info=True)
yield f"data: {json.dumps({'type': 'error', 'content': str(e)}, ensure_ascii=False)}\n\n"
return StreamingResponse(
event_generator(),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"X-Accel-Buffering": "no",
},
)