完整实现 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,497 @@
"""
原生 LLM Agent 适配器
直接调用 LLM API 实现 Agent不需要外部进程。
这是主要使用的适配器类型。
"""
import asyncio
import logging
from typing import Dict, Optional, Any
from datetime import datetime
from ..core.agent_adapter import (
CLIPluginAdapter,
Task,
Result,
AgentCapabilities
)
from ..services.llm_service import ModelRouter, get_llm_service
from ..services.agent_executor import AgentExecutor, get_agent_executor
from ..services.meeting_scheduler import get_meeting_scheduler
from ..services.agent_registry import get_agent_registry
from ..services.heartbeat import get_heartbeat_service
logger = logging.getLogger(__name__)
class NativeLLMAgent(CLIPluginAdapter):
"""
原生 LLM Agent
直接通过 LLM API 实现的 Agent特点
1. 无需外部进程,完全异步
2. 支持所有主流 LLM 提供商
3. 自动管理资源(文件锁、心跳)
4. 支持会议协作
"""
def __init__(
self,
agent_id: str,
name: str,
role: str,
model: str,
config: Dict[str, Any] = None,
llm_service: ModelRouter = None,
executor: AgentExecutor = None
):
self._id = agent_id
self._name = name
self._role = role
self._model = model
self.config = config or {}
self._version = "1.0.0"
# 获取服务
self.llm_service = llm_service or get_llm_service()
self.executor = executor or get_agent_executor(self.llm_service)
self.scheduler = get_meeting_scheduler()
self.registry = get_agent_registry()
self.heartbeat_service = get_heartbeat_service()
# 状态
self._is_running = False
self._current_task: Optional[Task] = None
logger.info(f"NativeLLMAgent 初始化: {self._id} ({self._name})")
# ========== 属性 ==========
@property
def id(self) -> str:
return self._id
@property
def name(self) -> str:
return self._name
@property
def version(self) -> str:
return self._version
@property
def role(self) -> str:
return self._role
@property
def model(self) -> str:
return self._model
@property
def capabilities(self) -> AgentCapabilities:
"""返回 Agent 能力声明"""
return AgentCapabilities(
can_execute_code=True,
can_read_files=True,
can_write_files=True,
can_analyze_code=True,
can_generate_tests=self._role in ["developer", "qa"],
can_review_code=self._role == "reviewer",
supported_languages=["Python", "JavaScript", "TypeScript", "Java", "Go"],
max_context_length=200000
)
@property
def is_running(self) -> bool:
return self._is_running
# ========== 核心能力 ==========
async def execute(self, task: Task) -> Result:
"""
执行任务
通过 AgentExecutor 协调 LLM 调用和资源管理
"""
self._current_task = task
try:
# 获取或注册 Agent 信息
agent_info = await self._ensure_registered()
# 使用执行引擎执行任务
result = await self.executor.execute_task(
agent_info,
task,
context=self.config.get("context", {})
)
logger.info(f"任务执行完成: {task.task_id} -> {result.success}")
return result
except Exception as e:
logger.error(f"任务执行失败: {task.task_id}: {e}", exc_info=True)
return Result(
success=False,
output="",
error=str(e)
)
finally:
self._current_task = None
async def join_meeting(self, meeting_id: str, timeout: int = 300) -> str:
"""
加入会议等待队列(栅栏同步)
当最后一个参与者到达时,会议自动开始
"""
logger.info(f"Agent {self._id} 等待会议: {meeting_id}")
# 更新心跳为等待状态
await self.update_heartbeat("waiting", f"等待会议: {meeting_id}", 0)
# 调用会议调度器
result = await self.scheduler.wait_for_meeting(
self._id,
meeting_id,
timeout=timeout
)
logger.info(f"会议 {meeting_id} 结果: {result}")
return result
async def write_state(self, state: Dict) -> None:
"""
写入状态到注册表
状态包含:当前任务、进度、临时数据等
"""
task = state.get("task", "")
progress = state.get("progress", 0)
await self.registry.update_state(self._id, task, progress)
logger.debug(f"状态已更新: {self._id} -> {progress}%")
async def read_others(self, agent_id: str) -> Dict:
"""
读取其他 Agent 的状态
用于 Agent 之间互相了解工作状态
"""
agent = await self.registry.get_agent(agent_id)
if not agent:
return {"error": f"Agent {agent_id} 不存在"}
state = await self.registry.get_state(agent_id)
heartbeat = await self.heartbeat_service.get_heartbeat(agent_id)
return {
"agent": {
"agent_id": agent.agent_id,
"name": agent.name,
"role": agent.role,
"model": agent.model,
"status": agent.status
},
"state": {
"current_task": state.current_task if state else None,
"progress": state.progress if state else 0
} if state else None,
"heartbeat": {
"status": heartbeat.status if heartbeat else None,
"last_seen": heartbeat.last_seen.isoformat() if heartbeat else None,
"is_alive": heartbeat.is_alive() if heartbeat else False
} if heartbeat else None
}
async def update_heartbeat(self, status: str, task: str = "", progress: int = 0) -> None:
"""
更新心跳
参数:
status: working, waiting, idle, error
task: 当前任务描述
progress: 进度 0-100
"""
await self.heartbeat_service.update_heartbeat(
self._id,
status,
task,
progress
)
# ========== 生命周期 ==========
async def initialize(self) -> None:
"""Agent 初始化"""
logger.info(f"Agent 初始化: {self._id}")
# 确保已注册
await self._ensure_registered()
# 发送初始心跳
await self.update_heartbeat("idle", "", 0)
self._is_running = True
async def shutdown(self) -> None:
"""Agent 关闭"""
logger.info(f"Agent 关闭: {self._id}")
# 更新状态为离线
await self.update_heartbeat("offline", "", 0)
self._is_running = False
async def health_check(self) -> bool:
"""健康检查"""
try:
heartbeat = await self.heartbeat_service.get_heartbeat(self._id)
return heartbeat is not None and heartbeat.is_alive()
except Exception:
return False
# ========== 会议相关 ==========
async def propose(self, meeting_id: str, content: str, step: str = "") -> None:
"""在会议中提出提案"""
from ..services.meeting_recorder import get_meeting_recorder
recorder = get_meeting_recorder()
await recorder.add_discussion(
meeting_id,
self._id,
self._role.upper(),
content,
step
)
logger.debug(f"提案已添加: {self._id} -> {meeting_id}")
async def discuss(self, meeting_id: str, content: str, step: str = "") -> None:
"""在会议中参与讨论"""
await self.propose(meeting_id, content, step)
async def vote(self, meeting_id: str, proposal_id: str, agree: bool) -> None:
"""对提案进行投票"""
# TODO: 实现投票机制
logger.debug(f"投票: {self._id} -> {proposal_id}: {agree}")
# ========== 私有方法 ==========
async def _ensure_registered(self):
"""确保 Agent 已注册"""
agent = await self.registry.get_agent(self._id)
if agent is None:
# 注册 Agent
agent = await self.registry.register_agent(
self._id,
self._name,
self._role,
self._model,
self.config.get("description", f"{self._name} - {self._role}")
)
logger.info(f"Agent 已注册: {self._id}")
return agent
# ========== 高级功能 ==========
async def collaborate_with(
self,
other_agent_ids: list,
task: str,
meeting_id: str = None
) -> Dict:
"""
与其他 Agent 协作完成任务
流程:
1. 创建或加入会议
2. 等待所有 Agent 到达
3. 讨论和分工
4. 执行分配的任务
5. 汇总结果
"""
if not meeting_id:
# 生成会议 ID
import uuid
meeting_id = f"meeting_{uuid.uuid4().hex[:12]}"
# 创建会议
await self.scheduler.create_meeting(
meeting_id,
f"协作任务: {task[:50]}",
[self._id] + other_agent_ids
)
# 更新心跳
await self.update_heartbeat("waiting", f"等待协作会议: {meeting_id}", 0)
# 提出初始提案
await self.propose(meeting_id, f"任务: {task}")
return {
"meeting_id": meeting_id,
"status": "waiting",
"participants": [self._id] + other_agent_ids
}
async def start_collaboration_loop(
self,
meeting_id: str,
max_iterations: int = 5
) -> Dict:
"""
启动协作循环
持续参与会议讨论,直到达成共识或达到最大迭代次数
"""
from ..services.meeting_recorder import get_meeting_recorder
recorder = get_meeting_recorder()
for iteration in range(max_iterations):
# 等待会议开始
result = await self.join_meeting(meeting_id)
if result == "started":
# 获取会议信息
meeting = await recorder.get_meeting(meeting_id)
if meeting and meeting.status == "completed":
return {
"status": "consensus_reached",
"consensus": meeting.consensus,
"iterations": iteration + 1
}
# 分析当前讨论,提出自己的观点
await self._analyze_and_respond(meeting_id)
elif result == "timeout":
return {
"status": "timeout",
"iterations": iteration + 1
}
return {
"status": "max_iterations_reached",
"iterations": max_iterations
}
async def _analyze_and_respond(self, meeting_id: str) -> None:
"""分析会议讨论并响应"""
from ..services.meeting_recorder import get_meeting_recorder
recorder = get_meeting_recorder()
meeting = await recorder.get_meeting(meeting_id)
if not meeting:
return
# 获取最近的讨论
recent_discussions = meeting.discussions[-5:] if meeting.discussions else []
# 构建分析提示
discussion_summary = "\n".join([
f"{d.agent} ({d.timestamp}): {d.content}"
for d in recent_discussions
])
response_prompt = f"""
你是 {self._name} ({self._role})。
以下是会议讨论的摘要:
{discussion_summary}
请基于你的角色 ({self._role}),给出你的回应:
- 如果你不同意之前的提案,说明理由
- 如果你有更好的建议,提出新方案
- 如果你同意,可以表示支持或补充细节
保持简洁,直接回应。
"""
# 调用 LLM 生成响应
if self.llm_service:
try:
llm_response = await self.llm_service.route_task(
task=response_prompt,
messages=[
{"role": "system", "content": f"你是 {self._name},一个 {self._role}"},
{"role": "user", "content": response_prompt}
]
)
# 发送响应到会议
await self.discuss(meeting_id, llm_response.content)
except Exception as e:
logger.error(f"生成会议响应失败: {e}")
class NativeLLMAgentFactory:
"""原生 LLM Agent 工厂类"""
@staticmethod
async def create(
agent_id: str,
name: str = None,
role: str = "developer",
model: str = "claude-sonnet-4.6",
config: Dict = None
) -> NativeLLMAgent:
"""
创建并初始化一个 Agent
参数:
agent_id: Agent 唯一标识
name: 显示名称(默认从 agent_id 生成)
role: 角色 (architect, pm, developer, qa, reviewer)
model: 使用的模型
config: 额外配置
"""
if name is None:
name = agent_id.replace("-", " ").title()
agent = NativeLLMAgent(
agent_id=agent_id,
name=name,
role=role,
model=model,
config=config
)
# 初始化 Agent
await agent.initialize()
return agent
@staticmethod
async def create_team(team_config: Dict) -> Dict[str, NativeLLMAgent]:
"""
创建一个 Agent 团队
配置格式:
{
"team_id": "dev-team-1",
"agents": [
{"id": "arch-001", "role": "architect", "model": "claude-opus-4.6"},
{"id": "dev-001", "role": "developer", "model": "claude-sonnet-4.6"},
{"id": "qa-001", "role": "qa", "model": "claude-haiku-4.6"}
]
}
"""
agents = {}
for agent_config in team_config.get("agents", []):
agent = await NativeLLMAgentFactory.create(
agent_id=agent_config["id"],
role=agent_config.get("role", "developer"),
model=agent_config.get("model", "claude-sonnet-4.6"),
config=agent_config.get("config", {})
)
agents[agent.id] = agent
return agents