Compare commits

..

2 Commits

Author SHA1 Message Date
oudecheng
2607ca4aa4 refactor(task): 优化任务工具和子代理深度管理
- 在 AgentContext 中新增 task_id 字段,用于标识当前智能体任务 ID
- 统一 TaskTool 构造函数,使用 Option<u32> 表示最大嵌套深度限制
- 调整子代理工具注册,适配新的 TaskTool 构造函数
- 修改子代理运行时创建逻辑,传递并设置 task_id 和 parent_task_id
- 删除重复的 task_id 提取函数,改用传递参数进行关联
- 深度校验改为判断 Option,支持无深度限制和有限层数
- 增强子代理间的任务层级关联,正确传递 parent_task_id 元数据
2026-06-18 11:41:40 +08:00
oudecheng
e506ffd539 feat(agent): 支持子孙智能体的父任务链路传递
- 在 agent_factory 和子代理相关结构体中新增 parent_task_id 字段
- 协议层序列化、反序列化及 WebSocket 适配器支持 parent_task_id 信息传递
- todo_read 与 todo_write 工具添加 parent_task_id 以保持任务层级一致
- 在 DefaultSubAgentRuntime 中实现从 session_id 提取 parent_task_id 的逻辑
- 子智能体创建孙智能体时,正确设置并传递 parent_task_id 元数据
- 前端 useChat 钩子过滤孙智能体的 TaskStarted 事件,避免视图串扰
2026-06-18 11:30:47 +08:00
12 changed files with 48 additions and 26 deletions

View File

