2026-01-19 00:09:36 +08:00
|
|
|
|
"""
|
|
|
|
|
|
Agent管理API
|
|
|
|
|
|
"""
|
2026-05-06 01:37:13 +08:00
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, status, Query, Response
|
2026-01-19 00:09:36 +08:00
|
|
|
|
from sqlalchemy.orm import Session
|
2026-04-09 21:58:53 +08:00
|
|
|
|
from pydantic import BaseModel, Field
|
2026-05-08 22:36:03 +08:00
|
|
|
|
from typing import List, Optional, Dict, Any, Union
|
2026-01-19 00:09:36 +08:00
|
|
|
|
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
|
2026-04-13 20:17:18 +08:00
|
|
|
|
from app.services.agent_workspace_chat_log import fetch_agent_preview_chat_turns
|
2026-01-19 00:09:36 +08:00
|
|
|
|
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
|
2026-04-09 21:58:53 +08:00
|
|
|
|
budget_config: Optional[Dict[str, Any]] = None
|
2026-05-07 08:02:38 +08:00
|
|
|
|
category: Optional[str] = None
|
|
|
|
|
|
tags: Optional[List[str]] = None
|
|
|
|
|
|
is_public: bool = False
|
|
|
|
|
|
is_featured: bool = False
|
2026-01-19 00:09:36 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AgentUpdate(BaseModel):
|
|
|
|
|
|
"""Agent更新模型"""
|
|
|
|
|
|
name: Optional[str] = None
|
|
|
|
|
|
description: Optional[str] = None
|
|
|
|
|
|
workflow_config: Optional[Dict[str, Any]] = None
|
|
|
|
|
|
status: Optional[str] = None
|
2026-04-09 21:58:53 +08:00
|
|
|
|
budget_config: Optional[Dict[str, Any]] = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SceneTemplateItem(BaseModel):
|
|
|
|
|
|
"""场景模板列表项(元数据)"""
|
|
|
|
|
|
|
|
|
|
|
|
id: str
|
|
|
|
|
|
title: str
|
|
|
|
|
|
description: str
|
|
|
|
|
|
category: Optional[str] = None
|
|
|
|
|
|
default_temperature: Optional[float] = None
|
|
|
|
|
|
parameter_hints: List[str] = Field(default_factory=list)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AgentFromSceneTemplateCreate(BaseModel):
|
|
|
|
|
|
"""从场景模板创建 Agent"""
|
|
|
|
|
|
|
|
|
|
|
|
template_id: str
|
|
|
|
|
|
name: str
|
|
|
|
|
|
description: Optional[str] = None
|
|
|
|
|
|
parameters: Dict[str, Any] = Field(default_factory=dict)
|
|
|
|
|
|
budget_config: Optional[Dict[str, Any]] = None
|
2026-01-19 00:09:36 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-04-13 20:17:18 +08:00
|
|
|
|
class PreviewChatTurnResponse(BaseModel):
|
|
|
|
|
|
"""设计器预览侧单轮对话(来自已完成执行)"""
|
|
|
|
|
|
|
|
|
|
|
|
execution_id: str
|
|
|
|
|
|
created_at: datetime
|
|
|
|
|
|
user_text: str
|
|
|
|
|
|
agent_text: str
|
2026-04-13 22:52:36 +08:00
|
|
|
|
attachments: List[Dict[str, Any]] = Field(default_factory=list)
|
2026-04-13 20:17:18 +08:00
|
|
|
|
|
|
|
|
|
|
class Config:
|
|
|
|
|
|
from_attributes = True
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-01-19 00:09:36 +08:00
|
|
|
|
class AgentResponse(BaseModel):
|
|
|
|
|
|
"""Agent响应模型"""
|
|
|
|
|
|
id: str
|
|
|
|
|
|
name: str
|
|
|
|
|
|
description: Optional[str]
|
|
|
|
|
|
workflow_config: Dict[str, Any]
|
2026-04-09 21:58:53 +08:00
|
|
|
|
budget_config: Optional[Dict[str, Any]] = None
|
2026-01-19 00:09:36 +08:00
|
|
|
|
version: int
|
|
|
|
|
|
status: str
|
2026-03-06 22:31:41 +08:00
|
|
|
|
user_id: Optional[str] # 允许为None
|
2026-01-19 00:09:36 +08:00
|
|
|
|
created_at: datetime
|
|
|
|
|
|
updated_at: datetime
|
|
|
|
|
|
|
|
|
|
|
|
class Config:
|
|
|
|
|
|
from_attributes = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("", response_model=List[AgentResponse])
|
|
|
|
|
|
async def get_agents(
|
2026-05-06 01:37:13 +08:00
|
|
|
|
response: Response,
|
2026-01-19 00:09:36 +08:00
|
|
|
|
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)
|
|
|
|
|
|
|
2026-05-06 01:37:13 +08:00
|
|
|
|
# 先获取总数(不带分页)
|
|
|
|
|
|
total_count = query.count()
|
|
|
|
|
|
|
2026-01-19 00:09:36 +08:00
|
|
|
|
# 排序和分页
|
|
|
|
|
|
agents = query.order_by(Agent.created_at.desc()).offset(skip).limit(limit).all()
|
2026-05-06 01:37:13 +08:00
|
|
|
|
|
2026-03-06 22:31:41 +08:00
|
|
|
|
# 转换为响应格式,确保user_id和日期时间字段正确处理
|
|
|
|
|
|
result = []
|
|
|
|
|
|
for agent in agents:
|
|
|
|
|
|
result.append({
|
|
|
|
|
|
"id": agent.id,
|
|
|
|
|
|
"name": agent.name,
|
|
|
|
|
|
"description": agent.description,
|
|
|
|
|
|
"workflow_config": agent.workflow_config,
|
2026-04-09 21:58:53 +08:00
|
|
|
|
"budget_config": agent.budget_config,
|
2026-03-06 22:31:41 +08:00
|
|
|
|
"version": agent.version,
|
|
|
|
|
|
"status": agent.status,
|
|
|
|
|
|
"user_id": agent.user_id if agent.user_id else None,
|
|
|
|
|
|
"created_at": agent.created_at if agent.created_at else datetime.now(),
|
|
|
|
|
|
"updated_at": agent.updated_at if agent.updated_at else datetime.now()
|
|
|
|
|
|
})
|
2026-05-06 01:37:13 +08:00
|
|
|
|
|
|
|
|
|
|
# 通过 X-Total-Count 响应头返回总数,前端借此正确分页
|
|
|
|
|
|
response.headers["X-Total-Count"] = str(total_count)
|
2026-03-06 22:31:41 +08:00
|
|
|
|
return result
|
2026-01-19 00:09:36 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@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,
|
2026-04-09 21:58:53 +08:00
|
|
|
|
budget_config=agent_data.budget_config,
|
2026-01-19 00:09:36 +08:00
|
|
|
|
user_id=current_user.id,
|
2026-05-07 08:02:38 +08:00
|
|
|
|
status="draft",
|
|
|
|
|
|
category=agent_data.category,
|
|
|
|
|
|
tags=agent_data.tags,
|
|
|
|
|
|
is_public=1 if agent_data.is_public else 0,
|
|
|
|
|
|
is_featured=1 if agent_data.is_featured else 0,
|
2026-01-19 00:09:36 +08:00
|
|
|
|
)
|
|
|
|
|
|
db.add(agent)
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
db.refresh(agent)
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(f"用户 {current_user.username} 创建了Agent: {agent.name} ({agent.id})")
|
|
|
|
|
|
return agent
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-04-13 20:17:18 +08:00
|
|
|
|
@router.get(
|
|
|
|
|
|
"/{agent_id}/preview-chat-history",
|
|
|
|
|
|
response_model=List[PreviewChatTurnResponse],
|
|
|
|
|
|
)
|
|
|
|
|
|
async def get_agent_preview_chat_history(
|
|
|
|
|
|
agent_id: str,
|
|
|
|
|
|
preview_user_id: Optional[str] = Query(
|
|
|
|
|
|
None,
|
|
|
|
|
|
description="预览会话 user_id(与创建执行时 input_data.user_id 一致),只返回本会话记录",
|
|
|
|
|
|
),
|
|
|
|
|
|
limit: int = Query(50, ge=1, le=200),
|
|
|
|
|
|
db: Session = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_current_user),
|
|
|
|
|
|
):
|
|
|
|
|
|
"""
|
|
|
|
|
|
获取设计器预览区的对话历史(按已完成执行还原)。
|
|
|
|
|
|
与前端 localStorage 中的 preview user_id 对齐,避免多人混在同一 Agent 下串会话。
|
|
|
|
|
|
须注册在 GET /{agent_id} 之前,避免路径被误匹配。
|
|
|
|
|
|
"""
|
|
|
|
|
|
agent = db.query(Agent).filter(Agent.id == agent_id).first()
|
|
|
|
|
|
if not agent:
|
|
|
|
|
|
raise NotFoundError(f"Agent不存在: {agent_id}")
|
|
|
|
|
|
if not check_agent_permission(db, current_user, agent, "read"):
|
|
|
|
|
|
raise HTTPException(status_code=403, detail="无权访问此Agent")
|
|
|
|
|
|
|
|
|
|
|
|
rows = fetch_agent_preview_chat_turns(
|
|
|
|
|
|
db,
|
|
|
|
|
|
agent_id,
|
|
|
|
|
|
preview_user_id=preview_user_id,
|
|
|
|
|
|
limit=limit,
|
|
|
|
|
|
)
|
|
|
|
|
|
return rows
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-01-19 00:09:36 +08:00
|
|
|
|
@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
|
2026-04-09 21:58:53 +08:00
|
|
|
|
|
|
|
|
|
|
if agent_data.budget_config is not None:
|
|
|
|
|
|
agent.budget_config = agent_data.budget_config
|
2026-01-19 00:09:36 +08:00
|
|
|
|
|
|
|
|
|
|
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,
|
2026-04-09 21:58:53 +08:00
|
|
|
|
budget_config=copy.deepcopy(original_agent.budget_config)
|
|
|
|
|
|
if original_agent.budget_config is not None
|
|
|
|
|
|
else None,
|
2026-01-19 17:52:29 +08:00
|
|
|
|
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)
|
|
|
|
|
|
):
|
|
|
|
|
|
"""导出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,
|
2026-04-09 21:58:53 +08:00
|
|
|
|
"budget_config": agent.budget_config,
|
2026-01-20 11:03:55 +08:00
|
|
|
|
"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
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-08 22:36:03 +08:00
|
|
|
|
@router.post("/{agent_id}/create-main-agent", response_model=AgentResponse)
|
|
|
|
|
|
async def create_main_agent(
|
|
|
|
|
|
agent_id: str,
|
|
|
|
|
|
db: Session = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_current_user),
|
|
|
|
|
|
):
|
|
|
|
|
|
"""一键将当前 Agent 升级为 Main Agent(预置专用工具 + 系统提示词)"""
|
|
|
|
|
|
agent = db.query(Agent).filter(Agent.id == agent_id).first()
|
|
|
|
|
|
if not agent:
|
|
|
|
|
|
raise NotFoundError(f"Agent不存在: {agent_id}")
|
|
|
|
|
|
if not check_agent_permission(db, current_user, agent, "write"):
|
|
|
|
|
|
raise HTTPException(status_code=403, detail="无权修改此Agent")
|
|
|
|
|
|
|
|
|
|
|
|
MAIN_AGENT_PROMPT = (
|
|
|
|
|
|
"你是一个 Main Agent(数字员工项目经理),负责理解用户目标、自主分解任务、"
|
|
|
|
|
|
"调度 Specialist Agent 执行、追踪进度并在必要时重新规划。\n\n"
|
|
|
|
|
|
"## 核心能力\n"
|
|
|
|
|
|
"1. **目标分解**:将用户的高层目标拆解为可执行的子任务(Task),考虑依赖关系\n"
|
|
|
|
|
|
"2. **任务分配**:根据任务特点选择最合适的执行策略(单Agent / Pipeline / Debate / Workflow)\n"
|
|
|
|
|
|
"3. **进度监控**:定期检查各任务状态,处理失败、重试、重新分配\n"
|
|
|
|
|
|
"4. **主动汇报**:关键节点通知用户,遇到阻塞时寻求决策\n\n"
|
|
|
|
|
|
"## 可用工具\n"
|
|
|
|
|
|
"- create_task: 创建子任务\n"
|
|
|
|
|
|
"- assign_task: 分配任务给指定Agent\n"
|
|
|
|
|
|
"- check_progress: 查看目标整体进度\n"
|
|
|
|
|
|
"- notify_user: 向用户发送通知\n"
|
|
|
|
|
|
"- agent_call: 调用其他Agent执行任务\n"
|
|
|
|
|
|
"- web_search: 搜索网络信息\n"
|
|
|
|
|
|
"- knowledge_graph_search: 搜索知识库\n\n"
|
|
|
|
|
|
"## 工作原则\n"
|
|
|
|
|
|
"- 接到目标后先理解、再分解、最后执行\n"
|
|
|
|
|
|
"- 复杂目标先做规划,与用户确认后再大规模执行\n"
|
|
|
|
|
|
"- 遇到无法处理的情况主动上报,不要静默失败"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
wc = agent.workflow_config or {"nodes": [], "edges": []}
|
|
|
|
|
|
wc["nodes"] = [
|
|
|
|
|
|
{
|
|
|
|
|
|
"id": "main_node",
|
|
|
|
|
|
"type": "agent",
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"system_prompt": MAIN_AGENT_PROMPT,
|
|
|
|
|
|
"model": "deepseek-v4-flash",
|
|
|
|
|
|
"provider": "deepseek",
|
|
|
|
|
|
"temperature": 0.7,
|
|
|
|
|
|
"max_iterations": 15,
|
|
|
|
|
|
"memory_max_history": 40,
|
|
|
|
|
|
"memory_vector_top_k": 10,
|
|
|
|
|
|
"memory_persist": True,
|
|
|
|
|
|
"memory_vector_enabled": True,
|
|
|
|
|
|
"memory_learning": True,
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
wc["edges"] = []
|
|
|
|
|
|
agent.workflow_config = wc
|
|
|
|
|
|
agent.agent_type = "main"
|
|
|
|
|
|
agent.description = agent.description or "Main Agent — 数字员工项目经理"
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
db.refresh(agent)
|
|
|
|
|
|
logger.info("用户 %s 将 Agent %s 升级为 Main Agent", current_user.username, agent.id)
|
|
|
|
|
|
return agent
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ExecuteRequest(BaseModel):
|
|
|
|
|
|
input: str = Field(..., description="用户输入/对话消息")
|
|
|
|
|
|
mode: str = Field(default="agent", description="编排模式: agent/sequential/pipeline/debate/graph")
|
|
|
|
|
|
agents: Optional[List[str]] = Field(default=None, description="编排模式下参与执行的Agent ID列表")
|
|
|
|
|
|
graph_nodes: Optional[List[Dict[str, Any]]] = Field(default=None, description="Graph模式的节点定义")
|
|
|
|
|
|
graph_edges: Optional[List[Dict[str, Any]]] = Field(default=None, description="Graph模式的边定义")
|
|
|
|
|
|
execution_id: Optional[str] = Field(default=None, description="可选的预创建执行记录ID")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ExecuteResponse(BaseModel):
|
|
|
|
|
|
execution_id: Optional[str] = None
|
|
|
|
|
|
task_id: Optional[str] = None
|
|
|
|
|
|
content: str = ""
|
|
|
|
|
|
iterations_used: int = 0
|
|
|
|
|
|
tool_calls_made: int = 0
|
|
|
|
|
|
status: str = "completed"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/{agent_id}/execute", response_model=ExecuteResponse)
|
|
|
|
|
|
async def execute_agent(
|
|
|
|
|
|
agent_id: str,
|
|
|
|
|
|
req: ExecuteRequest,
|
|
|
|
|
|
db: Session = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_current_user),
|
|
|
|
|
|
):
|
|
|
|
|
|
"""执行 Agent — 支持 agent/sequential/pipeline/debate/graph 五种模式"""
|
|
|
|
|
|
agent = db.query(Agent).filter(Agent.id == agent_id).first()
|
|
|
|
|
|
if not agent:
|
|
|
|
|
|
raise NotFoundError(f"Agent不存在: {agent_id}")
|
|
|
|
|
|
if not check_agent_permission(db, current_user, agent, "execute"):
|
|
|
|
|
|
raise HTTPException(status_code=403, detail="无权执行此Agent")
|
|
|
|
|
|
|
|
|
|
|
|
from app.agent_runtime import AgentRuntime, AgentConfig, AgentLLMConfig, AgentToolConfig, AgentMemoryConfig
|
|
|
|
|
|
from app.agent_runtime.orchestrator import AgentOrchestrator
|
|
|
|
|
|
|
|
|
|
|
|
if req.mode in ("sequential", "pipeline", "debate", "graph"):
|
|
|
|
|
|
orchestrator = AgentOrchestrator()
|
|
|
|
|
|
result = await orchestrator.run(
|
|
|
|
|
|
user_message=req.input,
|
|
|
|
|
|
mode=req.mode,
|
|
|
|
|
|
agents=[{"agent_id": aid} for aid in (req.agents or [])],
|
|
|
|
|
|
graph_nodes=req.graph_nodes,
|
|
|
|
|
|
graph_edges=req.graph_edges,
|
|
|
|
|
|
)
|
|
|
|
|
|
return ExecuteResponse(
|
|
|
|
|
|
content=result.get("output", ""),
|
|
|
|
|
|
iterations_used=result.get("iterations_used", 0),
|
|
|
|
|
|
tool_calls_made=result.get("tool_calls_made", 0),
|
|
|
|
|
|
status="completed",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
wc = agent.workflow_config or {}
|
|
|
|
|
|
nodes = wc.get("nodes", [])
|
|
|
|
|
|
system_prompt = agent.description or ""
|
|
|
|
|
|
model = "deepseek-v4-flash"
|
|
|
|
|
|
provider = "deepseek"
|
|
|
|
|
|
temperature = 0.7
|
|
|
|
|
|
max_iterations = 10
|
|
|
|
|
|
|
|
|
|
|
|
for n in nodes:
|
|
|
|
|
|
if n.get("type") not in ("agent", "llm", "template"):
|
|
|
|
|
|
continue
|
|
|
|
|
|
cfg = n.get("data", {}) if isinstance(n, dict) else getattr(n, "data", {})
|
|
|
|
|
|
system_prompt = cfg.get("system_prompt", "") or system_prompt
|
|
|
|
|
|
model = cfg.get("model", model)
|
|
|
|
|
|
provider = cfg.get("provider", provider)
|
|
|
|
|
|
temperature = float(cfg.get("temperature", temperature))
|
|
|
|
|
|
max_iterations = int(cfg.get("max_iterations", max_iterations))
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
config = AgentConfig(
|
|
|
|
|
|
name=agent.name or "agent",
|
|
|
|
|
|
system_prompt=system_prompt,
|
|
|
|
|
|
llm=AgentLLMConfig(model=model, provider=provider, temperature=temperature, max_iterations=max_iterations),
|
|
|
|
|
|
tools=AgentToolConfig(),
|
|
|
|
|
|
memory=AgentMemoryConfig(),
|
|
|
|
|
|
user_id=current_user.id,
|
|
|
|
|
|
memory_scope_id=str(agent.id),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
runtime = AgentRuntime(config=config)
|
|
|
|
|
|
run_result = await runtime.run(req.input)
|
|
|
|
|
|
return ExecuteResponse(
|
|
|
|
|
|
content=run_result.content or "",
|
|
|
|
|
|
iterations_used=run_result.iterations_used,
|
|
|
|
|
|
tool_calls_made=run_result.tool_calls_made,
|
|
|
|
|
|
status="completed",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-01-20 11:03:55 +08:00
|
|
|
|
@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
|
2026-04-09 21:58:53 +08:00
|
|
|
|
bc = agent_data.get("budget_config")
|
2026-01-20 11:03:55 +08:00
|
|
|
|
agent = Agent(
|
|
|
|
|
|
name=name,
|
|
|
|
|
|
description=description,
|
|
|
|
|
|
workflow_config={
|
|
|
|
|
|
"nodes": nodes,
|
|
|
|
|
|
"edges": edges
|
|
|
|
|
|
},
|
2026-04-09 21:58:53 +08:00
|
|
|
|
budget_config=bc if isinstance(bc, dict) else None,
|
2026-01-20 11:03:55 +08:00
|
|
|
|
user_id=current_user.id,
|
|
|
|
|
|
status="draft", # 导入的Agent默认为草稿状态
|
|
|
|
|
|
version=1
|
|
|
|
|
|
)
|
|
|
|
|
|
db.add(agent)
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
db.refresh(agent)
|
|
|
|
|
|
return agent
|