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:
241
app-instance/backend/nanobot/agent_team/orchestrator.py
Normal file
241
app-instance/backend/nanobot/agent_team/orchestrator.py
Normal file
@ -0,0 +1,241 @@
|
||||
"""Thin swarms orchestrator for `spawn_agent_team`."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from nanobot.agent.agent_registry import AgentRegistry
|
||||
from nanobot.agent.process_events import emit_process_event
|
||||
from nanobot.agent_team.memory import ProcedureMemory, RunMemory
|
||||
from nanobot.agent_team.swarms_adapter import MemberRunner
|
||||
from nanobot.agent_team.swarms_bridge import SwarmsBridge
|
||||
from nanobot.agent_team.swarms_planner import SwarmsRunPlanner
|
||||
from nanobot.agent_team.swarms_policy import SwarmsPolicy
|
||||
from nanobot.agent_team.target_resolver import TargetResolver
|
||||
from nanobot.agent_team.types import BridgeResult, ExecutionMode
|
||||
from nanobot.providers.base import LLMProvider
|
||||
|
||||
|
||||
class AgentTeamOrchestrator:
|
||||
"""Plan a swarms run, execute it, and persist the normalized result."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
workspace: Path,
|
||||
provider: LLMProvider,
|
||||
model: str | None,
|
||||
registry: AgentRegistry,
|
||||
bus: Any,
|
||||
local_executor: Any,
|
||||
member_runner: MemberRunner,
|
||||
max_parallel_agents: int = 4,
|
||||
gateway_port: int = 18790,
|
||||
) -> None:
|
||||
self.workspace = workspace
|
||||
self.registry = registry
|
||||
self.bus = bus
|
||||
self.local_executor = local_executor
|
||||
self.procedure_memory = ProcedureMemory(workspace)
|
||||
self.run_memory = RunMemory(workspace)
|
||||
self.policy = SwarmsPolicy(max_agents=max_parallel_agents)
|
||||
self.target_resolver = TargetResolver(
|
||||
workspace=workspace,
|
||||
registry=registry,
|
||||
provider=provider,
|
||||
model=model,
|
||||
max_parallel_agents=max_parallel_agents,
|
||||
gateway_port=gateway_port,
|
||||
)
|
||||
self.planner = SwarmsRunPlanner(
|
||||
model=model,
|
||||
registry=registry,
|
||||
target_resolver=self.target_resolver,
|
||||
procedure_memory=self.procedure_memory,
|
||||
policy=self.policy,
|
||||
)
|
||||
self.swarms = SwarmsBridge(
|
||||
workspace=workspace,
|
||||
registry=registry,
|
||||
member_runner=member_runner,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _clean_metadata(metadata: dict[str, Any]) -> dict[str, Any]:
|
||||
return {
|
||||
key: value
|
||||
for key, value in metadata.items()
|
||||
if value is not None
|
||||
and not (isinstance(value, str) and not value.strip())
|
||||
and not (isinstance(value, (list, tuple, set, dict)) and not value)
|
||||
}
|
||||
|
||||
async def _emit_trace(
|
||||
self,
|
||||
run_id: str,
|
||||
text: str,
|
||||
*,
|
||||
stage_label: str,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
) -> None:
|
||||
await emit_process_event(
|
||||
"process_run_progress",
|
||||
run_id=run_id,
|
||||
actor_type="system",
|
||||
actor_id="agent-team",
|
||||
actor_name="Agent Team",
|
||||
text=text,
|
||||
metadata=self._clean_metadata({
|
||||
"source": "agent_team_orchestrator",
|
||||
"stage_label": stage_label,
|
||||
**(metadata or {}),
|
||||
}),
|
||||
)
|
||||
|
||||
async def run_task(
|
||||
self,
|
||||
*,
|
||||
task: str,
|
||||
label: str,
|
||||
skills: list[str],
|
||||
origin: dict[str, str],
|
||||
announce_via_bus: bool,
|
||||
run_id: str,
|
||||
) -> BridgeResult:
|
||||
"""Run the team task through swarms only."""
|
||||
await self._emit_trace(
|
||||
run_id,
|
||||
"Preparing a swarms run specification for the agent team.",
|
||||
stage_label="准备 swarms 运行规格",
|
||||
metadata={
|
||||
"phase": "planning",
|
||||
"skills": list(skills),
|
||||
"origin": dict(origin),
|
||||
"announce_via_bus": announce_via_bus,
|
||||
},
|
||||
)
|
||||
spec = await self.planner.plan(task=task, label=label, skills=list(skills))
|
||||
await self._emit_trace(
|
||||
run_id,
|
||||
f"Swarms run spec is ready: {spec.swarm_type} with {len(spec.agent_ids)} agent(s).",
|
||||
stage_label="swarms 运行规格已就绪",
|
||||
metadata={
|
||||
"phase": "planning",
|
||||
"spec": spec.to_dict(),
|
||||
},
|
||||
)
|
||||
logger.info(
|
||||
"Agent team [{}] running swarms type={} agents={}",
|
||||
run_id,
|
||||
spec.swarm_type,
|
||||
spec.agent_ids,
|
||||
)
|
||||
|
||||
cleanup: dict[str, Any] = {}
|
||||
try:
|
||||
result = await self.swarms.run_spec(spec=spec, run_id=run_id)
|
||||
finally:
|
||||
cleanup = await self._cleanup_created_specialists(spec, run_id)
|
||||
if cleanup:
|
||||
result.raw.setdefault("provisioning_cleanup", cleanup)
|
||||
if cleanup.get("created_targets"):
|
||||
# The run used temporary specialists that have now been removed; do not
|
||||
# persist a reusable procedure pointing at deleted agent ids.
|
||||
result.candidate_procedure = None
|
||||
result.raw.setdefault("origin", dict(origin))
|
||||
result.raw.setdefault("announce_via_bus", announce_via_bus)
|
||||
|
||||
stored_procedure = None
|
||||
if result.success:
|
||||
stored_procedure = await self.procedure_memory.record_candidate(task, result)
|
||||
await self.run_memory.record_run(
|
||||
task,
|
||||
ExecutionMode.SWARMS,
|
||||
result,
|
||||
procedure_id=(
|
||||
stored_procedure.id
|
||||
if stored_procedure is not None
|
||||
else (
|
||||
result.matched_procedure.id
|
||||
if result.matched_procedure is not None
|
||||
else None
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
await self._emit_trace(
|
||||
run_id,
|
||||
"Swarms agent team run completed.",
|
||||
stage_label="swarms 团队执行完成",
|
||||
metadata={
|
||||
"phase": "completed",
|
||||
"success": result.success,
|
||||
"mode": result.mode.value,
|
||||
"stored_procedure_id": stored_procedure.id if stored_procedure else None,
|
||||
"attempt_count": len(result.attempts),
|
||||
},
|
||||
)
|
||||
return result
|
||||
|
||||
async def _cleanup_created_specialists(
|
||||
self,
|
||||
spec: Any,
|
||||
run_id: str,
|
||||
) -> dict[str, Any]:
|
||||
created_targets = self._created_provisioned_targets(spec)
|
||||
if not created_targets:
|
||||
return {}
|
||||
error = None
|
||||
try:
|
||||
deleted_targets = self.target_resolver.provisioning.cleanup_local_specialists(created_targets)
|
||||
except Exception as exc:
|
||||
deleted_targets = []
|
||||
error = str(exc)
|
||||
logger.warning("Failed to clean up auto-provisioned agent-team specialists: {}", exc)
|
||||
deleted_set = set(deleted_targets)
|
||||
cleanup = {
|
||||
"created_targets": created_targets,
|
||||
"deleted_targets": deleted_targets,
|
||||
"skipped_targets": [
|
||||
target
|
||||
for target in created_targets
|
||||
if target not in deleted_set
|
||||
],
|
||||
}
|
||||
if error is not None:
|
||||
cleanup["error"] = error
|
||||
try:
|
||||
await self._emit_trace(
|
||||
run_id,
|
||||
"Cleaned up auto-provisioned agent-team specialists.",
|
||||
stage_label="清理自动创建的团队成员",
|
||||
metadata={
|
||||
"phase": "cleanup",
|
||||
**cleanup,
|
||||
},
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.warning("Failed to emit agent-team cleanup trace: {}", exc)
|
||||
return cleanup
|
||||
|
||||
@staticmethod
|
||||
def _created_provisioned_targets(spec: Any) -> list[str]:
|
||||
metadata = getattr(spec, "metadata", {})
|
||||
if not isinstance(metadata, dict):
|
||||
return []
|
||||
target_plan = metadata.get("target_plan")
|
||||
if not isinstance(target_plan, dict):
|
||||
return []
|
||||
created_targets = target_plan.get("created_provisioned_targets")
|
||||
if not created_targets:
|
||||
plan_metadata = target_plan.get("metadata")
|
||||
if isinstance(plan_metadata, dict):
|
||||
created_targets = plan_metadata.get("created_provisioned_targets")
|
||||
return [
|
||||
target
|
||||
for target in dict.fromkeys(str(item).strip() for item in (created_targets or []))
|
||||
if target
|
||||
]
|
||||
Reference in New Issue
Block a user