feat: 添加swarms团队编排功能并优化agent委派系统
- 引入AgentTeamOrchestrator支持多agent协同任务执行 - 增加第三方swarms库依赖并配置git协议替换以改善包管理 - 扩展DelegationManager支持团队任务调度和进度跟踪 - 实现中文bigram分词算法提升中文任务检索准确性 - 调整A2AClient和DelegationManager超时时间从30秒增至600秒 - 优化AgentRunResult状态判断逻辑增加有意义摘要检测 - 修改Dockerfile配置npm仓库镜像地址和git协议映射 - 更新CLI命令行接口支持网关端口配置传递 - 调整提供者超时配置机制增强请求稳定性 - 移除过时的support_group字段简化agent描述符结构 - 增强错误处理和进度事件报告机制改进用户体验
This commit is contained in:
114
app-instance/backend/nanobot/agent_team/swarms_adapter.py
Normal file
114
app-instance/backend/nanobot/agent_team/swarms_adapter.py
Normal file
@ -0,0 +1,114 @@
|
||||
"""Thin adapters between nanobot agents and the vendored swarms runtime."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
from collections.abc import Awaitable, Callable
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from nanobot.agent.agent_registry import AgentDescriptor
|
||||
from nanobot.agent.run_result import AgentRunResult
|
||||
|
||||
MemberRunner = Callable[[AgentDescriptor, str, str, list[str]], Awaitable[AgentRunResult]]
|
||||
|
||||
|
||||
def _candidate_swarms_roots() -> list[Path]:
|
||||
"""Return likely vendored swarms paths across source and packaged layouts."""
|
||||
module_path = Path(__file__).resolve()
|
||||
candidates = [
|
||||
module_path.parents[2] / "third_party" / "swarms",
|
||||
Path("/opt/app/backend/third_party/swarms"),
|
||||
Path("/app/third_party/swarms"),
|
||||
Path.cwd() / "third_party" / "swarms",
|
||||
Path.cwd() / "backend" / "third_party" / "swarms",
|
||||
]
|
||||
unique: list[Path] = []
|
||||
seen: set[str] = set()
|
||||
for candidate in candidates:
|
||||
key = str(candidate)
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
unique.append(candidate)
|
||||
return unique
|
||||
|
||||
|
||||
def ensure_swarms_importable() -> None:
|
||||
"""Put the vendored swarms checkout on `sys.path` if needed."""
|
||||
for swarms_root in _candidate_swarms_roots():
|
||||
if swarms_root.exists() and str(swarms_root) not in sys.path:
|
||||
sys.path.insert(0, str(swarms_root))
|
||||
return
|
||||
|
||||
|
||||
def load_swarms_runtime() -> dict[str, Any]:
|
||||
"""Lazy-load swarms classes without making package import fragile."""
|
||||
ensure_swarms_importable()
|
||||
from swarms import AutoSwarmBuilder # type: ignore
|
||||
from swarms.structs.groupchat import GroupChat # type: ignore
|
||||
from swarms.structs.swarm_router import SwarmRouter # type: ignore
|
||||
|
||||
return {
|
||||
"AutoSwarmBuilder": AutoSwarmBuilder,
|
||||
"GroupChat": GroupChat,
|
||||
"SwarmRouter": SwarmRouter,
|
||||
}
|
||||
|
||||
|
||||
def __getattr__(name: str) -> Any:
|
||||
if name in {"AutoSwarmBuilder", "GroupChat", "SwarmRouter"}:
|
||||
return load_swarms_runtime()[name]
|
||||
raise AttributeError(name)
|
||||
|
||||
|
||||
def safe_swarms_name(agent_id: str) -> str:
|
||||
"""Return a GroupChat-friendly ASCII-ish name for @mentions."""
|
||||
normalized = "".join(ch if ch.isalnum() else "_" for ch in str(agent_id or "agent"))
|
||||
normalized = normalized.strip("_") or "agent"
|
||||
return f"agent_{normalized}"
|
||||
|
||||
|
||||
@dataclass(eq=False)
|
||||
class NanobotAgentAdapter:
|
||||
"""Callable wrapper that lets swarms invoke a nanobot agent descriptor."""
|
||||
|
||||
descriptor: AgentDescriptor
|
||||
run_id: str
|
||||
loop: asyncio.AbstractEventLoop
|
||||
member_runner: MemberRunner
|
||||
skills: list[str]
|
||||
results: list[AgentRunResult] = field(default_factory=list, init=False)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
self.agent_name = safe_swarms_name(self.descriptor.id)
|
||||
self.name = self.agent_name
|
||||
self.system_prompt = self.descriptor.system_prompt or self.descriptor.description
|
||||
self.__name__ = self.agent_name
|
||||
|
||||
def __call__(self, conversation_context: str) -> str:
|
||||
return self.run(conversation_context)
|
||||
|
||||
def run(self, task: str, *args: Any, **kwargs: Any) -> str:
|
||||
delegated_task = self._task_with_skills(task)
|
||||
future = asyncio.run_coroutine_threadsafe(
|
||||
self.member_runner(self.descriptor, delegated_task, self.run_id, list(self.skills)),
|
||||
self.loop,
|
||||
)
|
||||
result = future.result(timeout=300)
|
||||
self.results.append(result)
|
||||
if result.status != "ok":
|
||||
return f"Error from {self.agent_name}: {result.summary}"
|
||||
return result.summary
|
||||
|
||||
def _task_with_skills(self, conversation_context: str) -> str:
|
||||
if not self.skills:
|
||||
return conversation_context
|
||||
return (
|
||||
"Required skills for this delegated team member:\n"
|
||||
f"{', '.join(self.skills)}\n\n"
|
||||
"Swarms conversation context:\n"
|
||||
f"{conversation_context}"
|
||||
).strip()
|
||||
Reference in New Issue
Block a user