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:
184
app-instance/backend/nanobot/agent_team/swarms_planner.py
Normal file
184
app-instance/backend/nanobot/agent_team/swarms_planner.py
Normal file
@ -0,0 +1,184 @@
|
||||
"""Planner that prepares a minimal swarms run spec for agent-team tasks."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from nanobot.agent.agent_registry import AgentRegistry
|
||||
from nanobot.agent_team.memory import ProcedureMemory
|
||||
from nanobot.agent_team.swarms_adapter import load_swarms_runtime, safe_swarms_name
|
||||
from nanobot.agent_team.swarms_policy import SwarmsPolicy
|
||||
from nanobot.agent_team.target_resolver import TargetResolver
|
||||
from nanobot.agent_team.types import SwarmsRunSpec
|
||||
|
||||
|
||||
class SwarmsRunPlanner:
|
||||
"""Generate `SwarmsRunSpec` without rebuilding swarms' own planner/runtime."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
model: str | None,
|
||||
registry: AgentRegistry,
|
||||
target_resolver: TargetResolver,
|
||||
procedure_memory: ProcedureMemory,
|
||||
policy: SwarmsPolicy,
|
||||
) -> None:
|
||||
self.model = model
|
||||
self.registry = registry
|
||||
self.target_resolver = target_resolver
|
||||
self.procedure_memory = procedure_memory
|
||||
self.policy = policy
|
||||
|
||||
async def plan(self, *, task: str, label: str, skills: list[str]) -> SwarmsRunSpec:
|
||||
memory_hint = self.procedure_memory.match_procedure(task)
|
||||
if self._should_auto_build(task, skills, memory_hint):
|
||||
raw_config = await self._run_auto_swarm_builder(task, skills, memory_hint)
|
||||
return await self._spec_from_auto_config(task, label, skills, raw_config)
|
||||
|
||||
target_plan = await self.target_resolver.resolve_team_targets(
|
||||
task=task,
|
||||
skills=skills,
|
||||
required_specialists=self._simple_required_roles(task, skills),
|
||||
)
|
||||
return SwarmsRunSpec(
|
||||
task=task,
|
||||
label=label,
|
||||
skills=list(skills),
|
||||
swarm_type="GroupChat",
|
||||
agent_ids=list(target_plan.final_targets),
|
||||
auto_generated=False,
|
||||
max_loops=2,
|
||||
rules=self._default_rules(),
|
||||
metadata={
|
||||
"memory_hint": memory_hint.id if memory_hint else None,
|
||||
"target_plan": target_plan.to_dict(),
|
||||
},
|
||||
)
|
||||
|
||||
def _should_auto_build(self, task: str, skills: list[str], memory_hint: Any) -> bool:
|
||||
source = task or ""
|
||||
text = source.lower()
|
||||
markers = ("架构", "调研", "复杂", "多阶段", "strategy", "architecture", "research")
|
||||
return len(source) > 80 or memory_hint is not None or any(
|
||||
marker in source or marker in text for marker in markers
|
||||
)
|
||||
|
||||
async def _run_auto_swarm_builder(self, task: str, skills: list[str], memory_hint: Any) -> dict[str, Any]:
|
||||
try:
|
||||
runtime = load_swarms_runtime()
|
||||
builder = runtime["AutoSwarmBuilder"](
|
||||
name="nanobot-auto-swarm-builder",
|
||||
description="Generate a safe swarms router config for nanobot",
|
||||
max_loops=1,
|
||||
model_name=self._auto_builder_model_name(),
|
||||
generate_router_config=True,
|
||||
execution_type="return-swarm-router-config",
|
||||
interactive=False,
|
||||
verbose=False,
|
||||
)
|
||||
raw = await asyncio.to_thread(
|
||||
builder.run,
|
||||
self._auto_builder_prompt(task, skills, memory_hint),
|
||||
)
|
||||
if isinstance(raw, dict):
|
||||
return raw
|
||||
if isinstance(raw, str):
|
||||
return json.loads(raw)
|
||||
model_dump = getattr(raw, "model_dump", None)
|
||||
if callable(model_dump):
|
||||
payload = model_dump()
|
||||
return payload if isinstance(payload, dict) else {}
|
||||
except Exception as exc:
|
||||
logger.warning("AutoSwarmBuilder failed; falling back to deterministic run spec: {}", exc)
|
||||
return {}
|
||||
|
||||
def _auto_builder_model_name(self) -> str:
|
||||
model_name = str(self.model or "").strip()
|
||||
if not model_name:
|
||||
return "gpt-4.1"
|
||||
if "/" in model_name:
|
||||
return model_name
|
||||
return f"openai/{model_name}"
|
||||
|
||||
def _auto_builder_prompt(self, task: str, skills: list[str], memory_hint: Any) -> str:
|
||||
return (
|
||||
"Build a multi-agent swarm router config for nanobot.\n\n"
|
||||
f"User task:\n{task}\n\n"
|
||||
f"Required nanobot skills:\n{skills}\n\n"
|
||||
f"Procedure memory hint:\n{memory_hint}\n\n"
|
||||
"Return a valid JSON object that matches the swarm router config schema.\n\n"
|
||||
"Hard constraints:\n"
|
||||
"- Every generated role must follow the listed skills.\n"
|
||||
"- Do not replace, ignore, or reinterpret the listed skills.\n"
|
||||
"- Do not add external tools, credentials, MCP URLs, or hidden side effects.\n"
|
||||
"- Prefer existing nanobot registry agents; only describe missing roles."
|
||||
)
|
||||
|
||||
async def _spec_from_auto_config(
|
||||
self,
|
||||
task: str,
|
||||
label: str,
|
||||
skills: list[str],
|
||||
raw_config: dict[str, Any],
|
||||
) -> SwarmsRunSpec:
|
||||
safe_config = self.policy.validate_auto_config(raw_config)
|
||||
target_plan = await self.target_resolver.resolve_team_targets(
|
||||
task=task,
|
||||
skills=skills,
|
||||
required_specialists=self._roles_from_auto_config(safe_config),
|
||||
)
|
||||
return SwarmsRunSpec(
|
||||
task=task,
|
||||
label=label,
|
||||
skills=list(skills),
|
||||
swarm_type=str(safe_config.get("swarm_type") or "GroupChat"),
|
||||
agent_ids=list(target_plan.final_targets),
|
||||
auto_generated=bool(raw_config),
|
||||
max_loops=min(int(safe_config.get("max_loops") or 2), self.policy.max_loops),
|
||||
rearrange_flow=self._rearrange_flow(safe_config, target_plan.final_targets),
|
||||
rules=str(safe_config.get("rules") or self._default_rules()),
|
||||
raw_auto_config=safe_config,
|
||||
metadata={
|
||||
"target_plan": target_plan.to_dict(),
|
||||
"auto_builder_returned_config": bool(raw_config),
|
||||
},
|
||||
)
|
||||
|
||||
def _rearrange_flow(self, config: dict[str, Any], agent_ids: list[str]) -> str | None:
|
||||
if str(config.get("swarm_type") or "") == "AgentRearrange" and agent_ids:
|
||||
return " -> ".join(safe_swarms_name(agent_id) for agent_id in agent_ids)
|
||||
flow = config.get("rearrange_flow") or config.get("flow")
|
||||
if flow:
|
||||
return str(flow)
|
||||
return None
|
||||
|
||||
def _roles_from_auto_config(self, config: dict[str, Any]) -> list[str]:
|
||||
roles: list[str] = []
|
||||
for item in config.get("agents", []) or []:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
role = str(
|
||||
item.get("description")
|
||||
or item.get("system_prompt")
|
||||
or item.get("agent_name")
|
||||
or ""
|
||||
).strip()
|
||||
if role:
|
||||
roles.append(role)
|
||||
return roles or ["general specialist", "synthesis analyst"]
|
||||
|
||||
def _simple_required_roles(self, task: str, skills: list[str]) -> list[str]:
|
||||
if skills:
|
||||
return [f"{skill} specialist" for skill in skills]
|
||||
return ["general specialist", "synthesis analyst"]
|
||||
|
||||
def _default_rules(self) -> str:
|
||||
return (
|
||||
"You are running inside a nanobot agent team. Follow the provided skills, "
|
||||
"stay within your assigned role, and produce a concise final synthesis."
|
||||
)
|
||||
Reference in New Issue
Block a user