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:
@@ -1,4 +1,5 @@
|
||||
//! agent_session_demo —— Agent 装配 + 会话链路 + SessionMemory 桥接。
|
||||
//! Required features: cargo run --example agent_session_demo --features "agent"
|
||||
//!
|
||||
//! 演示:
|
||||
//! 1. 实现 `Agent` trait(定义角色 + system prompt)
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
//! bridge_keys_demo —— bridge_keys 过滤 + 子↔子共享 namespace 示例。
|
||||
//! Required features: cargo run --example bridge_keys_demo --features "engine"
|
||||
//!
|
||||
//! 演示:
|
||||
//! 1. 父 session 设置 SessionMemory(key: "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");
|
||||
|
||||
|
||||
@@ -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,4 +1,5 @@
|
||||
//! conversation_memory_demo —— 对话记忆滑动窗口与隔离。
|
||||
//! Required features: cargo run --example conversation_memory_demo --features "memory"
|
||||
//!
|
||||
//! 演示:
|
||||
//! 1. `ConversationMemoryConfig` 构造(SlidingWindow / Full 策略)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
//! custom_tool —— 自定义工具注册、单次 / 并行调用、权限检查。
|
||||
//! Required features: cargo run --example custom_tool --features "tools,llm"
|
||||
//!
|
||||
//! 演示:
|
||||
//! 1. 实现 `BaseTool` trait(WeatherTool + DeleteFileTool)
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
@@ -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
@@ -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 完成");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,4 +1,5 @@
|
||||
//! knowledge_search_demo —— 知识页面存储与关键词检索。
|
||||
//! Required features: cargo run --example knowledge_search_demo --features "memory"
|
||||
//!
|
||||
//! 演示:
|
||||
//! 1. `KnowledgeStore` 存储多个 `KnowledgePage`
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
//! prompt_composer —— 提示词模板与组合器离线示例。
|
||||
//! Required features: cargo run --example prompt_composer --features "prompt,llm"
|
||||
//!
|
||||
//! 演示:
|
||||
//! 1. `PromptTemplate::compile` + `render` 变量插值(`{{var}}` 语法)
|
||||
|
||||
+55
-18
@@ -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();
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Required features: cargo run --example simple_visit --features "llm,provider-openai,tracing-init"
|
||||
|
||||
use std::env;
|
||||
|
||||
use agcore::init_tracing;
|
||||
|
||||
@@ -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,4 +1,5 @@
|
||||
//! sub_agent_dispatch_demo —— SubAgent 并行派发示例。
|
||||
//! Required features: cargo run --example sub_agent_dispatch_demo --features "engine"
|
||||
//!
|
||||
//! 演示:
|
||||
//! 1. 创建父 session("主编" agent)
|
||||
|
||||
@@ -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 完成");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user