5baa170508
- README 添加 feature 组合表 + 模块级 features 清单 + 升级指南 - 18 个 example 顶部添加 Required features 注释 - roadmap.md 和 roadmap-v0.3.2.md 同步 Phase 26-27 完成状态 - cargo fmt 全量格式化(修复预存格式问题,CI format job 可通过)
205 lines
7.3 KiB
Rust
205 lines
7.3 KiB
Rust
//! bridge_keys_demo —— bridge_keys 过滤 + 子↔子共享 namespace 示例。
|
||
//! Required features: cargo run --example bridge_keys_demo --features "engine"
|
||
//!
|
||
//! 演示:
|
||
//! 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 ===");
|
||
}
|