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 事件,避免视图串扰
This commit is contained in:
parent
761f8577be
commit
e506ffd539
@ -80,6 +80,7 @@ impl AgentFactory {
|
|||||||
message_seq: None,
|
message_seq: None,
|
||||||
subagent_description: None,
|
subagent_description: None,
|
||||||
nesting_depth: 0,
|
nesting_depth: 0,
|
||||||
|
parent_task_id: None,
|
||||||
});
|
});
|
||||||
// 如果有取消信号接收端,注入 Agent
|
// 如果有取消信号接收端,注入 Agent
|
||||||
if let Some(token) = request.cancel_token {
|
if let Some(token) = request.cancel_token {
|
||||||
|
|||||||
@ -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 },
|
||||||
|
|||||||
@ -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(),
|
||||||
|
|||||||
@ -262,6 +262,14 @@ impl SystemPromptProvider for StaticSystemPromptProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 从 session_id 中提取 task_id 部分
|
||||||
|
/// session_id 格式: "sub:{...}:task:{uuid}",提取 "task:{uuid}" 部分
|
||||||
|
fn extract_task_id_from_session_id(session_id: &str) -> Option<String> {
|
||||||
|
session_id
|
||||||
|
.rfind(":task:")
|
||||||
|
.map(|pos| session_id[pos + 1..].to_string())
|
||||||
|
}
|
||||||
|
|
||||||
/// 默认子代理运行时实现
|
/// 默认子代理运行时实现
|
||||||
pub struct DefaultSubAgentRuntime {
|
pub struct DefaultSubAgentRuntime {
|
||||||
config: SubAgentRuntimeConfig,
|
config: SubAgentRuntimeConfig,
|
||||||
@ -337,6 +345,15 @@ impl DefaultSubAgentRuntime {
|
|||||||
None, // 子代理不需要 skill provider
|
None, // 子代理不需要 skill provider
|
||||||
)
|
)
|
||||||
.map(|agent| {
|
.map(|agent| {
|
||||||
|
// 确定 parent_task_id:
|
||||||
|
// - 主 agent (depth=0) 创建子 agent → parent_task_id = None
|
||||||
|
// - 子 agent (depth>0) 创建孙 agent → parent_task_id = 父 agent 自身的 task_id
|
||||||
|
let parent_task_id = if parent_nesting_depth > 0 {
|
||||||
|
extract_task_id_from_session_id(&session.parent_session_id)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let agent = agent.with_tool_context(ToolContext {
|
let agent = agent.with_tool_context(ToolContext {
|
||||||
channel_name: Some(session.parent_channel_name.clone()),
|
channel_name: Some(session.parent_channel_name.clone()),
|
||||||
sender_id: None,
|
sender_id: None,
|
||||||
@ -347,6 +364,7 @@ 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,
|
||||||
|
parent_task_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 如果有 MessageBus,附加实时广播 emitter
|
// 如果有 MessageBus,附加实时广播 emitter
|
||||||
@ -534,6 +552,17 @@ 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 parent_context.nesting_depth > 0 {
|
||||||
|
if let Some(ref ptid) = parent_context.parent_task_id {
|
||||||
|
metadata.insert("parent_task_id".to_string(), ptid.clone());
|
||||||
|
} else if let Some(ptid) = extract_task_id_from_session_id(
|
||||||
|
parent_context.session_id.as_deref().unwrap_or(""),
|
||||||
|
) {
|
||||||
|
metadata.insert("parent_task_id".to_string(), ptid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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(),
|
||||||
|
|||||||
@ -183,6 +183,7 @@ mod tests {
|
|||||||
message_seq: Some(1),
|
message_seq: Some(1),
|
||||||
subagent_description: None,
|
subagent_description: None,
|
||||||
nesting_depth: 0,
|
nesting_depth: 0,
|
||||||
|
parent_task_id: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -409,6 +409,7 @@ mod tests {
|
|||||||
message_seq: Some(1),
|
message_seq: Some(1),
|
||||||
subagent_description: None,
|
subagent_description: None,
|
||||||
nesting_depth: 0,
|
nesting_depth: 0,
|
||||||
|
parent_task_id: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,8 @@ pub struct ToolContext {
|
|||||||
pub subagent_description: Option<String>,
|
pub subagent_description: Option<String>,
|
||||||
/// 当前嵌套深度(0 = 主 agent,1 = 子 agent,2 = 孙 agent...)
|
/// 当前嵌套深度(0 = 主 agent,1 = 子 agent,2 = 孙 agent...)
|
||||||
pub nesting_depth: u32,
|
pub nesting_depth: u32,
|
||||||
|
/// 父任务 ID(仅子/孙智能体有值,用于构建任务层级)
|
||||||
|
pub parent_task_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
|||||||
@ -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) => {
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user