""" Agent管理API """ from fastapi import APIRouter, Depends, HTTPException, status, Query from sqlalchemy.orm import Session from pydantic import BaseModel from typing import List, Optional, Dict, Any from datetime import datetime import logging from app.core.database import get_db from app.models.agent import Agent from app.api.auth import get_current_user from app.models.user import User from app.core.exceptions import NotFoundError, ValidationError, ConflictError from app.services.permission_service import check_agent_permission from app.services.workflow_validator import validate_workflow import uuid logger = logging.getLogger(__name__) router = APIRouter( prefix="/api/v1/agents", tags=["agents"], responses={ 401: {"description": "未授权"}, 404: {"description": "资源不存在"}, 400: {"description": "请求参数错误"}, 500: {"description": "服务器内部错误"} } ) class AgentCreate(BaseModel): """Agent创建模型""" name: str description: Optional[str] = None workflow_config: Dict[str, Any] # 包含nodes和edges class AgentUpdate(BaseModel): """Agent更新模型""" name: Optional[str] = None description: Optional[str] = None workflow_config: Optional[Dict[str, Any]] = None status: Optional[str] = None class AgentResponse(BaseModel): """Agent响应模型""" id: str name: str description: Optional[str] workflow_config: Dict[str, Any] version: int status: str user_id: str created_at: datetime updated_at: datetime class Config: from_attributes = True @router.get("", response_model=List[AgentResponse]) async def get_agents( skip: int = Query(0, ge=0, description="跳过记录数"), limit: int = Query(100, ge=1, le=100, description="每页记录数"), search: Optional[str] = Query(None, description="搜索关键词(按名称或描述)"), status: Optional[str] = Query(None, description="状态筛选"), db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """ 获取Agent列表 支持分页、搜索、状态筛选 """ # 管理员可以看到所有Agent,普通用户只能看到自己拥有的或有read权限的 if current_user.role == "admin": query = db.query(Agent) else: # 获取用户拥有或有read权限的Agent from sqlalchemy import or_ from app.models.permission import AgentPermission # 用户拥有的Agent owned_agents = db.query(Agent.id).filter(Agent.user_id == current_user.id).subquery() # 用户有read权限的Agent(通过用户ID或角色) user_permissions = db.query(AgentPermission.agent_id).filter( AgentPermission.permission_type == "read", or_( AgentPermission.user_id == current_user.id, AgentPermission.role_id.in_([r.id for r in current_user.roles]) ) ).subquery() query = db.query(Agent).filter( or_( Agent.id.in_(db.query(owned_agents.c.id)), Agent.id.in_(db.query(user_permissions.c.agent_id)) ) ) # 搜索:按名称或描述搜索 if search: search_pattern = f"%{search}%" query = query.filter( (Agent.name.like(search_pattern)) | (Agent.description.like(search_pattern)) ) # 筛选:按状态筛选 if status: query = query.filter(Agent.status == status) # 排序和分页 agents = query.order_by(Agent.created_at.desc()).offset(skip).limit(limit).all() return agents @router.post("", response_model=AgentResponse, status_code=status.HTTP_201_CREATED) async def create_agent( agent_data: AgentCreate, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """ 创建Agent 创建时会验证工作流配置的有效性 """ # 验证工作流配置 if "nodes" not in agent_data.workflow_config or "edges" not in agent_data.workflow_config: raise ValidationError("工作流配置必须包含nodes和edges") nodes = agent_data.workflow_config.get("nodes", []) edges = agent_data.workflow_config.get("edges", []) # 验证工作流 validation_result = validate_workflow(nodes, edges) if not validation_result["valid"]: raise ValidationError(f"工作流配置验证失败: {', '.join(validation_result['errors'])}") # 检查名称是否重复 existing_agent = db.query(Agent).filter( Agent.name == agent_data.name, Agent.user_id == current_user.id ).first() if existing_agent: raise ConflictError(f"Agent名称 '{agent_data.name}' 已存在") # 创建Agent agent = Agent( name=agent_data.name, description=agent_data.description, workflow_config=agent_data.workflow_config, user_id=current_user.id, status="draft" ) db.add(agent) db.commit() db.refresh(agent) logger.info(f"用户 {current_user.username} 创建了Agent: {agent.name} ({agent.id})") return agent @router.get("/{agent_id}", response_model=AgentResponse) async def get_agent( agent_id: str, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """ 获取Agent详情 """ agent = db.query(Agent).filter(Agent.id == agent_id).first() if not agent: raise NotFoundError(f"Agent不存在: {agent_id}") # 检查权限:read权限 if not check_agent_permission(db, current_user, agent, "read"): raise HTTPException(status_code=403, detail="无权访问此Agent") return agent @router.put("/{agent_id}", response_model=AgentResponse) async def update_agent( agent_id: str, agent_data: AgentUpdate, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """ 更新Agent 如果更新了workflow_config,会验证工作流配置的有效性 """ agent = db.query(Agent).filter(Agent.id == agent_id).first() if not agent: raise NotFoundError(f"Agent不存在: {agent_id}") # 检查权限:write权限 if not check_agent_permission(db, current_user, agent, "write"): raise HTTPException(status_code=403, detail="无权修改此Agent") # 更新字段 if agent_data.name is not None: # 检查名称是否重复(排除当前Agent) existing_agent = db.query(Agent).filter( Agent.name == agent_data.name, Agent.user_id == current_user.id, Agent.id != agent_id ).first() if existing_agent: raise ConflictError(f"Agent名称 '{agent_data.name}' 已存在") agent.name = agent_data.name if agent_data.description is not None: agent.description = agent_data.description if agent_data.workflow_config is not None: # 验证工作流配置 if "nodes" not in agent_data.workflow_config or "edges" not in agent_data.workflow_config: raise ValidationError("工作流配置必须包含nodes和edges") nodes = agent_data.workflow_config.get("nodes", []) edges = agent_data.workflow_config.get("edges", []) validation_result = validate_workflow(nodes, edges) if not validation_result["valid"]: raise ValidationError(f"工作流配置验证失败: {', '.join(validation_result['errors'])}") agent.workflow_config = agent_data.workflow_config agent.version += 1 # 版本号自增 if agent_data.status is not None: valid_statuses = ["draft", "published", "running", "stopped"] if agent_data.status not in valid_statuses: raise ValidationError(f"无效的状态: {agent_data.status}") agent.status = agent_data.status db.commit() db.refresh(agent) logger.info(f"用户 {current_user.username} 更新了Agent: {agent.name} ({agent.id})") return agent @router.delete("/{agent_id}", status_code=status.HTTP_200_OK) async def delete_agent( agent_id: str, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """ 删除Agent(只有所有者可以删除) """ agent = db.query(Agent).filter(Agent.id == agent_id).first() if not agent: raise NotFoundError(f"Agent不存在: {agent_id}") # 只有Agent所有者可以删除 if agent.user_id != current_user.id and current_user.role != "admin": raise HTTPException(status_code=403, detail="无权删除此Agent") agent_name = agent.name db.delete(agent) db.commit() logger.info(f"用户 {current_user.username} 删除了Agent: {agent_name} ({agent_id})") return {"message": "Agent已删除"} @router.post("/{agent_id}/deploy", response_model=AgentResponse, status_code=status.HTTP_200_OK) async def deploy_agent( agent_id: str, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """ 部署Agent 将Agent状态设置为published """ agent = db.query(Agent).filter(Agent.id == agent_id).first() if not agent: raise NotFoundError(f"Agent不存在: {agent_id}") # 检查权限:deploy权限 if not check_agent_permission(db, current_user, agent, "deploy"): raise HTTPException(status_code=403, detail="无权部署此Agent") agent.status = "published" db.commit() db.refresh(agent) logger.info(f"用户 {current_user.username} 部署了Agent: {agent.name} ({agent_id})") return agent @router.post("/{agent_id}/stop", response_model=AgentResponse, status_code=status.HTTP_200_OK) async def stop_agent( agent_id: str, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """ 停止Agent 将Agent状态设置为stopped """ agent = db.query(Agent).filter(Agent.id == agent_id).first() if not agent: raise NotFoundError(f"Agent不存在: {agent_id}") # 检查权限:deploy权限(停止也需要deploy权限) if not check_agent_permission(db, current_user, agent, "deploy"): raise HTTPException(status_code=403, detail="无权停止此Agent") agent.status = "stopped" db.commit() db.refresh(agent) logger.info(f"用户 {current_user.username} 停止了Agent: {agent.name} ({agent_id})") return agent class AgentDuplicateRequest(BaseModel): """Agent复制请求模型""" name: Optional[str] = None # 如果提供,使用此名称;否则自动生成 @router.post("/{agent_id}/duplicate", response_model=AgentResponse, status_code=status.HTTP_201_CREATED) async def duplicate_agent( agent_id: str, duplicate_data: Optional[AgentDuplicateRequest] = None, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """ 复制Agent 创建一个新的Agent副本,包含原Agent的所有配置(工作流、描述等) 新Agent的状态为draft,版本号为1 """ # 获取原Agent original_agent = db.query(Agent).filter(Agent.id == agent_id).first() if not original_agent: raise NotFoundError(f"Agent不存在: {agent_id}") # 检查权限:read权限(需要能读取原Agent才能复制) if not check_agent_permission(db, current_user, original_agent, "read"): raise HTTPException(status_code=403, detail="无权复制此Agent") # 生成新名称 if duplicate_data and duplicate_data.name: new_name = duplicate_data.name else: # 自动生成名称:原名称 + " (副本)" base_name = original_agent.name new_name = f"{base_name} (副本)" # 如果名称已存在,添加序号 counter = 1 while db.query(Agent).filter( Agent.name == new_name, Agent.user_id == current_user.id ).first(): counter += 1 new_name = f"{base_name} (副本 {counter})" # 深拷贝工作流配置(避免引用问题) import copy new_workflow_config = copy.deepcopy(original_agent.workflow_config) # 创建新Agent new_agent = Agent( name=new_name, description=original_agent.description, workflow_config=new_workflow_config, user_id=current_user.id, status="draft", # 复制的Agent状态为草稿 version=1 # 版本号从1开始 ) db.add(new_agent) db.commit() db.refresh(new_agent) logger.info(f"用户 {current_user.username} 复制了Agent: {original_agent.name} ({agent_id}) -> {new_agent.name} ({new_agent.id})") return new_agent @router.get("/{agent_id}/export", status_code=status.HTTP_200_OK) async def export_agent( agent_id: str, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """导出Agent(JSON格式)""" try: agent = db.query(Agent).filter(Agent.id == agent_id).first() if not agent: raise NotFoundError("Agent", agent_id) # 检查权限:read权限 if not check_agent_permission(db, current_user, agent, "read"): raise HTTPException(status_code=403, detail="无权导出此Agent") # 验证工作流配置 workflow_config = agent.workflow_config if not workflow_config: raise ValidationError("Agent工作流配置为空,无法导出") export_data = { "id": str(agent.id), "name": agent.name, "description": agent.description, "workflow_config": workflow_config, "version": agent.version, "status": agent.status, "exported_at": datetime.utcnow().isoformat() } logger.info(f"用户 {current_user.username} 导出Agent: {agent.name} ({agent_id})") return export_data except Exception as e: logger.error(f"导出Agent失败: {str(e)}", exc_info=True) raise @router.post("/import", response_model=AgentResponse, status_code=status.HTTP_201_CREATED) async def import_agent( agent_data: Dict[str, Any], db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """导入Agent(JSON格式)""" # 提取Agent数据 name = agent_data.get("name", "导入的Agent") description = agent_data.get("description") workflow_config = agent_data.get("workflow_config", {}) # 验证工作流配置 if not workflow_config: raise ValidationError("Agent工作流配置不能为空") nodes = workflow_config.get("nodes", []) edges = workflow_config.get("edges", []) if not nodes or not edges: raise ValidationError("Agent工作流配置无效:缺少节点或边") # 验证工作流 validation_result = validate_workflow(nodes, edges) if not validation_result["valid"]: raise ValidationError(f"导入的Agent工作流验证失败: {', '.join(validation_result['errors'])}") # 重新生成节点ID(避免ID冲突) node_id_mapping = {} for node in nodes: old_id = node["id"] new_id = f"node_{len(node_id_mapping)}_{old_id}" node_id_mapping[old_id] = new_id node["id"] = new_id # 更新边的源节点和目标节点ID for edge in edges: if edge.get("source") in node_id_mapping: edge["source"] = node_id_mapping[edge["source"]] if edge.get("target") in node_id_mapping: edge["target"] = node_id_mapping[edge["target"]] # 检查名称是否已存在 base_name = name counter = 1 while db.query(Agent).filter( Agent.name == name, Agent.user_id == current_user.id ).first(): name = f"{base_name} (导入 {counter})" counter += 1 # 创建Agent agent = Agent( name=name, description=description, workflow_config={ "nodes": nodes, "edges": edges }, user_id=current_user.id, status="draft", # 导入的Agent默认为草稿状态 version=1 ) db.add(agent) db.commit() db.refresh(agent) return agent