docs: 更新 README feature 表 + 升级指南 + 示例注释 + roadmap 同步

- README 添加 feature 组合表 + 模块级 features 清单 + 升级指南
- 18 个 example 顶部添加 Required features 注释
- roadmap.md 和 roadmap-v0.3.2.md 同步 Phase 26-27 完成状态
- cargo fmt 全量格式化(修复预存格式问题,CI format job 可通过)
This commit is contained in:
徐涛
2026-07-19 08:18:04 +08:00
parent bc4eac72e1
commit 5baa170508
53 changed files with 1112 additions and 615 deletions
+1
View File
@@ -1,4 +1,5 @@
//! agent_session_demo —— Agent 装配 + 会话链路 + SessionMemory 桥接。
//! Required features: cargo run --example agent_session_demo --features "agent"
//!
//! 演示:
//! 1. 实现 `Agent` trait(定义角色 + system prompt
+5 -1
View File
@@ -1,4 +1,5 @@
//! agent_switch_demo —— Agent 角色热切换示例。
//! Required features: cargo run --example agent_switch_demo --features "engine"
//!
//! 演示:
//! 1. 创建 session(绑定 Analyst agent
@@ -110,6 +111,9 @@ async fn main() {
};
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");
assert_eq!(
agent_name_owned, "reporter",
"current agent should be reporter"
);
println!("✓ context preserved across agent switch");
}
+16 -9
View File
@@ -1,4 +1,5 @@
//! bridge_keys_demo —— bridge_keys 过滤 + 子↔子共享 namespace 示例。
//! Required features: cargo run --example bridge_keys_demo --features "engine"
//!
//! 演示:
//! 1. 父 session 设置 SessionMemorykey: "project_goal", "constraints", "noise"
@@ -103,13 +104,24 @@ async fn main() {
.dispatch(&parent_id, worker.clone(), "do work", config)
.await
.expect("dispatch");
println!("[3] dispatched sub-agent (child_id={})\n", &result.child_id[..20]);
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 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);
@@ -172,12 +184,7 @@ async fn main() {
// dispatch 第二个子 agent
let _writer_result = sm
.dispatch(
&parent_id,
worker.clone(),
"writing task",
shared_ns_config,
)
.dispatch(&parent_id, worker.clone(), "writing task", shared_ns_config)
.await
.expect("dispatch writer");
+14 -5
View File
@@ -1,4 +1,5 @@
//! context_slot_demo —— 多上下文槽位管理示例。
//! Required features: cargo run --example context_slot_demo --features "agent"
//!
//! 场景:法律咨询入口 → 派生两个独立探索方向 → 切换 → 隔离验证 → 删除。
//!
@@ -13,12 +14,12 @@
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::LlmProvider;
use agcore::llm::types::Usage;
use agcore::llm::types::message::{ContentBlock, Message};
use agcore::llm::types::response_v2::{MessageResponse, StopReason};
use agcore::llm::types::Usage;
use agcore::tools::ToolRegistry;
struct LegalAdvisor;
@@ -78,11 +79,19 @@ async fn main() {
println!("\n=== 3. 派生两个独立探索方向的 slot ===");
session
.derive_slot("option_jurisdiction", "default", agcore::agent::DeriveStrategy::Full)
.derive_slot(
"option_jurisdiction",
"default",
agcore::agent::DeriveStrategy::Full,
)
.await
.unwrap();
session
.derive_slot("option_amendment", "default", agcore::agent::DeriveStrategy::Full)
.derive_slot(
"option_amendment",
"default",
agcore::agent::DeriveStrategy::Full,
)
.await
.unwrap();
let slots: Vec<_> = session.list_slots().cloned().collect();
@@ -158,4 +167,4 @@ fn message_contains(msg: &Message, needle: &str) -> bool {
}
}
false
}
}
+1
View File
@@ -1,4 +1,5 @@
//! conversation_memory_demo —— 对话记忆滑动窗口与隔离。
//! Required features: cargo run --example conversation_memory_demo --features "memory"
//!
//! 演示:
//! 1. `ConversationMemoryConfig` 构造(SlidingWindow / Full 策略)
+1
View File
@@ -1,4 +1,5 @@
//! custom_tool —— 自定义工具注册、单次 / 并行调用、权限检查。
//! Required features: cargo run --example custom_tool --features "tools,llm"
//!
//! 演示:
//! 1. 实现 `BaseTool` traitWeatherTool + DeleteFileTool
+10 -2
View File
@@ -1,4 +1,5 @@
//! dispatch_stream_demo —— 流式子代理调度示例。
//! Required features: cargo run --example dispatch_stream_demo --features "engine"
//!
//! 演示:
//! 1. 创建父 session
@@ -92,7 +93,11 @@ async fn main() {
saw_stream_count += 1;
}
SubTaskStreamEvent::Completed(r) => {
println!(" → Completed(child_id={}, {} tokens)", &r.child_id[..20], r.usage.total().total_tokens);
println!(
" → Completed(child_id={}, {} tokens)",
&r.child_id[..20],
r.usage.total().total_tokens
);
completed = Some(r);
break;
}
@@ -114,7 +119,10 @@ async fn main() {
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");
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");
+3 -6
View File
@@ -1,4 +1,5 @@
//! document_demo —— Document + RecursiveCharacterSplitter + MockEmbedding + RagPipeline 完整衔接示例。
//! Required features: cargo run --example document_demo --features "memory,tracing-init"
//!
//! 演示 RAG 管线:
//! 1. 创建多段落 Document
@@ -37,11 +38,7 @@ async fn main() {
let embedder: Arc<dyn Embedding> = Arc::new(MockEmbedding::new(4));
let store: Arc<dyn VectorStore> = Arc::new(InMemoryVectorStore::new());
let splitter = RecursiveCharacterSplitter::new(200, 30);
let pipeline = RagPipeline::new(
Arc::clone(&embedder),
Arc::clone(&store),
Some(splitter),
);
let pipeline = RagPipeline::new(Arc::clone(&embedder), Arc::clone(&store), Some(splitter));
// 3. 一次性 ingest:自动 split → embed → add
pipeline.ingest(std::slice::from_ref(&doc)).await.unwrap();
@@ -67,4 +64,4 @@ async fn main() {
);
println!("\n✓ document_demo 完成");
}
}
+203 -65
View File
@@ -1,4 +1,5 @@
//! end_to_end —— 3 工具 + 3 轮对话 + SqliteStore 持久化跨连接验证。
//! Required features: cargo run --example end_to_end --features "agent,memory-sqlite,provider-openai"
//!
//! 运行:`cargo run --example end_to_end`(离线,零配置)
//!
@@ -16,11 +17,15 @@ use std::env;
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::LlmProvider;
use agcore::llm::provider::{create_provider, ProviderConfig, ProviderType};
use agcore::llm::types::{Usage, message::{ContentBlock, Message}, response_v2::{MessageResponse, StopReason}};
use agcore::llm::provider::{ProviderConfig, ProviderType, create_provider};
use agcore::llm::types::{
Usage,
message::{ContentBlock, Message},
response_v2::{MessageResponse, StopReason},
};
use agcore::memory::store::{MemoryStore, SqliteStore};
use agcore::memory::types::{MemoryFilter, MemoryItem};
use agcore::tools::{BaseTool, ToolContext, ToolError, ToolRegistry};
@@ -33,8 +38,12 @@ use time::OffsetDateTime;
struct AssistantAgent;
impl Agent for AssistantAgent {
fn name(&self) -> &str { "end-to-end assistant" }
fn system_prompt(&self) -> Option<&str> { Some("简洁助手,必要时调用工具完成任务。") }
fn name(&self) -> &str {
"end-to-end assistant"
}
fn system_prompt(&self) -> Option<&str> {
Some("简洁助手,必要时调用工具完成任务。")
}
}
// === Tools ===
@@ -42,14 +51,19 @@ impl Agent for AssistantAgent {
struct EchoTool;
#[async_trait]
impl BaseTool for EchoTool {
fn name(&self) -> &str { "echo" }
fn description(&self) -> &str { "回显输入文本" }
fn name(&self) -> &str {
"echo"
}
fn description(&self) -> &str {
"回显输入文本"
}
fn parameters(&self) -> Value {
json!({"type":"object","properties":{"text":{"type":"string"}},"required":["text"]})
}
async fn execute(&self, args: Value, _: &ToolContext<'_>) -> Result<Value, ToolError> {
let text = args.get("text").and_then(|v| v.as_str())
.ok_or_else(|| ToolError::InvalidArguments("text".into(), "需要 string 类型的 text 参数".into()))?;
let text = args.get("text").and_then(|v| v.as_str()).ok_or_else(|| {
ToolError::InvalidArguments("text".into(), "需要 string 类型的 text 参数".into())
})?;
Ok(json!({"echoed": format!("收到: {text}")}))
}
}
@@ -58,8 +72,12 @@ impl BaseTool for EchoTool {
struct CalcTool;
#[async_trait]
impl BaseTool for CalcTool {
fn name(&self) -> &str { "calc" }
fn description(&self) -> &str { "四则运算:'a op b' 格式,op ∈ {+, -, *, /}" }
fn name(&self) -> &str {
"calc"
}
fn description(&self) -> &str {
"四则运算:'a op b' 格式,op ∈ {+, -, *, /}"
}
fn parameters(&self) -> Value {
json!({"type":"object","properties":{"expr":{"type":"string"}},"required":["expr"]})
}
@@ -67,18 +85,30 @@ impl BaseTool for CalcTool {
let expr = args["expr"].as_str().unwrap_or("");
let parts: Vec<&str> = expr.split_whitespace().collect();
if parts.len() != 3 {
return Err(ToolError::InvalidArguments("expr".into(), "需要 'a op b' 三段式".into()));
return Err(ToolError::InvalidArguments(
"expr".into(),
"需要 'a op b' 三段式".into(),
));
}
let a: i64 = parts[0].parse().map_err(|_| ToolError::InvalidArguments("expr".into(), format!("无法解析 '{}'", parts[0])))?;
let b: i64 = parts[2].parse().map_err(|_| ToolError::InvalidArguments("expr".into(), format!("无法解析 '{}'", parts[2])))?;
let a: i64 = parts[0].parse().map_err(|_| {
ToolError::InvalidArguments("expr".into(), format!("无法解析 '{}'", parts[0]))
})?;
let b: i64 = parts[2].parse().map_err(|_| {
ToolError::InvalidArguments("expr".into(), format!("无法解析 '{}'", parts[2]))
})?;
let result = match parts[1] {
"+" => a + b,
"-" => a - b,
"*" => a * b,
"/" => a.checked_div(b).ok_or_else(|| {
ToolError::InvalidArguments("expr".into(), "除数不能为 0".into())
})?,
op => return Err(ToolError::InvalidArguments("expr".into(), format!("不支持的运算符: {op}"))),
"/" => a
.checked_div(b)
.ok_or_else(|| ToolError::InvalidArguments("expr".into(), "除数不能为 0".into()))?,
op => {
return Err(ToolError::InvalidArguments(
"expr".into(),
format!("不支持的运算符: {op}"),
));
}
};
Ok(json!({"result": result}))
}
@@ -87,13 +117,21 @@ impl BaseTool for CalcTool {
/// 通过 MemoryStore trait 读写笔记:直接持有 Arc<dyn MemoryStore>
/// 绕开 AgentSession 封装(NoteTool 在 tool.execute 中直接操作 store)。
/// 关键前缀 "note:" 用于 list 过滤。
struct NoteTool { store: Arc<dyn MemoryStore> }
impl NoteTool { const PREFIX: &'static str = "note:"; }
struct NoteTool {
store: Arc<dyn MemoryStore>,
}
impl NoteTool {
const PREFIX: &'static str = "note:";
}
#[async_trait]
impl BaseTool for NoteTool {
fn name(&self) -> &str { "note" }
fn description(&self) -> &str { "笔记 save/query: save(key, content) / query()" }
fn name(&self) -> &str {
"note"
}
fn description(&self) -> &str {
"笔记 save/query: save(key, content) / query()"
}
fn parameters(&self) -> Value {
json!({
"type":"object",
@@ -117,18 +155,29 @@ impl BaseTool for NoteTool {
metadata: json!({}),
created_at: OffsetDateTime::now_utc(),
};
self.store.save(item).await
self.store
.save(item)
.await
.map_err(|e| ToolError::ExecutionFailed("note".into(), e.to_string()))?;
Ok(json!({"saved": key}))
}
"query" => {
let filter = MemoryFilter { prefix: Some(Self::PREFIX.into()), ..Default::default() };
let items = self.store.list(&filter).await
let filter = MemoryFilter {
prefix: Some(Self::PREFIX.into()),
..Default::default()
};
let items = self
.store
.list(&filter)
.await
.map_err(|e| ToolError::ExecutionFailed("note".into(), e.to_string()))?;
let notes: Vec<String> = items.into_iter().map(|i| i.content).collect();
Ok(json!({"notes": notes}))
}
_ => Err(ToolError::InvalidArguments("action".into(), format!("未知 action: {action}"))),
_ => Err(ToolError::InvalidArguments(
"action".into(),
format!("未知 action: {action}"),
)),
}
}
}
@@ -136,31 +185,91 @@ impl BaseTool for NoteTool {
// === Mock response helper ===
fn resp(content: Vec<ContentBlock>, stop: StopReason, u: (u32, u32)) -> MessageResponse {
MessageResponse { id: String::new(), model: "mock".into(),
MessageResponse {
id: String::new(),
model: "mock".into(),
message: Message::Assistant { content },
usage: Usage::from_input_output(u.0, u.1),
stop_reason: stop, extra: Default::default() }
stop_reason: stop,
extra: Default::default(),
}
}
fn mock_responses() -> Vec<MessageResponse> {
vec![
// 第 1 轮:calc(25 * 4) → tool_result(100) → 文本回答
resp(vec![ContentBlock::ToolUse { id: "t1".into(), name: "calc".into(),
input: json!({"expr": "25 * 4"}) }], StopReason::ToolUse, (5, 8)),
resp(vec![ContentBlock::Text { text: "25 * 4 = 100".into() }], StopReason::Stop, (8, 12)),
resp(
vec![ContentBlock::ToolUse {
id: "t1".into(),
name: "calc".into(),
input: json!({"expr": "25 * 4"}),
}],
StopReason::ToolUse,
(5, 8),
),
resp(
vec![ContentBlock::Text {
text: "25 * 4 = 100".into(),
}],
StopReason::Stop,
(8, 12),
),
// 第 2 轮:note(save, last_calc, "100") → tool_result(saved) → 文本回答
resp(vec![ContentBlock::ToolUse { id: "t2".into(), name: "note".into(),
input: json!({"action": "save", "key": "last_calc", "content": "100"}) }],
StopReason::ToolUse, (10, 14)),
resp(vec![ContentBlock::Text { text: "已记录:last_calc = 100".into() }], StopReason::Stop, (12, 16)),
resp(
vec![ContentBlock::ToolUse {
id: "t2".into(),
name: "note".into(),
input: json!({"action": "save", "key": "last_calc", "content": "100"}),
}],
StopReason::ToolUse,
(10, 14),
),
resp(
vec![ContentBlock::Text {
text: "已记录:last_calc = 100".into(),
}],
StopReason::Stop,
(12, 16),
),
// 第 3 轮:note(query) → tool_result([100]) → 文本回答
resp(vec![ContentBlock::ToolUse { id: "t3".into(), name: "note".into(),
input: json!({"action": "query"}) }], StopReason::ToolUse, (8, 8)),
resp(vec![ContentBlock::Text { text: "您刚才的计算结果是 100".into() }], StopReason::Stop, (10, 14)),
resp(
vec![ContentBlock::ToolUse {
id: "t3".into(),
name: "note".into(),
input: json!({"action": "query"}),
}],
StopReason::ToolUse,
(8, 8),
),
resp(
vec![ContentBlock::Text {
text: "您刚才的计算结果是 100".into(),
}],
StopReason::Stop,
(10, 14),
),
// 后续冗余响应(防止队列耗尽报错)
resp(vec![ContentBlock::Text { text: "done".into() }], StopReason::Stop, (1, 1)),
resp(vec![ContentBlock::Text { text: "done".into() }], StopReason::Stop, (1, 1)),
resp(vec![ContentBlock::Text { text: "done".into() }], StopReason::Stop, (1, 1)),
resp(
vec![ContentBlock::Text {
text: "done".into(),
}],
StopReason::Stop,
(1, 1),
),
resp(
vec![ContentBlock::Text {
text: "done".into(),
}],
StopReason::Stop,
(1, 1),
),
resp(
vec![ContentBlock::Text {
text: "done".into(),
}],
StopReason::Stop,
(1, 1),
),
]
}
@@ -169,14 +278,21 @@ fn mock_responses() -> Vec<MessageResponse> {
fn select_provider() -> Arc<dyn LlmProvider> {
if env::var("AG_LLM_BASE_URL").is_ok() && env::var("AG_LLM_API_KEY").is_ok() {
let cfg = ProviderConfig::from_env("AG_LLM").expect("AG_LLM_* 环境变量解析失败");
let provider_type = env::var("AG_LLM_PROVIDER").ok()
let provider_type = env::var("AG_LLM_PROVIDER")
.ok()
.and_then(|s| s.parse::<ProviderType>().ok())
.unwrap_or(ProviderType::OpenaiChat);
Arc::from(create_provider(provider_type, cfg).expect("Provider 创建失败"))
} else {
let found: Vec<&str> = ["AG_LLM_BASE_URL", "AG_LLM_API_KEY", "AG_LLM_MODEL"]
.iter().filter(|k| env::var(k).is_ok()).copied().collect();
eprintln!("AG_LLM_* 环境变量不完整(检测到: {:?}),回退到 MockProvider", found);
.iter()
.filter(|k| env::var(k).is_ok())
.copied()
.collect();
eprintln!(
"AG_LLM_* 环境变量不完整(检测到: {:?}),回退到 MockProvider",
found
);
Arc::new(MockProvider::new(mock_responses()))
}
}
@@ -191,52 +307,74 @@ async fn main() {
let backend: Arc<dyn MemoryStore> =
Arc::new(SqliteStore::open(&db_path).expect("SqliteStore 打开失败"));
println!("💾 SqliteStore: {}", db_path.display());
let provider_label = if env::var("AG_LLM_BASE_URL").is_ok() && env::var("AG_LLM_API_KEY").is_ok() {
"真实 LLM Provider"
} else {
"MockProvider (离线回退模式)"
};
let provider_label =
if env::var("AG_LLM_BASE_URL").is_ok() && env::var("AG_LLM_API_KEY").is_ok() {
"真实 LLM Provider"
} else {
"MockProvider (离线回退模式)"
};
println!("🔄 Provider: {provider_label}");
let mut registry = ToolRegistry::new();
registry.register(Arc::new(EchoTool)).unwrap();
registry.register(Arc::new(CalcTool)).unwrap();
registry.register(Arc::new(NoteTool { store: backend.clone() })).unwrap();
registry
.register(Arc::new(NoteTool {
store: backend.clone(),
}))
.unwrap();
println!("🔧 注册工具: {:?}", registry.list_tools());
let bundle = Arc::new(AgentBuilder::new()
.provider(select_provider())
.tool_registry(Arc::new(registry))
.hook_executor(Arc::new(HookExecutor::new()))
.build().expect("RuntimeBundle 装配失败"));
let bundle = Arc::new(
AgentBuilder::new()
.provider(select_provider())
.tool_registry(Arc::new(registry))
.hook_executor(Arc::new(HookExecutor::new()))
.build()
.expect("RuntimeBundle 装配失败"),
);
let mut session = AgentSession::new(Arc::new(AssistantAgent), "e2e-1", bundle.clone());
println!("\n第 1 轮 用户: 帮我算 25 * 4");
let r1 = session.submit_turn("帮我算 25 * 4").await.expect("turn 1 失败");
let r1 = session
.submit_turn("帮我算 25 * 4")
.await
.expect("turn 1 失败");
println!(" → 回答: {}", r1.text());
println!("\n第 2 轮 用户: 记下来:结果是 100");
let r2 = session.submit_turn("记下来:结果是 100").await.expect("turn 2 失败");
let r2 = session
.submit_turn("记下来:结果是 100")
.await
.expect("turn 2 失败");
println!(" → 回答: {}", r2.text());
println!("\n第 3 轮 用户: 我刚才算了什么?");
let r3 = session.submit_turn("我刚才算了什么?").await.expect("turn 3 失败");
let r3 = session
.submit_turn("我刚才算了什么?")
.await
.expect("turn 3 失败");
println!(" → 回答: {}", r3.text());
let total = session.usage().total();
println!("\n📊 用量: prompt={}, completion={}, total={}",
total.prompt_tokens, total.completion_tokens, total.total_tokens);
println!(
"\n📊 用量: prompt={}, completion={}, total={}",
total.prompt_tokens, total.completion_tokens, total.total_tokens
);
println!("\n=== 持久化验证 ===");
// 显式释放所有对 backend 的 Arc 引用,确保 SqliteStore Connection 真正关闭。
// 释放顺序:session → bundle(间接持有 NoteTool → backend clone)→ backend 局部变量。
drop(session); // session.bundle Arc 计数 -1
drop(bundle); // bundle Arc 计数归零 → registry → NoteTool → backend clone Arc 计数 2→1
drop(backend); // backend 局部变量 Arc 计数 1→0 → SqliteStore::drop → Connection 自动 close
drop(session); // session.bundle Arc 计数 -1
drop(bundle); // bundle Arc 计数归零 → registry → NoteTool → backend clone Arc 计数 2→1
drop(backend); // backend 局部变量 Arc 计数 1→0 → SqliteStore::drop → Connection 自动 close
let backend2: Arc<dyn MemoryStore> =
Arc::new(SqliteStore::open(&db_path).expect("重开 SqliteStore 失败"));
let filter = MemoryFilter { prefix: Some("note:".into()), ..Default::default() };
let filter = MemoryFilter {
prefix: Some("note:".into()),
..Default::default()
};
let items = backend2.list(&filter).await.expect("list 失败");
println!("✓ 跨连接数据存活: 找到 {} 条 note", items.len());
assert!(!items.is_empty(), "持久化验证失败:重开后无数据");
@@ -245,4 +383,4 @@ async fn main() {
}
println!("\n✓ 端到端演示完成");
}
}
+12 -5
View File
@@ -1,4 +1,5 @@
//! engine_demo —— SessionManager + Checkpointer 端到端示例。
//! Required features: cargo run --example engine_demo --features "engine"
//!
//! 演示:
//! 1. SessionManager::create 创建 session
@@ -190,8 +191,8 @@ async fn main() {
snapshot_turn, snapshot_data_count
);
let mut rolled_back =
AgentSession::from_snapshot(snapshot, agent.clone(), bundle.clone()).expect("from_snapshot");
let mut rolled_back = AgentSession::from_snapshot(snapshot, agent.clone(), bundle.clone())
.expect("from_snapshot");
rolled_back
.restore_memory()
.await
@@ -216,8 +217,14 @@ async fn main() {
after_turn,
before_turn
);
assert_eq!(after_turn, snapshot_turn, "rollback 后 turn_index 应等于 checkpoint 时刻值");
assert!(after_cost <= before_cost, "rollback 后 cost 应 ≤ rollback 前");
assert_eq!(
after_turn, snapshot_turn,
"rollback 后 turn_index 应等于 checkpoint 时刻值"
);
assert!(
after_cost <= before_cost,
"rollback 后 cost 应 ≤ rollback 前"
);
println!("✓ rollback + replace 一致性验证通过");
// 9. destroy 父子 session
@@ -249,4 +256,4 @@ async fn main() {
sm.destroy(&c_id).await.unwrap();
println!("\n✓ engine_demo 完成");
}
}
+33 -8
View File
@@ -1,4 +1,5 @@
//! knowledge_graph_demo -- 知识图谱 + 双通道检索演示。
//! Required features: cargo run --example knowledge_graph_demo --features "memory"
//!
//! 演示:
//! 1. 构建 KnowledgeGraph(实体 + 关系)
@@ -52,15 +53,30 @@ async fn main() {
graph.add_entity(e.clone()).await.unwrap();
}
graph
.add_relation(GraphRelation::new("langchain", "langgraph", "includes", 0.9))
.add_relation(GraphRelation::new(
"langchain",
"langgraph",
"includes",
0.9,
))
.await
.unwrap();
graph
.add_relation(GraphRelation::new("langchain", "langsmith", "includes", 0.7))
.add_relation(GraphRelation::new(
"langchain",
"langsmith",
"includes",
0.7,
))
.await
.unwrap();
graph
.add_relation(GraphRelation::new("langchain", "python", "built_with", 0.95))
.add_relation(GraphRelation::new(
"langchain",
"python",
"built_with",
0.95,
))
.await
.unwrap();
graph
@@ -86,7 +102,10 @@ async fn main() {
// ── 3. 标签管理 ──
println!("\n=== 3. 标签管理 ===");
graph
.set_entity_tags("langchain", vec!["ai".into(), "framework".into(), "llm".into()])
.set_entity_tags(
"langchain",
vec!["ai".into(), "framework".into(), "llm".into()],
)
.await
.unwrap();
graph
@@ -118,8 +137,8 @@ async fn main() {
.unwrap();
// Hybrid 策略(默认)
let retriever = MemoryRetriever::new(ks, RetrieverConfig::default())
.with_knowledge_graph(graph.clone());
let retriever =
MemoryRetriever::new(ks, RetrieverConfig::default()).with_knowledge_graph(graph.clone());
println!("\n--- Hybrid 检索: 'langchain' ---");
let result = retriever.retrieve("langchain").await.unwrap();
println!("策略: {:?}", result.strategy);
@@ -129,7 +148,10 @@ async fn main() {
println!(" [Store] {} (score={:.3})", page.title, score);
}
RetrievalItem::GraphEntity {
entity, score, path, ..
entity,
score,
path,
..
} => {
println!(
" [Graph] {} (score={:.3}, path={:?})",
@@ -172,7 +194,10 @@ async fn main() {
}
}
assert!(
result.items.iter().all(|i| matches!(i, RetrievalItem::GraphEntity { .. })),
result
.items
.iter()
.all(|i| matches!(i, RetrievalItem::GraphEntity { .. })),
"GraphOnly 应只返回 Graph 结果"
);
+1
View File
@@ -1,4 +1,5 @@
//! knowledge_search_demo —— 知识页面存储与关键词检索。
//! Required features: cargo run --example knowledge_search_demo --features "memory"
//!
//! 演示:
//! 1. `KnowledgeStore` 存储多个 `KnowledgePage`
+1
View File
@@ -1,4 +1,5 @@
//! prompt_composer —— 提示词模板与组合器离线示例。
//! Required features: cargo run --example prompt_composer --features "prompt,llm"
//!
//! 演示:
//! 1. `PromptTemplate::compile` + `render` 变量插值(`{{var}}` 语法)
+55 -18
View File
@@ -1,41 +1,61 @@
//! quick_start —— 30 行最小可运行示例,展示 Agent / BaseTool / Builder / Session 四层抽象。
//! Required features: cargo run --example quick_start --features "agent"
//!
//! 运行:`cargo run --example quick_start`(离线,零配置)
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::LlmProvider;
use agcore::llm::types::{Usage, message::{ContentBlock, Message}, response_v2::{MessageResponse, StopReason}};
use agcore::llm::types::{
Usage,
message::{ContentBlock, Message},
response_v2::{MessageResponse, StopReason},
};
use agcore::tools::{BaseTool, ToolContext, ToolError, ToolRegistry};
use async_trait::async_trait;
use serde_json::{Value, json};
use std::sync::Arc;
struct Greeter;
impl Agent for Greeter {
fn name(&self) -> &str { "greeter" }
fn system_prompt(&self) -> Option<&str> { Some("中文助手,先调用 echo 工具,再总结。") }
fn name(&self) -> &str {
"greeter"
}
fn system_prompt(&self) -> Option<&str> {
Some("中文助手,先调用 echo 工具,再总结。")
}
}
struct EchoTool;
#[async_trait]
impl BaseTool for EchoTool {
fn name(&self) -> &str { "echo" }
fn description(&self) -> &str { "回显文本" }
fn name(&self) -> &str {
"echo"
}
fn description(&self) -> &str {
"回显文本"
}
fn parameters(&self) -> Value {
json!({"type":"object","properties":{"text":{"type":"string"}},"required":["text"]})
}
async fn execute(&self, args: Value, _: &ToolContext<'_>) -> Result<Value, ToolError> {
let text = args.get("text").and_then(|v| v.as_str())
.ok_or_else(|| ToolError::InvalidArguments("text".into(), "需要 string 类型的 text 参数".into()))?;
let text = args.get("text").and_then(|v| v.as_str()).ok_or_else(|| {
ToolError::InvalidArguments("text".into(), "需要 string 类型的 text 参数".into())
})?;
Ok(json!({"echoed": format!("收到: {text}")}))
}
}
fn resp(content: Vec<ContentBlock>, stop: StopReason, u: (u32, u32)) -> MessageResponse {
MessageResponse { id: String::new(), model: "mock".into(), message: Message::Assistant { content },
usage: Usage::from_input_output(u.0, u.1), stop_reason: stop, extra: Default::default() }
MessageResponse {
id: String::new(),
model: "mock".into(),
message: Message::Assistant { content },
usage: Usage::from_input_output(u.0, u.1),
stop_reason: stop,
extra: Default::default(),
}
}
#[tokio::main]
@@ -43,14 +63,31 @@ async fn main() {
let mut registry = ToolRegistry::new();
registry.register(Arc::new(EchoTool)).unwrap();
let provider: Arc<dyn LlmProvider> = Arc::new(MockProvider::new(vec![
resp(vec![ContentBlock::ToolUse { id: "c1".into(), name: "echo".into(),
input: json!({"text": "你好"}) }], StopReason::ToolUse, (5, 8)),
resp(vec![ContentBlock::Text { text: "EchoTool 已收到您的消息并完成回传。".into() }],
StopReason::Stop, (8, 16)),
resp(
vec![ContentBlock::ToolUse {
id: "c1".into(),
name: "echo".into(),
input: json!({"text": "你好"}),
}],
StopReason::ToolUse,
(5, 8),
),
resp(
vec![ContentBlock::Text {
text: "EchoTool 已收到您的消息并完成回传。".into(),
}],
StopReason::Stop,
(8, 16),
),
]));
let bundle = Arc::new(AgentBuilder::new()
.provider(provider).tool_registry(Arc::new(registry))
.hook_executor(Arc::new(HookExecutor::new())).build().unwrap());
let bundle = Arc::new(
AgentBuilder::new()
.provider(provider)
.tool_registry(Arc::new(registry))
.hook_executor(Arc::new(HookExecutor::new()))
.build()
.unwrap(),
);
let mut session = AgentSession::new(Arc::new(Greeter), "qs", bundle);
let resp = session.submit_turn("你好").await.unwrap();
let text = resp.text();
+2
View File
@@ -1,3 +1,5 @@
//! Required features: cargo run --example simple_visit --features "llm,provider-openai,tracing-init"
use std::env;
use agcore::init_tracing;
+2 -1
View File
@@ -1,4 +1,5 @@
//! streaming_events_demo —— LLM 流式响应事件流消费(含错误路径)。
//! Required features: cargo run --example streaming_events_demo --features "llm,provider-openai"
//!
//! 演示:
//! 1. `MockProvider::chat_stream` 输出标准 `StreamEvent` 流(离线可跑)
@@ -14,9 +15,9 @@
use std::sync::Arc;
use agcore::llm::LlmProvider;
use agcore::llm::cycle::{CycleConfig, LlmCycle};
use agcore::llm::mock::MockProvider;
use agcore::llm::LlmProvider;
use agcore::llm::types::Usage;
use agcore::llm::types::message::{ContentBlock, Message};
use agcore::llm::types::response_v2::{MessageResponse, StopReason, StreamEvent};
+1
View File
@@ -1,4 +1,5 @@
//! sub_agent_dispatch_demo —— SubAgent 并行派发示例。
//! Required features: cargo run --example sub_agent_dispatch_demo --features "engine"
//!
//! 演示:
//! 1. 创建父 session"主编" agent
+3 -2
View File
@@ -1,4 +1,5 @@
//! task_agent_demo —— Plan 解析、Step 状态机、错误路径。
//! Required features: cargo run --example task_agent_demo --features "agent"
//!
//! 演示:
//! 1. `JsonPlanParser::parse` 解析合法 JSON 输入
@@ -12,9 +13,9 @@
use std::collections::HashMap;
use agcore::agent::{AgentError, JsonPlanParser, PlanParser, Step, StepStatus};
use agcore::llm::types::Usage;
use agcore::llm::types::message::Message;
use agcore::llm::types::response_v2::{MessageResponse, StopReason};
use agcore::llm::types::Usage;
#[tokio::main]
async fn main() {
@@ -111,4 +112,4 @@ async fn main() {
assert!(matches!(err, AgentError::PlanParse(_)));
println!("\n✓ task_agent_demo 完成");
}
}