498 lines
14 KiB
Python
498 lines
14 KiB
Python
|
|
"""
|
|||
|
|
原生 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
|