Files
agcore/examples/knowledge_search_demo.rs
T
徐涛 5baa170508 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 可通过)
2026-07-19 08:18:04 +08:00

160 lines
5.5 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! knowledge_search_demo —— 知识页面存储与关键词检索。
//! Required features: cargo run --example knowledge_search_demo --features "memory"
//!
//! 演示:
//! 1. `KnowledgeStore` 存储多个 `KnowledgePage`
//! 2. `MemoryRetriever` 按关键词检索 + TextOverlap (Dice) 评分
//! 3. 评分 [0.0, 1.0] 范围校验
//! 4. `RetrieverConfig::min_score` 阈值过滤
//! 5. `RetrieverConfig::max_results` 截断
//! 6. 空 query 返回空结果
//!
//! 运行:`cargo run --example knowledge_search_demo`
use std::sync::Arc;
use agcore::memory::{
InMemoryStore, KnowledgePage, KnowledgeStore, MemoryRetriever, MemoryStore, RetrieverConfig,
};
use time::OffsetDateTime;
fn make_page(id: &str, title: &str, content: &str) -> KnowledgePage {
let now = OffsetDateTime::now_utc();
KnowledgePage {
id: id.to_string(),
title: title.to_string(),
summary: content.chars().take(30).collect(),
content: content.to_string(),
tags: Vec::new(),
references: Vec::new(),
created_at: now,
updated_at: now,
}
}
#[tokio::main]
async fn main() {
// 1. 创建知识库 + 批量存储页面
let store: Arc<dyn MemoryStore> = Arc::new(InMemoryStore::new());
let ks = KnowledgeStore::new(store);
let pages = vec![
make_page(
"rust-1",
"Rust 入门",
"Rust 是一门系统级编程语言,注重安全性与并发。",
),
make_page(
"python-1",
"Python 简介",
"Python 是一门动态类型的高级编程语言。",
),
make_page(
"langgraph-1",
"LangGraph 框架",
"LangGraph 是 LangChain 的状态图扩展,用于构建多步 Agent。",
),
make_page(
"rust-async",
"Rust 异步编程",
"Rust 异步基于 tokio 与 futures 抽象。",
),
];
for p in &pages {
ks.add_page(p.clone()).await.expect("保存页面失败");
}
println!("=== 已存储 {} 个知识页面 ===", pages.len());
let index = ks.get_index().await;
for entry in &index {
println!(" - {} ({})", entry.title, entry.id);
}
// 2. 关键词检索 —— 期望命中 Rust 相关页面
println!("\n=== 关键词检索:'Rust 异步' ===");
let retriever = MemoryRetriever::new(ks, RetrieverConfig::default());
let result = retriever.retrieve("Rust 异步").await.unwrap();
println!("query: {}", result.query);
for item in &result.items {
match item {
agcore::memory::RetrievalItem::KnowledgePage { page, score } => {
println!(" 命中: {} (score={:.3})", page.title, score);
assert!((0.0..=1.0).contains(score), "score 应在 [0, 1] 区间");
}
agcore::memory::RetrievalItem::GraphEntity { entity, score, .. } => {
println!(" 命中实体: {} (score={:.3})", entity.name, score);
}
}
}
assert!(!result.items.is_empty(), "应至少命中一个页面");
// 3. min_score 阈值过滤
println!("\n=== min_score=0.5 阈值过滤(无关 query===");
let store2: Arc<dyn MemoryStore> = Arc::new(InMemoryStore::new());
let ks2 = KnowledgeStore::new(store2);
ks2.add_page(make_page("rust-1", "Rust 入门", "Rust 入门内容。"))
.await
.unwrap();
let cfg = RetrieverConfig {
max_results: 20,
min_score: 0.5,
graph_depth: 2,
};
let retriever2 = MemoryRetriever::new(ks2, cfg);
let result = retriever2.retrieve("完全不相关的火锅配方").await.unwrap();
println!("无关 query → items.len = {} (期望 0)", result.items.len());
assert!(result.items.is_empty());
// 4. max_results 截断
println!("\n=== max_results=2 截断 ===");
let store3: Arc<dyn MemoryStore> = Arc::new(InMemoryStore::new());
let ks3 = KnowledgeStore::new(store3);
for i in 0..5 {
ks3.add_page(make_page(
&format!("rust-{i}"),
"Rust 主题",
&format!("{i} 篇关于 Rust 的内容"),
))
.await
.unwrap();
}
let cfg = RetrieverConfig {
max_results: 2,
min_score: 0.0,
graph_depth: 2,
};
let retriever3 = MemoryRetriever::new(ks3, cfg);
let result = retriever3.retrieve("Rust").await.unwrap();
println!(
"5 个相关页面 → 返回 items.len = {} (期望 2)",
result.items.len()
);
assert_eq!(result.items.len(), 2);
// 5. 空 query
println!("\n=== 空 query ===");
let empty = retriever3.retrieve("").await.unwrap();
println!("空 query → items.len = {}", empty.items.len());
assert!(empty.items.is_empty());
// 6. 停用词过滤:`extract_keywords` 在检索前过滤单字符词与停用词
println!("\n=== 停用词过滤 ===");
let mixed = retriever3.retrieve("the Rust is").await.unwrap();
println!(
"query='the Rust is' → 命中 {} 个 (停用词 'the'/'is' 被过滤,仅 'rust' 进入搜索)",
mixed.items.len()
);
assert!(
!mixed.items.is_empty(),
"非停用词 'rust' 应命中页面(即使 query 中含停用词)"
);
let only_stop = retriever3.retrieve("the is are").await.unwrap();
println!(
"纯停用词 query='the is are' → 命中 {} 个 (期望 0,所有 token 均被过滤)",
only_stop.items.len()
);
assert!(only_stop.items.is_empty(), "纯停用词 query 必须返回空结果");
println!("\n✓ knowledge_search_demo 完成");
}