Files
agcore/examples/agent_switch_demo.rs
T
徐涛 46de111965 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。
2026-07-15 11:15:58 +08:00

116 lines
3.8 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 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");
}