Files
beaver_project/app-instance/backend/nanobot/agent_team/swarms_adapter.py
steven_li cdfc222c9f feat: 添加swarms团队编排功能并优化agent委派系统
- 引入AgentTeamOrchestrator支持多agent协同任务执行
- 增加第三方swarms库依赖并配置git协议替换以改善包管理
- 扩展DelegationManager支持团队任务调度和进度跟踪
- 实现中文bigram分词算法提升中文任务检索准确性
- 调整A2AClient和DelegationManager超时时间从30秒增至600秒
- 优化AgentRunResult状态判断逻辑增加有意义摘要检测
- 修改Dockerfile配置npm仓库镜像地址和git协议映射
- 更新CLI命令行接口支持网关端口配置传递
- 调整提供者超时配置机制增强请求稳定性
- 移除过时的support_group字段简化agent描述符结构
- 增强错误处理和进度事件报告机制改进用户体验
2026-04-14 14:34:23 +08:00

115 lines
3.9 KiB
Python

"""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()