ai_model_vault/rag/
knowledge.rs1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6use super::documents::{ChunkInfo, Document, DocumentStore};
7
8pub struct KnowledgeBase {
10 pub store: DocumentStore,
12
13 pub name: String,
15
16 pub config: KnowledgeBaseConfig,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct KnowledgeBaseConfig {
23 pub embedding_dim: usize,
25
26 pub chunk_size: usize,
28
29 pub chunk_overlap: usize,
31
32 pub max_results: usize,
34
35 pub similarity_threshold: f32,
37}
38
39impl Default for KnowledgeBaseConfig {
40 fn default() -> Self {
41 Self {
42 embedding_dim: 384, chunk_size: 512,
44 chunk_overlap: 50,
45 max_results: 5,
46 similarity_threshold: 0.5,
47 }
48 }
49}
50
51impl KnowledgeBase {
52 pub fn new(name: String, config: KnowledgeBaseConfig) -> Self {
54 Self {
55 store: DocumentStore::new(),
56 name,
57 config,
58 }
59 }
60
61 pub fn add(&mut self, doc: Document) -> crate::error::Result<()> {
63 self.store.add_document(doc)
64 }
65
66 pub fn retrieve(&self, query_embedding: &[f32], top_k: Option<usize>) -> Vec<Document> {
68 let k = top_k.unwrap_or(self.config.max_results);
69 let results = self.store.search_similar(query_embedding, k);
70
71 results
72 .into_iter()
73 .filter(|(_, score)| *score >= self.config.similarity_threshold)
74 .filter_map(|(id, _)| self.store.get_document(&id).cloned())
75 .collect()
76 }
77
78 pub fn chunk_text(&self, text: &str, doc_id: &str) -> Vec<Document> {
80 let chars: Vec<char> = text.chars().collect();
81 let mut chunks = Vec::new();
82 let mut start = 0;
83 let chunk_size = self.config.chunk_size;
84 let overlap = self.config.chunk_overlap;
85
86 let mut chunk_index = 0;
87 while start < chars.len() {
88 let end = (start + chunk_size).min(chars.len());
89 let chunk_text: String = chars[start..end].iter().collect();
90
91 let chunk = Document {
92 id: format!("{}_chunk_{}", doc_id, chunk_index),
93 content: chunk_text,
94 metadata: {
95 let mut meta = HashMap::new();
96 meta.insert("parent_id".to_string(), doc_id.to_string());
97 meta.insert("chunk_index".to_string(), chunk_index.to_string());
98 meta
99 },
100 embedding: None,
101 chunk_info: Some(ChunkInfo {
102 parent_id: Some(doc_id.to_string()),
103 chunk_index,
104 total_chunks: 0, overlap,
106 }),
107 };
108
109 chunks.push(chunk);
110 chunk_index += 1;
111
112 if end >= chars.len() {
113 break;
114 }
115
116 start = end - overlap.min(end);
117 }
118
119 let total = chunks.len();
121 for chunk in &mut chunks {
122 if let Some(info) = &mut chunk.chunk_info {
123 info.total_chunks = total;
124 }
125 }
126
127 chunks
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 #[test]
136 fn test_knowledge_base_config_default() {
137 let cfg = KnowledgeBaseConfig::default();
138 assert_eq!(cfg.embedding_dim, 384);
139 assert_eq!(cfg.chunk_size, 512);
140 assert_eq!(cfg.chunk_overlap, 50);
141 assert_eq!(cfg.max_results, 5);
142 assert!((cfg.similarity_threshold - 0.5).abs() < 1e-5);
143 }
144
145 #[test]
146 fn test_knowledge_base_add_and_retrieve() {
147 let kb = KnowledgeBase::new("test".to_string(), KnowledgeBaseConfig::default());
148 let results = kb.retrieve(&[1.0, 0.0], None);
150 assert!(results.is_empty());
151 }
152
153 #[test]
154 fn test_knowledge_base_add_doc() {
155 let mut kb = KnowledgeBase::new("test".to_string(), KnowledgeBaseConfig::default());
156 let doc = Document {
157 id: "d1".to_string(),
158 content: "hello".to_string(),
159 metadata: HashMap::new(),
160 embedding: Some(vec![1.0, 0.0]),
161 chunk_info: None,
162 };
163 kb.add(doc).unwrap();
164 assert_eq!(kb.store.count(), 1);
165 }
166
167 #[test]
168 fn test_knowledge_base_retrieve_with_threshold() {
169 let cfg = KnowledgeBaseConfig {
170 similarity_threshold: 0.9,
171 ..KnowledgeBaseConfig::default()
172 };
173 let mut kb = KnowledgeBase::new("test".to_string(), cfg);
174
175 let doc = Document {
176 id: "d1".to_string(),
177 content: "hello".to_string(),
178 metadata: HashMap::new(),
179 embedding: Some(vec![1.0, 0.0]),
180 chunk_info: None,
181 };
182 kb.add(doc).unwrap();
183
184 let results = kb.retrieve(&[0.0, 1.0], Some(10));
186 assert!(results.is_empty());
187
188 let results = kb.retrieve(&[1.0, 0.0], Some(10));
190 assert_eq!(results.len(), 1);
191 }
192
193 #[test]
194 fn test_chunk_text() {
195 let cfg = KnowledgeBaseConfig {
196 chunk_size: 10,
197 chunk_overlap: 3,
198 ..KnowledgeBaseConfig::default()
199 };
200 let kb = KnowledgeBase::new("kb".to_string(), cfg);
201
202 let text = "abcdefghijklmnopqrstuvwxyz"; let chunks = kb.chunk_text(text, "doc1");
204 assert!(chunks.len() >= 3);
205
206 assert_eq!(chunks[0].content.len(), 10);
208 assert_eq!(chunks[0].id, "doc1_chunk_0");
209
210 for chunk in &chunks {
212 let info = chunk.chunk_info.as_ref().unwrap();
213 assert_eq!(info.total_chunks, chunks.len());
214 assert_eq!(info.parent_id, Some("doc1".to_string()));
215 }
216 }
217}