Files
aiagent/backend/app/api/agents.py

509 lines
16 KiB
Python
Raw Normal View History

2026-01-19 00:09:36 +08:00
"""
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
2026-01-19 17:52:29 +08:00
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
2026-01-20 11:03:55 +08:00
@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)
):
"""导出AgentJSON格式"""
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)
):
"""导入AgentJSON格式"""
# 提取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