Files
beaver_project/app-instance/backend/nanobot/agent_team/swarms_planner.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

185 lines
7.3 KiB
Python

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