feat(engine): 实现 Agent 角色热切换与子代理调度
新增 SessionManager::switch_agent / dispatch / dispatch_all / dispatch_stream 四个核心方法,补齐多 Agent 基础系统原语。 交付物: - switch_agent 运行时替换 Arc<dyn Agent>,保留上下文并更新 SessionMeta - dispatch 单任务派发:create_child → inherit_memory → submit_turn - dispatch_all 并行派发:Semaphore 并发控制 + 部分成功语义 - dispatch_stream 流式派发:unbounded_channel + spawn task 消息重建 + finalize_turn - DispatchConfig / SubTaskResult / SubTaskStreamEvent 公开类型 - 4 个端到端示例(agent_switch_demo / sub_agent_dispatch_demo / bridge_keys_demo / dispatch_stream_demo) 辅助变更: - EngineError 新增 DispatchFailed 变体 - CostTracker 实现 From<Usage> 转换 - save_session_meta / load_session_meta 改 pub(crate) 供 switch.rs 使用 测试: +17 个内联测试(4 switch + 5 dispatch + 4 dispatch_all + 4 dispatch_stream),全量 391 passed / 0 failed, clippy 0 警告,doc 0 warning。
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
//! agent_switch_demo —— Agent 角色热切换示例。
|
||||
//!
|
||||
//! 演示:
|
||||
//! 1. 创建 session(绑定 Analyst agent)
|
||||
//! 2. 提交一轮对话(角色 A 输出"分析数据")
|
||||
//! 3. switch_agent 切换为 Reporter agent
|
||||
//! 4. 提交第二轮对话(角色 B 基于已有上下文输出"报告")
|
||||
//! 5. 验证:turn_index 连续、session_memory 保留、slot 历史保留
|
||||
//!
|
||||
//! 运行:`cargo run --example agent_switch_demo`
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use agcore::agent::{Agent, AgentBuilder};
|
||||
use agcore::engine::SessionManager;
|
||||
use agcore::llm::hooks::HookExecutor;
|
||||
use agcore::llm::mock::MockProvider;
|
||||
use agcore::llm::types::Usage;
|
||||
use agcore::llm::types::message::{ContentBlock, Message};
|
||||
use agcore::llm::types::response_v2::{MessageResponse, StopReason};
|
||||
use agcore::memory::store::InMemoryStore;
|
||||
use agcore::tools::ToolRegistry;
|
||||
|
||||
struct AnalystAgent;
|
||||
struct ReporterAgent;
|
||||
|
||||
impl Agent for AnalystAgent {
|
||||
fn name(&self) -> &str {
|
||||
"analyst"
|
||||
}
|
||||
fn system_prompt(&self) -> Option<&str> {
|
||||
Some("You are a data analyst. Analyze the input concisely.")
|
||||
}
|
||||
}
|
||||
|
||||
impl Agent for ReporterAgent {
|
||||
fn name(&self) -> &str {
|
||||
"reporter"
|
||||
}
|
||||
fn system_prompt(&self) -> Option<&str> {
|
||||
Some("You are a report writer. Write concise reports based on context.")
|
||||
}
|
||||
}
|
||||
|
||||
fn assistant_text(text: &str) -> MessageResponse {
|
||||
MessageResponse {
|
||||
id: String::new(),
|
||||
model: String::new(),
|
||||
message: Message::Assistant {
|
||||
content: vec![ContentBlock::Text { text: text.into() }],
|
||||
},
|
||||
usage: Usage::from_input_output(8, 4),
|
||||
stop_reason: StopReason::Stop,
|
||||
extra: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
println!("=== Agent Switch Demo ===\n");
|
||||
|
||||
// 1. 准备组件
|
||||
let store: Arc<dyn agcore::memory::store::MemoryStore> = Arc::new(InMemoryStore::new());
|
||||
let provider = Arc::new(MockProvider::new(vec![
|
||||
assistant_text("Analyst: data analyzed (Q3 sales up 15%)"),
|
||||
assistant_text("Reporter: report drafted (3 paragraphs)"),
|
||||
]));
|
||||
let bundle = Arc::new(
|
||||
AgentBuilder::new()
|
||||
.provider(provider)
|
||||
.tool_registry(Arc::new(ToolRegistry::new()))
|
||||
.hook_executor(Arc::new(HookExecutor::new()))
|
||||
.session_memory_backend(store.clone())
|
||||
.build()
|
||||
.expect("RuntimeBundle 装配失败"),
|
||||
);
|
||||
|
||||
let analyst: Arc<dyn Agent> = Arc::new(AnalystAgent);
|
||||
let reporter: Arc<dyn Agent> = Arc::new(ReporterAgent);
|
||||
|
||||
let sm = Arc::new(SessionManager::new(store));
|
||||
let session_id = sm.create(analyst, bundle.clone()).await.expect("create");
|
||||
println!("[1] session created: {session_id}");
|
||||
|
||||
// 2. Analyst 跑一轮
|
||||
let resp1 = sm
|
||||
.submit_turn(&session_id, "Analyze Q3 sales data")
|
||||
.await
|
||||
.expect("submit_turn 1");
|
||||
println!("[2] analyst turn 1: {:?}", resp1.text());
|
||||
|
||||
// 3. 切换到 Reporter
|
||||
sm.switch_agent(&session_id, reporter)
|
||||
.await
|
||||
.expect("switch_agent");
|
||||
println!("[3] agent switched to 'reporter'");
|
||||
|
||||
// 4. Reporter 跑一轮(基于已有上下文)
|
||||
let resp2 = sm
|
||||
.submit_turn(&session_id, "Write a report based on the analysis")
|
||||
.await
|
||||
.expect("submit_turn 2");
|
||||
println!("[4] reporter turn 2: {:?}", resp2.text());
|
||||
|
||||
// 5. 验证 turn_index 连续
|
||||
let (turn_index, agent_name_owned) = {
|
||||
let session = sm.get(&session_id).await.unwrap();
|
||||
let guard = session.lock().await;
|
||||
(guard.turn_index(), guard.agent.name().to_string())
|
||||
};
|
||||
println!("\n[verify] turn_index = {turn_index}, agent = {agent_name_owned}");
|
||||
assert_eq!(turn_index, 2, "turn_index should be 2 after 2 turns");
|
||||
assert_eq!(agent_name_owned, "reporter", "current agent should be reporter");
|
||||
println!("✓ context preserved across agent switch");
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
//! bridge_keys_demo —— bridge_keys 过滤 + 子↔子共享 namespace 示例。
|
||||
//!
|
||||
//! 演示:
|
||||
//! 1. 父 session 设置 SessionMemory(key: "project_goal", "constraints", "noise")
|
||||
//! 2. dispatch + bridge_keys = ["project_goal", "constraints"] → 只继承这两个
|
||||
//! 3. 验证子 session 读到的 session_memory 与过滤一致
|
||||
//! 4. 演示子↔子共享 namespace:dispatch 时设 `shared_namespace`,
|
||||
//! 子 A 写入 `shared:{parent_id}:fact_x`,子 B 通过约定 key 读取
|
||||
//!
|
||||
//! 运行:`cargo run --example bridge_keys_demo`
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use agcore::agent::{Agent, AgentBuilder};
|
||||
use agcore::engine::{DispatchConfig, SessionManager};
|
||||
use agcore::llm::hooks::HookExecutor;
|
||||
use agcore::llm::mock::MockProvider;
|
||||
use agcore::llm::types::Usage;
|
||||
use agcore::llm::types::message::{ContentBlock, Message};
|
||||
use agcore::llm::types::response_v2::{MessageResponse, StopReason};
|
||||
use agcore::memory::store::InMemoryStore;
|
||||
use agcore::tools::ToolRegistry;
|
||||
|
||||
struct WorkerAgent;
|
||||
|
||||
impl Agent for WorkerAgent {
|
||||
fn name(&self) -> &str {
|
||||
"worker"
|
||||
}
|
||||
fn system_prompt(&self) -> Option<&str> {
|
||||
Some("You are a worker.")
|
||||
}
|
||||
}
|
||||
|
||||
fn assistant_text(text: &str) -> MessageResponse {
|
||||
MessageResponse {
|
||||
id: String::new(),
|
||||
model: String::new(),
|
||||
message: Message::Assistant {
|
||||
content: vec![ContentBlock::Text { text: text.into() }],
|
||||
},
|
||||
usage: Usage::from_input_output(8, 4),
|
||||
stop_reason: StopReason::Stop,
|
||||
extra: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
println!("=== Bridge Keys Demo ===\n");
|
||||
|
||||
let store: Arc<dyn agcore::memory::store::MemoryStore> = Arc::new(InMemoryStore::new());
|
||||
// 3 个 dispatch 调用需要 3 个 mock response
|
||||
let provider = Arc::new(MockProvider::new(vec![
|
||||
assistant_text("Worker 1: done"),
|
||||
assistant_text("Worker 2: done"),
|
||||
assistant_text("Worker 3: done"),
|
||||
]));
|
||||
let bundle = Arc::new(
|
||||
AgentBuilder::new()
|
||||
.provider(provider)
|
||||
.tool_registry(Arc::new(ToolRegistry::new()))
|
||||
.hook_executor(Arc::new(HookExecutor::new()))
|
||||
.session_memory_backend(store.clone())
|
||||
.build()
|
||||
.expect("RuntimeBundle"),
|
||||
);
|
||||
|
||||
let worker: Arc<dyn Agent> = Arc::new(WorkerAgent);
|
||||
|
||||
let sm = Arc::new(SessionManager::new(store));
|
||||
let parent_id = sm
|
||||
.create(worker.clone(), bundle.clone())
|
||||
.await
|
||||
.expect("create");
|
||||
println!("[1] parent session created: {parent_id}");
|
||||
|
||||
// 父 session_memory 写入 3 个 key
|
||||
{
|
||||
let session = sm.get(&parent_id).await.unwrap();
|
||||
let mut guard = session.lock().await;
|
||||
guard
|
||||
.set_session_data("project_goal", "Build a fast compiler")
|
||||
.await
|
||||
.unwrap();
|
||||
guard
|
||||
.set_session_data("constraints", "Rust, no unsafe")
|
||||
.await
|
||||
.unwrap();
|
||||
guard
|
||||
.set_session_data("noise", "should NOT be inherited")
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
println!("[2] parent set 3 keys: project_goal, constraints, noise");
|
||||
|
||||
// dispatch + bridge_keys 过滤
|
||||
let config = DispatchConfig {
|
||||
bridge_keys: Some(vec!["project_goal".to_string(), "constraints".to_string()]),
|
||||
..Default::default()
|
||||
};
|
||||
let result = sm
|
||||
.dispatch(&parent_id, worker.clone(), "do work", config)
|
||||
.await
|
||||
.expect("dispatch");
|
||||
println!("[3] dispatched sub-agent (child_id={})\n", &result.child_id[..20]);
|
||||
|
||||
// 验证过滤效果
|
||||
let child_session = sm.get(&result.child_id).await.unwrap();
|
||||
let child_guard = child_session.lock().await;
|
||||
let inherited_goal = child_guard.session_memory().get("project_goal").await.unwrap();
|
||||
let inherited_constraint = child_guard.session_memory().get("constraints").await.unwrap();
|
||||
let filtered_noise = child_guard.session_memory().get("noise").await.unwrap();
|
||||
drop(child_guard);
|
||||
|
||||
println!("[verify] inherited keys in child session:");
|
||||
println!(" - project_goal: {:?}", inherited_goal);
|
||||
println!(" - constraints: {:?}", inherited_constraint);
|
||||
println!(" - noise: {:?} (should be None)", filtered_noise);
|
||||
|
||||
assert_eq!(inherited_goal, Some("Build a fast compiler".to_string()));
|
||||
assert_eq!(inherited_constraint, Some("Rust, no unsafe".to_string()));
|
||||
assert_eq!(filtered_noise, None, "noise should be filtered out");
|
||||
|
||||
println!("\n✓ bridge_keys filtering works correctly");
|
||||
|
||||
// =============== 第二部分:子↔子共享 namespace(convention)===============
|
||||
println!("\n=== Part 2: Child↔Child Shared Namespace (convention) ===\n");
|
||||
|
||||
// 关键点:`SessionMemory::get`/`set` 通过 session 自身 namespace 隔离
|
||||
// (每个 session 一个独立 namespace),所以"子↔子共享"不能直接通过 SessionMemory。
|
||||
// 真正的子↔子共享需要直接操作底层 MemoryStore,或由上层应用维护一个
|
||||
// 跨 session 的"共享通道"(例如独立的 namespace + 所有子 session 知道 key 前缀)。
|
||||
//
|
||||
// 本 demo 演示通过 DispatchConfig.shared_namespace(convention-based):
|
||||
// - `shared_namespace: Some(prefix)` 作为约定标记,告知子 agent
|
||||
// "你的数据共享 namespace 是 shared:{prefix}:*"
|
||||
// - 子 agent 自行通过 `sm.store()` 直接操作 MemoryStore(绕过 SessionMemory 的 namespace 隔离)
|
||||
//
|
||||
// 演示 2 个子 agent 通过约定 namespace prefix 共享数据。
|
||||
|
||||
let shared_ns_config = DispatchConfig {
|
||||
bridge_keys: Some(vec![]),
|
||||
shared_namespace: Some("parent-123".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// dispatch 第一个子 agent
|
||||
let _researcher_result = sm
|
||||
.dispatch(
|
||||
&parent_id,
|
||||
worker.clone(),
|
||||
"research task",
|
||||
shared_ns_config.clone(),
|
||||
)
|
||||
.await
|
||||
.expect("dispatch researcher");
|
||||
|
||||
// 子 A 通过 `sm.store()` 直接写入共享 namespace key
|
||||
// (约定 prefix: "shared:parent-123:")
|
||||
let shared_key = "shared:parent-123:fact_architecture";
|
||||
sm.store()
|
||||
.save(agcore::memory::types::MemoryItem {
|
||||
id: shared_key.to_string(),
|
||||
content: "Microservices with event sourcing".to_string(),
|
||||
metadata: serde_json::json!({}),
|
||||
created_at: time::OffsetDateTime::now_utc(),
|
||||
})
|
||||
.await
|
||||
.expect("save shared fact");
|
||||
println!("[4] researcher wrote {shared_key}");
|
||||
|
||||
// dispatch 第二个子 agent
|
||||
let _writer_result = sm
|
||||
.dispatch(
|
||||
&parent_id,
|
||||
worker.clone(),
|
||||
"writing task",
|
||||
shared_ns_config,
|
||||
)
|
||||
.await
|
||||
.expect("dispatch writer");
|
||||
|
||||
// 子 B 通过 `sm.store()` 直接读取共享 namespace key
|
||||
let read_item = sm.store().get(shared_key).await.expect("get");
|
||||
let read_fact = read_item.map(|i| i.content);
|
||||
println!("[5] writer reads {shared_key} = {read_fact:?}");
|
||||
|
||||
assert_eq!(
|
||||
read_fact,
|
||||
Some("Microservices with event sourcing".to_string()),
|
||||
"writer should read researcher's shared fact"
|
||||
);
|
||||
|
||||
println!("\n✓ child↔child shared namespace works correctly (via MemoryStore convention)");
|
||||
println!("\n=== All Bridge Keys Demo checks passed ===");
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
//! dispatch_stream_demo —— 流式子代理调度示例。
|
||||
//!
|
||||
//! 演示:
|
||||
//! 1. 创建父 session
|
||||
//! 2. dispatch_stream 单个子 agent
|
||||
//! 3. 消费 SubTaskStreamEvent 序列
|
||||
//! 4. 验证事件序列:ChildCreated → Stream(...) × N → Completed
|
||||
//! 5. 验证:完成时 turn_index 已递增(finalize 副作用)
|
||||
//!
|
||||
//! 运行:`cargo run --example dispatch_stream_demo`
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use agcore::agent::{Agent, AgentBuilder};
|
||||
use agcore::engine::{SessionManager, SubTaskStreamEvent};
|
||||
use agcore::llm::hooks::HookExecutor;
|
||||
use agcore::llm::mock::MockProvider;
|
||||
use agcore::llm::types::Usage;
|
||||
use agcore::llm::types::message::{ContentBlock, Message};
|
||||
use agcore::llm::types::response_v2::{MessageResponse, StopReason};
|
||||
use agcore::memory::store::InMemoryStore;
|
||||
use agcore::tools::ToolRegistry;
|
||||
use futures_util::StreamExt;
|
||||
|
||||
struct StreamWorkerAgent;
|
||||
|
||||
impl Agent for StreamWorkerAgent {
|
||||
fn name(&self) -> &str {
|
||||
"stream_worker"
|
||||
}
|
||||
fn system_prompt(&self) -> Option<&str> {
|
||||
Some("You are a streaming worker.")
|
||||
}
|
||||
}
|
||||
|
||||
fn assistant_text(text: &str) -> MessageResponse {
|
||||
MessageResponse {
|
||||
id: String::new(),
|
||||
model: String::new(),
|
||||
message: Message::Assistant {
|
||||
content: vec![ContentBlock::Text { text: text.into() }],
|
||||
},
|
||||
usage: Usage::from_input_output(8, 4),
|
||||
stop_reason: StopReason::Stop,
|
||||
extra: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
println!("=== Dispatch Stream Demo ===\n");
|
||||
|
||||
let store: Arc<dyn agcore::memory::store::MemoryStore> = Arc::new(InMemoryStore::new());
|
||||
let provider = Arc::new(MockProvider::new(vec![assistant_text("streamed response")]));
|
||||
let bundle = Arc::new(
|
||||
AgentBuilder::new()
|
||||
.provider(provider)
|
||||
.tool_registry(Arc::new(ToolRegistry::new()))
|
||||
.hook_executor(Arc::new(HookExecutor::new()))
|
||||
.session_memory_backend(store.clone())
|
||||
.build()
|
||||
.expect("RuntimeBundle"),
|
||||
);
|
||||
|
||||
let worker: Arc<dyn Agent> = Arc::new(StreamWorkerAgent);
|
||||
|
||||
let sm = Arc::new(SessionManager::new(store));
|
||||
let parent_id = sm
|
||||
.create(worker.clone(), bundle.clone())
|
||||
.await
|
||||
.expect("create");
|
||||
println!("[1] parent session: {parent_id}");
|
||||
|
||||
// dispatch_stream
|
||||
let mut stream = sm
|
||||
.dispatch_stream(&parent_id, worker, "do streaming work", Default::default())
|
||||
.await
|
||||
.expect("dispatch_stream");
|
||||
|
||||
println!("[2] consuming SubTaskStreamEvent sequence...\n");
|
||||
let mut saw_child_created = false;
|
||||
let mut saw_stream_count = 0;
|
||||
let mut completed = None;
|
||||
|
||||
while let Some(event) = stream.next().await {
|
||||
match event {
|
||||
SubTaskStreamEvent::ChildCreated { child_id } => {
|
||||
println!(" → ChildCreated({})", &child_id[..20]);
|
||||
saw_child_created = true;
|
||||
}
|
||||
SubTaskStreamEvent::Stream(_) => {
|
||||
saw_stream_count += 1;
|
||||
}
|
||||
SubTaskStreamEvent::Completed(r) => {
|
||||
println!(" → Completed(child_id={}, {} tokens)", &r.child_id[..20], r.usage.total().total_tokens);
|
||||
completed = Some(r);
|
||||
break;
|
||||
}
|
||||
SubTaskStreamEvent::Error { child_id, error } => {
|
||||
panic!("unexpected error: child_id={child_id}, error={error}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let result = completed.expect("Completed should arrive");
|
||||
|
||||
// 验证事件序列
|
||||
assert!(saw_child_created, "ChildCreated should be received");
|
||||
assert!(saw_stream_count > 0, "at least one Stream event");
|
||||
println!("\n[3] received {} stream events", saw_stream_count);
|
||||
|
||||
// 验证 finalize 已发生(turn_index 递增)
|
||||
let child_session = sm.get(&result.child_id).await.unwrap();
|
||||
let child_guard = child_session.lock().await;
|
||||
let child_turn_index = child_guard.turn_index();
|
||||
drop(child_guard);
|
||||
assert_eq!(child_turn_index, 1, "turn_index should increment after finalize");
|
||||
println!("[4] child session turn_index = {child_turn_index} (finalize works)");
|
||||
|
||||
println!("\n✓ dispatch_stream completed successfully");
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
//! sub_agent_dispatch_demo —— SubAgent 并行派发示例。
|
||||
//!
|
||||
//! 演示:
|
||||
//! 1. 创建父 session("主编" agent)
|
||||
//! 2. 并行 dispatch_all 3 个子 agent(研究员 / 写手 / 审校)
|
||||
//! 3. 收集子任务结果
|
||||
//! 4. 验证树形结构:children(parent_id) 应返回 3 个子 ID
|
||||
//!
|
||||
//! 运行:`cargo run --example sub_agent_dispatch_demo`
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use agcore::agent::{Agent, AgentBuilder};
|
||||
use agcore::engine::{SessionManager, SubTaskResult};
|
||||
use agcore::llm::hooks::HookExecutor;
|
||||
use agcore::llm::mock::MockProvider;
|
||||
use agcore::llm::types::Usage;
|
||||
use agcore::llm::types::message::{ContentBlock, Message};
|
||||
use agcore::llm::types::response_v2::{MessageResponse, StopReason};
|
||||
use agcore::memory::store::InMemoryStore;
|
||||
use agcore::tools::ToolRegistry;
|
||||
|
||||
struct EditorAgent;
|
||||
struct ResearcherAgent;
|
||||
struct WriterAgent;
|
||||
struct ReviewerAgent;
|
||||
|
||||
impl Agent for EditorAgent {
|
||||
fn name(&self) -> &str {
|
||||
"editor"
|
||||
}
|
||||
fn system_prompt(&self) -> Option<&str> {
|
||||
Some("You are an editor coordinating a team.")
|
||||
}
|
||||
}
|
||||
impl Agent for ResearcherAgent {
|
||||
fn name(&self) -> &str {
|
||||
"researcher"
|
||||
}
|
||||
fn system_prompt(&self) -> Option<&str> {
|
||||
Some("You are a researcher. Provide 3 key findings.")
|
||||
}
|
||||
}
|
||||
impl Agent for WriterAgent {
|
||||
fn name(&self) -> &str {
|
||||
"writer"
|
||||
}
|
||||
fn system_prompt(&self) -> Option<&str> {
|
||||
Some("You are a writer. Draft a section.")
|
||||
}
|
||||
}
|
||||
impl Agent for ReviewerAgent {
|
||||
fn name(&self) -> &str {
|
||||
"reviewer"
|
||||
}
|
||||
fn system_prompt(&self) -> Option<&str> {
|
||||
Some("You are a reviewer. Check for accuracy.")
|
||||
}
|
||||
}
|
||||
|
||||
fn assistant_text(text: &str) -> MessageResponse {
|
||||
MessageResponse {
|
||||
id: String::new(),
|
||||
model: String::new(),
|
||||
message: Message::Assistant {
|
||||
content: vec![ContentBlock::Text { text: text.into() }],
|
||||
},
|
||||
usage: Usage::from_input_output(8, 4),
|
||||
stop_reason: StopReason::Stop,
|
||||
extra: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn print_result(name: &str, r: &Result<SubTaskResult, agcore::engine::EngineError>) {
|
||||
match r {
|
||||
Ok(res) => println!(
|
||||
" ✓ {name} (child_id={}): {} tokens",
|
||||
&res.child_id[..20.min(res.child_id.len())],
|
||||
res.usage.total().total_tokens,
|
||||
),
|
||||
Err(e) => println!(" ✗ {name}: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
println!("=== SubAgent Dispatch Demo ===\n");
|
||||
|
||||
let store: Arc<dyn agcore::memory::store::MemoryStore> = Arc::new(InMemoryStore::new());
|
||||
let provider = Arc::new(MockProvider::new(vec![
|
||||
assistant_text("Researcher: finding 1, 2, 3"),
|
||||
assistant_text("Writer: section drafted"),
|
||||
assistant_text("Reviewer: looks good"),
|
||||
]));
|
||||
let bundle = Arc::new(
|
||||
AgentBuilder::new()
|
||||
.provider(provider)
|
||||
.tool_registry(Arc::new(ToolRegistry::new()))
|
||||
.hook_executor(Arc::new(HookExecutor::new()))
|
||||
.session_memory_backend(store.clone())
|
||||
.build()
|
||||
.expect("RuntimeBundle"),
|
||||
);
|
||||
|
||||
let editor: Arc<dyn Agent> = Arc::new(EditorAgent);
|
||||
let researcher: Arc<dyn Agent> = Arc::new(ResearcherAgent);
|
||||
let writer: Arc<dyn Agent> = Arc::new(WriterAgent);
|
||||
let reviewer: Arc<dyn Agent> = Arc::new(ReviewerAgent);
|
||||
|
||||
let sm = Arc::new(SessionManager::new(store));
|
||||
let parent_id = sm.create(editor, bundle.clone()).await.expect("create");
|
||||
println!("[1] parent session created: {parent_id}");
|
||||
|
||||
// dispatch_all 3 个子 agent
|
||||
println!("[2] dispatching 3 sub-agents in parallel...\n");
|
||||
let results = sm
|
||||
.dispatch_all(
|
||||
&parent_id,
|
||||
vec![
|
||||
(researcher, "Research topic X".to_string()),
|
||||
(writer, "Draft intro section".to_string()),
|
||||
(reviewer, "Review draft".to_string()),
|
||||
],
|
||||
agcore::engine::DispatchConfig::default(),
|
||||
)
|
||||
.await;
|
||||
|
||||
print_result("researcher", &results[0]);
|
||||
print_result("writer", &results[1]);
|
||||
print_result("reviewer", &results[2]);
|
||||
|
||||
let success_count = results.iter().filter(|r| r.is_ok()).count();
|
||||
assert_eq!(success_count, 3, "all 3 should succeed");
|
||||
|
||||
// 验证树形
|
||||
let children = sm.children(&parent_id).await.expect("children");
|
||||
println!("\n[3] children(parent) = {} session(s)", children.len());
|
||||
assert_eq!(children.len(), 3);
|
||||
|
||||
println!("\n✓ dispatch_all completed: 3/3 sub-agents succeeded");
|
||||
}
|
||||
Reference in New Issue
Block a user