@ -80,6 +80,8 @@ impl AgentFactory {
message_seq: None, message_seq: None,
subagent_description: None, subagent_description: None,
nesting_depth: 0, nesting_depth: 0,
task_id: None,
parent_task_id: None,
}); });
// 如果有取消信号接收端,注入 Agent // 如果有取消信号接收端,注入 Agent
if let Some(token) = request.cancel_token { if let Some(token) = request.cancel_token {

View File

@ -209,9 +209,9 @@ pub(crate) fn build_session_manager_with_sender(
// 注册 task 工具到子代理工具集(需在 runtime 创建之后,打破循环依赖) // 注册 task 工具到子代理工具集(需在 runtime 创建之后,打破循环依赖)
if factory.is_enabled("task") { if factory.is_enabled("task") {
subagent_tools.register(TaskTool::new_with_depth( subagent_tools.register(TaskTool::new(
subagent_runtime.clone(), subagent_runtime.clone(),
task_config.max_nesting_depth, Some(task_config.max_nesting_depth),
)); ));
} }

View File

@ -164,7 +164,7 @@ impl ToolRegistryFactory {
// 注册 Task 工具(如果启用且有 subagent_runtime // 注册 Task 工具(如果启用且有 subagent_runtime
if self.is_enabled("task") && self.task_config.enabled { if self.is_enabled("task") && self.task_config.enabled {
if let Some(runtime) = &self.subagent_runtime { if let Some(runtime) = &self.subagent_runtime {
registry.register(TaskTool::new(runtime.clone())); registry.register(TaskTool::new(runtime.clone(), None));
} }
} }

View File

@ -212,6 +212,8 @@ pub enum WsOutbound {
subagent_type: String, subagent_type: String,
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
topic_id: Option<String>, topic_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
parent_task_id: Option<String>,
}, },
#[serde(rename = "session_established")] #[serde(rename = "session_established")]
SessionEstablished { session_id: String }, SessionEstablished { session_id: String },

View File

@ -173,6 +173,7 @@ pub(crate) fn ws_outbound_from_outbound_message(message: &OutboundMessage) -> Ve
description: message.metadata.get("task_description").cloned().unwrap_or_default(), description: message.metadata.get("task_description").cloned().unwrap_or_default(),
subagent_type: message.metadata.get("task_subagent_type").cloned().unwrap_or_default(), subagent_type: message.metadata.get("task_subagent_type").cloned().unwrap_or_default(),
topic_id: message.metadata.get("topic_id").cloned(), topic_id: message.metadata.get("topic_id").cloned(),
parent_task_id: message.metadata.get("parent_task_id").cloned(),
}], }],
OutboundEventKind::StreamDelta => vec![WsOutbound::StreamDelta { OutboundEventKind::StreamDelta => vec![WsOutbound::StreamDelta {
id: message.tool_call_id.clone().unwrap_or_default(), id: message.tool_call_id.clone().unwrap_or_default(),

View File

@ -327,6 +327,7 @@ impl DefaultSubAgentRuntime {
session: &TaskSession, session: &TaskSession,
system_prompt: String, system_prompt: String,
parent_nesting_depth: u32, parent_nesting_depth: u32,
parent_task_id: Option<String>,
) -> Result<AgentLoop, TaskError> { ) -> Result<AgentLoop, TaskError> {
let prompt_provider = Arc::new(StaticSystemPromptProvider::new(system_prompt)); let prompt_provider = Arc::new(StaticSystemPromptProvider::new(system_prompt));
@ -347,6 +348,8 @@ impl DefaultSubAgentRuntime {
message_seq: None, message_seq: None,
subagent_description: Some(session.description.clone()), subagent_description: Some(session.description.clone()),
nesting_depth: parent_nesting_depth + 1, nesting_depth: parent_nesting_depth + 1,
task_id: Some(session.id.clone()),
parent_task_id,
}); });
// 如果有 MessageBus附加实时广播 emitter // 如果有 MessageBus附加实时广播 emitter
@ -534,6 +537,11 @@ impl SubAgentRuntime for DefaultSubAgentRuntime {
metadata.insert("task_subagent_type".to_string(), session.subagent_type.clone()); metadata.insert("task_subagent_type".to_string(), session.subagent_type.clone());
metadata.insert("topic_id".to_string(), session.parent_topic_id.clone().unwrap_or_default()); metadata.insert("topic_id".to_string(), session.parent_topic_id.clone().unwrap_or_default());
// 如果是子智能体创建的孙智能体,传递父 task_id
if let Some(ref ptid) = parent_context.task_id {
metadata.insert("parent_task_id".to_string(), ptid.clone());
}
let event = OutboundMessage { let event = OutboundMessage {
channel: session.parent_channel_name.clone(), channel: session.parent_channel_name.clone(),
chat_id: session.parent_chat_id.clone(), chat_id: session.parent_chat_id.clone(),
@ -566,7 +574,7 @@ impl SubAgentRuntime for DefaultSubAgentRuntime {
); );
// 7. 创建子代理 // 7. 创建子代理
let agent = self.create_subagent(&session, system_prompt, parent_context.nesting_depth)?; let agent = self.create_subagent(&session, system_prompt, parent_context.nesting_depth, parent_context.task_id.clone())?;
// 8. 执行任务 // 8. 执行任务
let result = self let result = self
@ -647,7 +655,7 @@ impl SubAgentRuntime for DefaultSubAgentRuntime {
); );
// 5. 创建子代理 // 5. 创建子代理
let agent = self.create_subagent(&session, system_prompt, parent_context.nesting_depth)?; let agent = self.create_subagent(&session, system_prompt, parent_context.nesting_depth, parent_context.task_id.clone())?;
// 6. 使用历史继续执行 // 6. 使用历史继续执行
let result = self let result = self

View File

@ -10,20 +10,15 @@ use super::types::{TaskDefinition, TaskToolArgs};
/// Task 工具 - 创建和管理子代理 /// Task 工具 - 创建和管理子代理
pub struct TaskTool { pub struct TaskTool {
runtime: Arc<dyn SubAgentRuntime>, runtime: Arc<dyn SubAgentRuntime>,
/// 最大嵌套深度(0 = 无限制用于主 agent>0 = 限制子代理最大嵌套层级 /// 最大嵌套深度(None = 无限制用于主 agentSome(N) = 允许最多 N 层嵌套
max_nesting_depth: u32, max_nesting_depth: Option<u32>,
} }
impl TaskTool { impl TaskTool {
pub fn new(runtime: Arc<dyn SubAgentRuntime>) -> Self { /// 创建 TaskTool
Self { /// - `max_nesting_depth = None`:无深度限制(主 agent
runtime, /// - `max_nesting_depth = Some(N)`:允许最多 N 层嵌套(子 agent
max_nesting_depth: 0, // 主 agent 无深度限制 pub fn new(runtime: Arc<dyn SubAgentRuntime>, max_nesting_depth: Option<u32>) -> Self {
}
}
/// 创建带嵌套深度限制的 TaskTool用于子代理
pub fn new_with_depth(runtime: Arc<dyn SubAgentRuntime>, max_nesting_depth: u32) -> Self {
Self { Self {
runtime, runtime,
max_nesting_depth, max_nesting_depth,
@ -139,17 +134,20 @@ impl Tool for TaskTool {
}); });
} }
// 4. 深度校验(仅对嵌套场景生效,主 agent 的 max_nesting_depth = 0 不限制) // 4. 深度校验仅对嵌套场景生效None = 不限制)
if self.max_nesting_depth > 0 && context.nesting_depth >= self.max_nesting_depth { // Some(N) 表示允许最多 N 层嵌套depth=1 的 agent 可创建 depth=2但 depth=2 不能再创建
if let Some(max_depth) = self.max_nesting_depth {
if context.nesting_depth > max_depth {
return Ok(ToolResult { return Ok(ToolResult {
success: false, success: false,
output: String::new(), output: String::new(),
error: Some(format!( error: Some(format!(
"Cannot create nested subagent: max nesting depth ({}) reached", "Cannot create nested subagent: max nesting depth ({}) reached",
self.max_nesting_depth max_depth
)), )),
}); });
} }
}
// 5. 执行任务 // 5. 执行任务
let result = if let Some(task_id) = task_args.task_id { let result = if let Some(task_id) = task_args.task_id {

View File

@ -183,6 +183,8 @@ mod tests {
message_seq: Some(1), message_seq: Some(1),
subagent_description: None, subagent_description: None,
nesting_depth: 0, nesting_depth: 0,
task_id: None,
parent_task_id: None,
} }
} }

View File

@ -409,6 +409,8 @@ mod tests {
message_seq: Some(1), message_seq: Some(1),
subagent_description: None, subagent_description: None,
nesting_depth: 0, nesting_depth: 0,
task_id: None,
parent_task_id: None,
} }
} }

