//! context_slot_demo —— 多上下文槽位管理示例。 //! Required features: cargo run --example context_slot_demo --features "agent" //! //! 场景:法律咨询入口 → 派生两个独立探索方向 → 切换 → 隔离验证 → 删除。 //! //! 展示: //! - 默认 slot 自动创建 //! - 多 slot 间的消息隔离 //! - 派生 slot 从父 slot 复制消息 //! - 删除非 default slot 后自动回退到 default //! //! 运行:`cargo run --example context_slot_demo`(离线,零配置) use std::sync::Arc; use agcore::agent::{Agent, AgentBuilder, AgentSession}; use agcore::llm::LlmProvider; 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::tools::ToolRegistry; struct LegalAdvisor; impl Agent for LegalAdvisor { fn name(&self) -> &str { "legal-advisor" } fn system_prompt(&self) -> Option<&str> { Some("你是法律顾问。请用一句话回答用户问题。") } } /// 构造一个简单的 Assistant 响应(用于 MockProvider)。 fn assistant_resp(text: &str) -> MessageResponse { MessageResponse { id: String::new(), model: "mock".into(), message: Message::Assistant { content: vec![ContentBlock::Text { text: text.into() }], }, usage: Usage::from_input_output(5, 5), stop_reason: StopReason::Stop, extra: Default::default(), } } #[tokio::main] async fn main() { // 1. 构造 session(自动包含 default slot) let provider: Arc = Arc::new(MockProvider::new(vec![ assistant_resp("您好,我可以帮您处理法律问题。"), assistant_resp("管辖权问题:建议选择合同签订地法院。"), assistant_resp("条款修改:建议将上限调整为 80 万。"), assistant_resp("已回到主对话。"), ])); let bundle = Arc::new( AgentBuilder::new() .provider(provider) .tool_registry(Arc::new(ToolRegistry::new())) .hook_executor(Arc::new(HookExecutor::new())) .build() .unwrap(), ); let mut session = AgentSession::new(Arc::new(LegalAdvisor), "legal-001", bundle); println!("=== 1. 默认 slot 自动创建 ==="); assert_eq!(session.current_slot_id(), "default"); let slots: Vec<_> = session.list_slots().collect(); println!("初始 slots: {slots:?}"); assert_eq!(slots.len(), 1); assert!(slots.contains(&&"default".to_string())); println!("\n=== 2. 在 default slot 中提交一轮 ==="); let r1 = session.submit_turn("我需要法律援助").await.unwrap(); println!("default slot response: {}", r1.text()); println!("\n=== 3. 派生两个独立探索方向的 slot ==="); session .derive_slot( "option_jurisdiction", "default", agcore::agent::DeriveStrategy::Full, ) .await .unwrap(); session .derive_slot( "option_amendment", "default", agcore::agent::DeriveStrategy::Full, ) .await .unwrap(); let slots: Vec<_> = session.list_slots().cloned().collect(); println!("派生后 slots: {slots:?}"); assert_eq!(slots.len(), 3); println!("\n=== 4. 切到 option_jurisdiction 并提交 ==="); session.switch_slot("option_jurisdiction").await.unwrap(); assert_eq!(session.current_slot_id(), "option_jurisdiction"); let r2 = session.submit_turn("如果用户质疑管辖权?").await.unwrap(); println!("option_jurisdiction response: {}", r2.text()); println!("\n=== 5. 切到 option_amendment 并提交 ==="); session.switch_slot("option_amendment").await.unwrap(); let r3 = session.submit_turn("用户要求提高赔偿上限?").await.unwrap(); println!("option_amendment response: {}", r3.text()); println!("\n=== 6. 切回 default,验证消息隔离 ==="); session.switch_slot("default").await.unwrap(); let r4 = session.submit_turn("汇总一下我们的讨论").await.unwrap(); println!("default response: {}", r4.text()); // 验证 default slot 不包含 option_jurisdiction 的"管辖权"问题 let (_, default_slot) = session.slots().find(|(id, _)| *id == "default").unwrap(); let default_has_jurisdiction = default_slot .messages .iter() .any(|m| message_contains(m, "管辖权")); assert!( !default_has_jurisdiction, "default slot 不应包含 option_jurisdiction 的消息" ); println!("\n=== 7. 删除 option_amendment,验证回退到 default ==="); session.delete_slot("option_amendment").await.unwrap(); let slots: Vec<_> = session.list_slots().cloned().collect(); println!("删除后 slots: {slots:?}"); assert!(!slots.contains(&"option_amendment".to_string())); assert_eq!(slots.len(), 2); println!("\n=== 8. 切到 option_jurisdiction 并删除,验证 current 回退 ==="); session.switch_slot("option_jurisdiction").await.unwrap(); session.delete_slot("option_jurisdiction").await.unwrap(); assert_eq!(session.current_slot_id(), "default"); let slots: Vec<_> = session.list_slots().cloned().collect(); println!("删除后 slots: {slots:?}"); assert_eq!(slots.len(), 1); assert_eq!(slots[0], "default"); println!("\n=== 9. 验证 delete_slot 保护逻辑 ==="); let err = session.delete_slot("default").await.unwrap_err(); println!("删除 default 返回错误: {err}"); assert!(matches!(err, agcore::agent::AgentError::Config(_))); println!("\n✓ context_slot_demo 完成"); } /// 检查 Message 是否包含指定文本(提取第一个 Text block)。 fn message_contains(msg: &Message, needle: &str) -> bool { use agcore::llm::types::message::ContentBlock; let blocks = match msg { Message::System { content } | Message::User { content } | Message::Assistant { content } => content, Message::UserImage { .. } => return false, Message::ToolResult { content, .. } => content, _ => return false, }; for block in blocks { if let ContentBlock::Text { text } = block && text.contains(needle) { return true; } } false }