feat(agent): 实现委派工具重构,支持子代理和代理团队模式
- 新增 spawn_subagent 和 spawn_agent_team 工具,替代原有的 spawn 工具 - 重构 DelegationManager 以支持单个子代理和代理团队两种委派模式 - 更新系统提示词中的委派策略说明,明确使用场景和区别 - 添加技能上下文传递功能,确保委派任务遵循指定技能 - 实现代理内部的受控下游委派机制,防止无限嵌套 - 更新工具注册和上下文设置逻辑以适配新架构
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
"""spawn 工具:用于把任务委派给后台 agent。"""
|
||||
"""委派工具:分别暴露 subagent 与 agent team 两种调用接口。"""
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
@ -8,74 +8,178 @@ if TYPE_CHECKING:
|
||||
from nanobot.agent.delegation import DelegationManager
|
||||
|
||||
|
||||
class SpawnTool(Tool):
|
||||
"""
|
||||
后台委派工具。
|
||||
|
||||
作用:
|
||||
1. 把耗时/可并行的任务委派给 DelegationManager;
|
||||
2. 目标可以是本地 agent、A2A 远端 agent 或 agent group;
|
||||
3. 后台任务异步执行,不阻塞当前对话回合。
|
||||
"""
|
||||
class DelegationTool(Tool):
|
||||
"""委派类工具的公共上下文注入逻辑。"""
|
||||
|
||||
def __init__(self, manager: "DelegationManager"):
|
||||
# manager 负责真正创建 asyncio 后台任务并管理生命周期。
|
||||
self._manager = manager
|
||||
# 默认来源会话(CLI 直连场景)。实际会在每轮由 loop._set_tool_context 覆盖。
|
||||
self._origin_channel = "cli"
|
||||
self._origin_chat_id = "direct"
|
||||
self._announce_via_bus = True
|
||||
|
||||
def set_context(self, channel: str, chat_id: str, announce_via_bus: bool = True) -> None:
|
||||
"""设置后台委派结果回传的目标会话。"""
|
||||
# 委派任务完成后并不会直接给用户发消息,
|
||||
# 而是把结果发回这里记录的 origin(channel/chat_id)对应会话。
|
||||
self._origin_channel = channel
|
||||
self._origin_chat_id = chat_id
|
||||
self._announce_via_bus = announce_via_bus
|
||||
|
||||
|
||||
class SpawnSubagentTool(DelegationTool):
|
||||
"""把任务委派给单个 subagent。"""
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
# 暴露给 LLM 的工具名;模型会用这个名字发起 function call。
|
||||
return "spawn"
|
||||
return "spawn_subagent"
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
# 给模型看的能力描述,强调“后台执行 + 完成后回报”语义。
|
||||
return (
|
||||
"Delegate a task to a background agent. "
|
||||
"Delegate a focused task to one background subagent. "
|
||||
"Use this for complex or time-consuming work that can run independently. "
|
||||
"You can target a specific agent, a group of agents, or let the system choose. "
|
||||
"The delegated agent(s) will report back when done."
|
||||
"You only provide the task and optional required skills; downstream routing decides the concrete agent. "
|
||||
"The subagent will report back when done."
|
||||
)
|
||||
|
||||
@property
|
||||
def parameters(self) -> dict[str, Any]:
|
||||
# OpenAI function schema:定义模型可传入的参数结构。
|
||||
return {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"task": {
|
||||
"type": "string",
|
||||
"description": "The task for the delegated agent to complete",
|
||||
"description": "The task for the delegated subagent to complete",
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Optional short label for the task (for display)",
|
||||
},
|
||||
"target": {
|
||||
"type": "string",
|
||||
"description": "Optional agent ID or name for a single target",
|
||||
},
|
||||
"targets": {
|
||||
"skills": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "Optional list of agent IDs/names for a group task",
|
||||
"description": "Optional list of skill names the delegated worker must follow",
|
||||
},
|
||||
},
|
||||
"required": ["task"],
|
||||
}
|
||||
|
||||
async def execute(
|
||||
self,
|
||||
task: str,
|
||||
label: str | None = None,
|
||||
skills: list[str] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> str:
|
||||
"""创建并启动一个 subagent 后台任务。"""
|
||||
return await self._manager.dispatch_subagent(
|
||||
task=task,
|
||||
label=label,
|
||||
skills=skills,
|
||||
origin_channel=self._origin_channel,
|
||||
origin_chat_id=self._origin_chat_id,
|
||||
announce_via_bus=self._announce_via_bus,
|
||||
)
|
||||
|
||||
|
||||
class SpawnAgentTeamTool(DelegationTool):
|
||||
"""启动一个 agent team 任务。"""
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "spawn_agent_team"
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return (
|
||||
"Start an agent team for parallel exploration. "
|
||||
"Use this when multiple agents should investigate the task in parallel and return a combined result. "
|
||||
"You only provide the task and optional required skills; downstream routing selects the concrete members."
|
||||
)
|
||||
|
||||
@property
|
||||
def parameters(self) -> dict[str, Any]:
|
||||
return {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"task": {
|
||||
"type": "string",
|
||||
"description": "The shared task for the agent team",
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Optional short label for the team task (for display)",
|
||||
},
|
||||
"skills": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "Optional list of skill names the team must follow",
|
||||
},
|
||||
},
|
||||
"required": ["task"],
|
||||
}
|
||||
|
||||
async def execute(
|
||||
self,
|
||||
task: str,
|
||||
label: str | None = None,
|
||||
skills: list[str] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> str:
|
||||
"""创建并启动一个 agent team 后台任务。"""
|
||||
return await self._manager.dispatch_agent_team(
|
||||
task=task,
|
||||
label=label,
|
||||
skills=skills,
|
||||
origin_channel=self._origin_channel,
|
||||
origin_chat_id=self._origin_chat_id,
|
||||
announce_via_bus=self._announce_via_bus,
|
||||
)
|
||||
|
||||
|
||||
class NestedDelegateTool(Tool):
|
||||
"""供 delegated worker 使用的受控下游委派工具。"""
|
||||
|
||||
def __init__(self, manager: "DelegationManager", default_skills: list[str] | None = None):
|
||||
self._manager = manager
|
||||
self._default_skills = [str(item).strip() for item in (default_skills or []) if str(item).strip()]
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "delegate_task"
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return (
|
||||
"Synchronously delegate a downstream task from a delegated worker. "
|
||||
"Use this only when specialized help is needed. "
|
||||
"It can route to an A2A agent or an ephemeral local subagent, but never creates a persistent subagent."
|
||||
)
|
||||
|
||||
@property
|
||||
def parameters(self) -> dict[str, Any]:
|
||||
return {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"task": {
|
||||
"type": "string",
|
||||
"description": "The downstream task to delegate",
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Optional short label for the downstream task",
|
||||
},
|
||||
"target": {
|
||||
"type": "string",
|
||||
"description": "Optional agent ID or name for the downstream worker",
|
||||
},
|
||||
"strategy": {
|
||||
"type": "string",
|
||||
"enum": ["auto", "local", "plugin", "a2a", "group"],
|
||||
"description": "Routing strategy. Default is auto.",
|
||||
"enum": ["auto", "a2a", "ephemeral_subagent"],
|
||||
"description": "Routing strategy for downstream delegation. Default is auto.",
|
||||
},
|
||||
"skills": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "Optional required skills for the downstream delegate. Defaults to the current worker's required skills.",
|
||||
},
|
||||
},
|
||||
"required": ["task"],
|
||||
@ -86,20 +190,15 @@ class SpawnTool(Tool):
|
||||
task: str,
|
||||
label: str | None = None,
|
||||
target: str | None = None,
|
||||
targets: list[str] | None = None,
|
||||
strategy: str = "auto",
|
||||
skills: list[str] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> str:
|
||||
"""创建并启动一个后台委派任务。"""
|
||||
# 这里仅负责转发请求,不在本工具内执行实际任务逻辑。
|
||||
# 返回值是“已启动”状态文本,真正结果稍后通过主消息总线回传。
|
||||
return await self._manager.dispatch(
|
||||
"""同步执行一次受控下游委派,并把结果返回给当前 worker。"""
|
||||
return await self._manager.delegate_for_subagent(
|
||||
task=task,
|
||||
label=label,
|
||||
target=target,
|
||||
targets=targets,
|
||||
strategy=strategy,
|
||||
origin_channel=self._origin_channel,
|
||||
origin_chat_id=self._origin_chat_id,
|
||||
announce_via_bus=self._announce_via_bus,
|
||||
skills=skills if skills is not None else list(self._default_skills),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user