feat(document): 实现 Document 类型与 RecursiveCharacterSplitter 分割器
新增 Phase 14 核心模块,为 RAG 管线提供 split → embed 阶段的底层支撑。 新增内容: - Document 类型(id/content/metadata/mime_type 四字段 + new/from_raw 构造器) - RecursiveCharacterSplitter(两阶段算法:按 separator 优先级递归分割 + 贪心合并 overlap 滑动窗口) - Embedding trait(异步向量化抽象,复用 LlmError)+ MockEmbedding(sin-hash 零依赖伪随机实现) - 19 个 Document 单元测试 + 6 个 Embedding 单元测试 - document_demo 示例(Document → Splitter → MockEmbedding → InMemoryVectorRetriever 端到端演示) 模块注册: - src/lib.rs: pub mod document + pub use Document - src/llm.rs: pub mod embedding 设计文档:docs/20-phase14-document-and-embedding.md(1417 行,含背景/调研/方案对比/实施计划) 零新外部依赖,所有长度比较以 Unicode 字符为单位(chars_len),CJK 文本行为正确。
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
//! document_demo —— Document + RecursiveCharacterSplitter + MockEmbedding + VectorRetriever 完整衔接示例。
|
||||
//!
|
||||
//! 演示 RAG 管线前置流程:
|
||||
//! 1. 创建多段落 Document
|
||||
//! 2. RecursiveCharacterSplitter 分割为 chunk
|
||||
//! 3. MockEmbedding 嵌入所有 chunk
|
||||
//! 4. 与 InMemoryVectorRetriever 手动 zip 衔接
|
||||
//! 5. 模拟查询做语义检索
|
||||
//!
|
||||
//! 运行:`cargo run --example document_demo`(离线,零配置)
|
||||
|
||||
use agcore::document::{Document, RecursiveCharacterSplitter};
|
||||
use agcore::llm::embedding::{Embedding, MockEmbedding};
|
||||
use agcore::memory::vector::{InMemoryVectorRetriever, VectorRetriever};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
agcore::init_tracing();
|
||||
|
||||
// 1. 创建多段落 Document(含中英文混合)
|
||||
let doc = Document::new(
|
||||
"rust-intro",
|
||||
"Rust 是一门系统编程语言,注重安全、并发和性能。\n\n\
|
||||
Rust 通过所有权系统管理内存,无需垃圾回收器。\
|
||||
所有权规则让内存安全在编译期就能得到保证。\n\n\
|
||||
Rust 的并发模型通过类型系统区分线程间共享与独占数据,\
|
||||
避免数据竞争。Send 和 Sync 两个 trait 标记了类型的线程安全性。\n\n\
|
||||
Rust 的性能与 C/C++ 相当,但提供了更现代的开发体验。\
|
||||
Cargo 是官方的构建系统和包管理器,使用简单直观。",
|
||||
"text/markdown",
|
||||
);
|
||||
|
||||
println!("输入文档: {} 字符", doc.content.chars().count());
|
||||
|
||||
// 2. RecursiveCharacterSplitter 分割
|
||||
let splitter = RecursiveCharacterSplitter::new(200, 30);
|
||||
let chunks = splitter.split(&[doc]);
|
||||
|
||||
println!("\n分割为 {} 个 chunk:", chunks.len());
|
||||
for (i, chunk) in chunks.iter().enumerate() {
|
||||
println!(
|
||||
" [{:02}] id={}, 长度={}",
|
||||
i, chunk.id, chunk.content.chars().count()
|
||||
);
|
||||
}
|
||||
|
||||
// 3. MockEmbedding 嵌入所有 chunk
|
||||
let embedder = MockEmbedding::new(4);
|
||||
let texts: Vec<String> = chunks.iter().map(|c| c.content.clone()).collect();
|
||||
let vectors = embedder.embed(&texts).await.unwrap();
|
||||
println!("\n嵌入维度: {},向量数: {}", embedder.dim(), vectors.len());
|
||||
|
||||
// 4. 与 InMemoryVectorRetriever 手动 zip 衔接
|
||||
let retriever: std::sync::Arc<dyn VectorRetriever> =
|
||||
std::sync::Arc::new(InMemoryVectorRetriever::new());
|
||||
for (chunk, vec) in chunks.iter().zip(vectors.iter()) {
|
||||
retriever.index(chunk.id.clone(), vec.clone()).await.unwrap();
|
||||
}
|
||||
println!("已索引 {} 个 chunk", chunks.len());
|
||||
|
||||
// 5. 模拟查询:复用第一个 chunk 的 embedding 作为查询向量
|
||||
let query_vec = vectors[0].clone();
|
||||
let results = retriever.search(query_vec, 3).await.unwrap();
|
||||
println!("\nTop 3 检索结果(与 chunk 0 相似度):");
|
||||
for (id, score) in &results {
|
||||
println!(" id={}, score={:.4}", id, score);
|
||||
}
|
||||
|
||||
assert_eq!(chunks.len(), vectors.len(), "chunks 与 vectors 数量必须一致");
|
||||
assert!(!results.is_empty(), "至少应返回 1 条检索结果");
|
||||
assert!(results[0].0.starts_with("rust-intro:chunk:0000"), "Top 1 应为 chunk 0 自身");
|
||||
|
||||
println!("\n✓ document_demo 完成");
|
||||
}
|
||||
Reference in New Issue
Block a user