5baa170508
- README 添加 feature 组合表 + 模块级 features 清单 + 升级指南 - 18 个 example 顶部添加 Required features 注释 - roadmap.md 和 roadmap-v0.3.2.md 同步 Phase 26-27 完成状态 - cargo fmt 全量格式化(修复预存格式问题,CI format job 可通过)
171 lines
6.3 KiB
Rust
171 lines
6.3 KiB
Rust
//! 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<dyn LlmProvider> = 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
|
||
}
|