View File

@ -20,6 +20,10 @@ pub struct ToolContext {
pub subagent_description: Option<String>, pub subagent_description: Option<String>,
/// 当前嵌套深度0 = 主 agent1 = 子 agent2 = 孙 agent... /// 当前嵌套深度0 = 主 agent1 = 子 agent2 = 孙 agent...
pub nesting_depth: u32, pub nesting_depth: u32,
/// 当前智能体自身的任务 ID主 agent 为 None子/孙 agent 为 Some(uuid)
pub task_id: Option<String>,
/// 父任务 ID仅子/孙智能体有值,用于构建任务层级)
pub parent_task_id: Option<String>,
} }
#[async_trait] #[async_trait]

View File

@ -431,6 +431,8 @@ export function useChat(): UseChatReturn {
const msg = message as TaskStarted const msg = message as TaskStarted
// 只 backfill 当前话题的 task tool_call避免跨话题串扰 // 只 backfill 当前话题的 task tool_call避免跨话题串扰
if (msg.topic_id && msg.topic_id !== selectedTopicRef.current) break if (msg.topic_id && msg.topic_id !== selectedTopicRef.current) break
// 孙智能体的 TaskStarted 不应 backfill 到主视图
if (msg.parent_task_id) break
// 立即更新对应的 task tool_call让用户可以点击查看实时进度 // 立即更新对应的 task tool_call让用户可以点击查看实时进度
setMessages((prev) => { setMessages((prev) => {

View File

@ -101,6 +101,7 @@ export interface TaskStarted {
description: string description: string
subagent_type: string subagent_type: string
topic_id?: string topic_id?: string
parent_task_id?: string
} }
export interface SessionEstablished { export interface SessionEstablished {