完整实现 Swarm 多智能体协作系统

- 新增 CLIPluginAdapter 统一接口 (backend/app/core/agent_adapter.py)
- 新增 LLM 服务层,支持 Anthropic/OpenAI/DeepSeek/Ollama (backend/app/services/llm_service.py)
- 新增 Agent 执行引擎,支持文件锁自动管理 (backend/app/services/agent_executor.py)
- 新增 NativeLLMAgent 原生 LLM 适配器 (backend/app/adapters/native_llm_agent.py)
- 新增进程管理器 (backend/app/services/process_manager.py)
- 新增 Agent 控制 API (backend/app/routers/agents_control.py)
- 新增 WebSocket 实时通信 (backend/app/routers/websocket.py)
- 更新前端 AgentsPage,支持启动/停止 Agent
- 测试通过:Agent 启动、批量操作、栅栏同步

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Claude Code
2026-03-09 17:32:11 +08:00
commit dc398d7c7b
118 changed files with 23120 additions and 0 deletions

View File

@@ -0,0 +1,218 @@
"""
工作流管理 API 路由
"""
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from typing import List, Optional, Dict, Any
from pathlib import Path
from app.services.workflow_engine import get_workflow_engine
router = APIRouter()
# ========== 请求/响应模型 ==========
class MeetingNode(BaseModel):
"""工作流节点"""
meeting_id: str
title: str
node_type: str = "meeting"
attendees: List[str]
depends_on: List[str] = []
completed: bool = False
on_failure: Optional[str] = None
progress: Optional[str] = None
class WorkflowDetail(BaseModel):
"""工作流详情"""
workflow_id: str
name: str
description: str
status: str
progress: str
current_node: Optional[str] = None
meetings: List[MeetingNode]
class WorkflowSummary(BaseModel):
"""工作流摘要"""
workflow_id: str
name: str
status: str
progress: str
class JoinExecutionRequest(BaseModel):
"""加入执行节点请求"""
agent_id: str
class JumpRequest(BaseModel):
"""跳转请求"""
target_meeting_id: str
# ========== API 端点 ==========
@router.get("/files")
async def list_workflow_files():
"""获取工作流文件列表"""
engine = get_workflow_engine()
workflow_dir = Path(engine._storage.base_path) / engine.WORKFLOWS_DIR
if not workflow_dir.exists():
return {"files": []}
yaml_files = list(workflow_dir.glob("*.yaml")) + list(workflow_dir.glob("*.yml"))
files = []
for f in yaml_files:
stat = f.stat()
files.append({
"name": f.name,
"path": f"workflow/{f.name}",
"size": stat.st_size,
"modified": stat.st_mtime
})
return {"files": files}
@router.get("/list")
async def list_workflows():
"""获取已加载的工作流列表"""
engine = get_workflow_engine()
workflows = await engine.list_workflows()
return {"workflows": workflows}
@router.post("/start/{workflow_path:path}")
async def start_workflow(workflow_path: str):
"""
启动工作流
加载 YAML 工作流文件并准备执行
"""
engine = get_workflow_engine()
try:
workflow = await engine.load_workflow(workflow_path)
detail = await engine.get_workflow_detail(workflow.workflow_id)
return detail
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/{workflow_id}")
async def get_workflow(workflow_id: str):
"""获取工作流详情"""
engine = get_workflow_engine()
detail = await engine.get_workflow_detail(workflow_id)
if not detail:
raise HTTPException(status_code=404, detail="Workflow not found")
return detail
@router.get("/{workflow_id}/status")
async def get_workflow_status(workflow_id: str):
"""获取工作流状态"""
engine = get_workflow_engine()
status = await engine.get_workflow_status(workflow_id)
if not status:
raise HTTPException(status_code=404, detail="Workflow not found")
return status
@router.get("/{workflow_id}/next")
async def get_next_node(workflow_id: str):
"""获取下一个待执行节点"""
engine = get_workflow_engine()
meeting = await engine.get_next_meeting(workflow_id)
if not meeting:
return {"meeting": None, "message": "Workflow completed"}
return {
"meeting": {
"meeting_id": meeting.meeting_id,
"title": meeting.title,
"node_type": meeting.node_type,
"attendees": meeting.attendees,
"depends_on": meeting.depends_on
}
}
@router.post("/{workflow_id}/complete/{meeting_id}")
async def complete_node(workflow_id: str, meeting_id: str):
"""标记节点完成"""
engine = get_workflow_engine()
success = await engine.complete_meeting(workflow_id, meeting_id)
if not success:
raise HTTPException(status_code=404, detail="Workflow or meeting not found")
return {"success": True, "message": "Node completed"}
@router.post("/{workflow_id}/join/{meeting_id}")
async def join_execution_node(workflow_id: str, meeting_id: str, request: JoinExecutionRequest):
"""
Agent 加入执行节点
标记 Agent 已完成执行,当所有 Agent 都完成时返回 ready
"""
engine = get_workflow_engine()
result = await engine.join_execution_node(workflow_id, meeting_id, request.agent_id)
if result.get("status") == "error":
raise HTTPException(status_code=400, detail=result.get("message"))
return result
@router.get("/{workflow_id}/execution/{meeting_id}")
async def get_execution_node_status(workflow_id: str, meeting_id: str):
"""获取执行节点状态"""
engine = get_workflow_engine()
status = await engine.get_execution_status(workflow_id, meeting_id)
if not status:
raise HTTPException(status_code=404, detail="Execution node not found")
return status
@router.post("/{workflow_id}/jump")
async def jump_to_node(workflow_id: str, request: JumpRequest):
"""
强制跳转到指定节点
重置目标节点及所有后续节点的完成状态
"""
engine = get_workflow_engine()
success = await engine.jump_to_node(workflow_id, request.target_meeting_id)
if not success:
raise HTTPException(status_code=404, detail="Target node not found")
return {
"success": True,
"message": f"Jumped to {request.target_meeting_id}",
"detail": await engine.get_workflow_detail(workflow_id)
}
@router.post("/{workflow_id}/fail/{meeting_id}")
async def handle_node_failure(workflow_id: str, meeting_id: str):
"""
处理节点失败
根据 on_failure 配置跳转到指定节点
"""
engine = get_workflow_engine()
target = await engine.handle_failure(workflow_id, meeting_id)
if target:
return {
"success": True,
"message": f"Jumped to {target} due to failure",
"target": target,
"detail": await engine.get_workflow_detail(workflow_id)
}
return {
"success": True,
"message": "No failure handler configured"
}