1use anyhow::Result;
4use parking_lot::Mutex;
5use std::collections::HashMap;
6
7use crate::embeddings;
8use crate::vector_index::{MemoryVectorIndex, VectorIndex};
9use crate::{BatchSearchResult, Vector, VectorId, VectorStoreTrait};
10
11#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
13pub struct VectorStoreConfig {
14 pub auto_embed: bool,
15 pub cache_embeddings: bool,
16 pub similarity_threshold: f32,
17 pub max_results: usize,
18}
19
20impl Default for VectorStoreConfig {
21 fn default() -> Self {
22 Self {
23 auto_embed: true,
24 cache_embeddings: true,
25 similarity_threshold: 0.7,
26 max_results: 100,
27 }
28 }
29}
30
31pub struct VectorStore {
33 index: Box<dyn VectorIndex>,
34 embedding_manager: Mutex<Option<embeddings::EmbeddingManager>>,
40 config: VectorStoreConfig,
41}
42
43impl VectorStore {
44 pub fn new() -> Self {
46 Self {
47 index: Box::new(MemoryVectorIndex::new()),
48 embedding_manager: Mutex::new(None),
49 config: VectorStoreConfig::default(),
50 }
51 }
52
53 pub fn with_embedding_strategy(strategy: embeddings::EmbeddingStrategy) -> Result<Self> {
55 let embedding_manager = embeddings::EmbeddingManager::new(strategy, 1000)?;
56
57 Ok(Self {
58 index: Box::new(MemoryVectorIndex::new()),
59 embedding_manager: Mutex::new(Some(embedding_manager)),
60 config: VectorStoreConfig::default(),
61 })
62 }
63
64 pub fn with_index(index: Box<dyn VectorIndex>) -> Self {
66 Self {
67 index,
68 embedding_manager: Mutex::new(None),
69 config: VectorStoreConfig::default(),
70 }
71 }
72
73 pub fn with_index_and_embeddings(
75 index: Box<dyn VectorIndex>,
76 strategy: embeddings::EmbeddingStrategy,
77 ) -> Result<Self> {
78 let embedding_manager = embeddings::EmbeddingManager::new(strategy, 1000)?;
79
80 Ok(Self {
81 index,
82 embedding_manager: Mutex::new(Some(embedding_manager)),
83 config: VectorStoreConfig::default(),
84 })
85 }
86
87 pub fn with_config(mut self, config: VectorStoreConfig) -> Self {
89 self.config = config;
90 self
91 }
92
93 pub fn index_resource(&mut self, uri: String, content: &str) -> Result<()> {
95 let mut guard = self.embedding_manager.lock();
96 if let Some(ref mut embedding_manager) = *guard {
97 let embeddable_content = embeddings::EmbeddableContent::Text(content.to_string());
98 let vector = embedding_manager.get_embedding(&embeddable_content)?;
99 drop(guard);
100 self.index.insert(uri, vector)
101 } else {
102 drop(guard);
103 tracing::warn!(
105 "index_resource: no embedding manager configured for '{}', using hash-based fallback vector",
106 uri
107 );
108 let vector = self.generate_fallback_vector(content);
109 self.index.insert(uri, vector)
110 }
111 }
112
113 pub fn index_rdf_resource(
115 &mut self,
116 uri: String,
117 label: Option<String>,
118 description: Option<String>,
119 properties: std::collections::HashMap<String, Vec<String>>,
120 ) -> Result<()> {
121 let mut guard = self.embedding_manager.lock();
122 if let Some(ref mut embedding_manager) = *guard {
123 let embeddable_content = embeddings::EmbeddableContent::RdfResource {
124 uri: uri.clone(),
125 label,
126 description,
127 properties,
128 };
129 let vector = embedding_manager.get_embedding(&embeddable_content)?;
130 drop(guard);
131 self.index.insert(uri, vector)
132 } else {
133 Err(anyhow::anyhow!(
134 "Embedding manager required for RDF resource indexing"
135 ))
136 }
137 }
138
139 pub fn index_vector(&mut self, uri: String, vector: Vector) -> Result<()> {
141 self.index.insert(uri, vector)
142 }
143
144 fn compute_query_vector(&self, text: &str) -> Result<Vector> {
148 let mut guard = self.embedding_manager.lock();
149 if let Some(ref mut embedding_manager) = *guard {
150 let embeddable_content = embeddings::EmbeddableContent::Text(text.to_string());
151 embedding_manager.get_embedding(&embeddable_content)
152 } else {
153 drop(guard);
154 tracing::warn!(
155 "similarity search: no embedding manager configured, using hash-based fallback vector for query"
156 );
157 Ok(self.generate_fallback_vector(text))
158 }
159 }
160
161 pub fn similarity_search(&self, query: &str, limit: usize) -> Result<Vec<(String, f32)>> {
163 let query_vector = self.compute_query_vector(query)?;
164 self.index.search_knn(&query_vector, limit)
165 }
166
167 pub fn similarity_search_vector(
169 &self,
170 query: &Vector,
171 limit: usize,
172 ) -> Result<Vec<(String, f32)>> {
173 self.index.search_knn(query, limit)
174 }
175
176 pub fn threshold_search(&self, query: &str, threshold: f32) -> Result<Vec<(String, f32)>> {
178 let query_vector = self.compute_query_vector(query)?;
179 self.index.search_threshold(&query_vector, threshold)
180 }
181
182 pub fn advanced_search(&self, options: SearchOptions) -> Result<Vec<(String, f32)>> {
184 let query_vector = match options.query {
185 SearchQuery::Text(text) => self.compute_query_vector(&text)?,
186 SearchQuery::Vector(vector) => vector,
187 };
188
189 let results = match options.search_type {
190 SearchType::KNN(k) => self.index.search_knn(&query_vector, k)?,
191 SearchType::Threshold(threshold) => {
192 self.index.search_threshold(&query_vector, threshold)?
193 }
194 };
195
196 Ok(results)
197 }
198
199 fn generate_fallback_vector(&self, text: &str) -> Vector {
200 use std::collections::hash_map::DefaultHasher;
202 use std::hash::{Hash, Hasher};
203
204 let mut hasher = DefaultHasher::new();
205 text.hash(&mut hasher);
206 let hash = hasher.finish();
207
208 let mut values = Vec::with_capacity(384); let mut seed = hash;
210
211 for _ in 0..384 {
212 seed = seed.wrapping_mul(1103515245).wrapping_add(12345);
213 let normalized = (seed as f32) / (u64::MAX as f32);
214 values.push((normalized - 0.5) * 2.0); }
216
217 Vector::new(values)
218 }
219
220 pub fn embedding_stats(&self) -> Option<(usize, usize)> {
222 self.embedding_manager
223 .lock()
224 .as_ref()
225 .map(|em| em.cache_stats())
226 }
227
228 pub fn build_vocabulary(&mut self, documents: &[String]) -> Result<()> {
230 if let Some(ref mut embedding_manager) = *self.embedding_manager.lock() {
231 embedding_manager.build_vocabulary(documents)
232 } else {
233 Ok(()) }
235 }
236
237 pub fn calculate_similarity(&self, uri1: &str, uri2: &str) -> Result<f32> {
239 if uri1 == uri2 {
241 return Ok(1.0);
242 }
243
244 let vector1 = self
246 .index
247 .get_vector(uri1)
248 .ok_or_else(|| anyhow::anyhow!("Vector not found for URI: {}", uri1))?;
249
250 let vector2 = self
251 .index
252 .get_vector(uri2)
253 .ok_or_else(|| anyhow::anyhow!("Vector not found for URI: {}", uri2))?;
254
255 vector1.cosine_similarity(vector2)
257 }
258
259 pub fn get_vector(&self, id: &str) -> Option<&Vector> {
261 self.index.get_vector(id)
262 }
263
264 pub fn iter_vectors(&self) -> Vec<(String, Vector)> {
270 self.index.iter_vectors()
271 }
272
273 pub fn index_vector_with_metadata(
275 &mut self,
276 uri: String,
277 vector: Vector,
278 _metadata: HashMap<String, String>,
279 ) -> Result<()> {
280 self.index_vector(uri, vector)
283 }
284
285 pub fn index_resource_with_metadata(
287 &mut self,
288 uri: String,
289 content: &str,
290 _metadata: HashMap<String, String>,
291 ) -> Result<()> {
292 self.index_resource(uri, content)
295 }
296
297 pub fn similarity_search_with_params(
299 &self,
300 query: &str,
301 limit: usize,
302 _params: HashMap<String, String>,
303 ) -> Result<Vec<(String, f32)>> {
304 self.similarity_search(query, limit)
307 }
308
309 pub fn vector_search_with_params(
311 &self,
312 query: &Vector,
313 limit: usize,
314 _params: HashMap<String, String>,
315 ) -> Result<Vec<(String, f32)>> {
316 self.similarity_search_vector(query, limit)
319 }
320
321 pub fn get_vector_ids(&self) -> Result<Vec<String>> {
323 Ok(Vec::new())
326 }
327
328 pub fn remove_vector(&mut self, uri: &str) -> Result<()> {
330 self.index.remove_vector(uri.to_string())
332 }
333
334 pub fn get_statistics(&self) -> Result<HashMap<String, String>> {
336 let mut stats = HashMap::new();
339 stats.insert("type".to_string(), "VectorStore".to_string());
340
341 if let Some((cache_size, cache_capacity)) = self.embedding_stats() {
342 stats.insert("embedding_cache_size".to_string(), cache_size.to_string());
343 stats.insert(
344 "embedding_cache_capacity".to_string(),
345 cache_capacity.to_string(),
346 );
347 }
348
349 Ok(stats)
350 }
351
352 pub fn save_to_disk(&self, path: &str) -> Result<()> {
365 use anyhow::Context as _;
366
367 if !self.index.supports_enumeration() {
368 return Err(anyhow::anyhow!(
369 "save_to_disk: the configured index type does not support full \
370 vector enumeration (VectorIndex::supports_enumeration() == false); \
371 refusing to write a silently-empty vectors.json. Use an index type \
372 that implements iter_vectors/supports_enumeration (e.g. \
373 MemoryVectorIndex, HnswIndex, IvfIndex, PQIndex)."
374 ));
375 }
376
377 std::fs::create_dir_all(path)
378 .with_context(|| format!("Failed to create directory: {}", path))?;
379
380 let vectors = self.index.iter_vectors();
382 let metadata = serde_json::json!({
383 "config": self.config,
384 "vector_count": vectors.len(),
385 "index_type": "memory",
386 });
387 let metadata_path = std::path::Path::new(path).join("metadata.json");
388 let metadata_str = serde_json::to_string_pretty(&metadata)
389 .with_context(|| "Failed to serialize VectorStore metadata")?;
390 std::fs::write(&metadata_path, metadata_str)
391 .with_context(|| format!("Failed to write {}", metadata_path.display()))?;
392
393 let vectors_path = std::path::Path::new(path).join("vectors.json");
395 let vectors_str = serde_json::to_string_pretty(&vectors)
396 .with_context(|| "Failed to serialize VectorStore vectors")?;
397 std::fs::write(&vectors_path, vectors_str)
398 .with_context(|| format!("Failed to write {}", vectors_path.display()))?;
399
400 Ok(())
401 }
402
403 pub fn load_from_disk(path: &str) -> Result<Self> {
410 use anyhow::Context as _;
411
412 let metadata_path = std::path::Path::new(path).join("metadata.json");
414 let metadata_str = std::fs::read_to_string(&metadata_path)
415 .with_context(|| format!("Failed to read {}", metadata_path.display()))?;
416 let metadata: serde_json::Value = serde_json::from_str(&metadata_str)
417 .with_context(|| "Failed to parse VectorStore metadata")?;
418
419 let config: VectorStoreConfig = serde_json::from_value(metadata["config"].clone())
420 .with_context(|| "Failed to deserialize VectorStoreConfig from metadata")?;
421
422 let vectors_path = std::path::Path::new(path).join("vectors.json");
424 let vectors_str = std::fs::read_to_string(&vectors_path)
425 .with_context(|| format!("Failed to read {}", vectors_path.display()))?;
426 let entries: Vec<(String, Vector)> = serde_json::from_str(&vectors_str)
427 .with_context(|| "Failed to deserialize VectorStore vectors")?;
428
429 let mut store = Self {
431 index: Box::new(MemoryVectorIndex::new()),
432 embedding_manager: Mutex::new(None),
433 config,
434 };
435
436 for (id, vector) in entries {
437 store
438 .index
439 .insert(id.clone(), vector)
440 .with_context(|| format!("Failed to re-insert vector '{}'", id))?;
441 }
442
443 Ok(store)
444 }
445
446 pub fn optimize_index(&mut self) -> Result<()> {
448 Ok(())
451 }
452}
453
454impl Default for VectorStore {
455 fn default() -> Self {
456 Self::new()
457 }
458}
459
460impl VectorStoreTrait for VectorStore {
461 fn insert_vector(&mut self, id: VectorId, vector: Vector) -> Result<()> {
462 self.index.insert(id, vector)
463 }
464
465 fn add_vector(&mut self, vector: Vector) -> Result<VectorId> {
466 let id = format!("vec_{}", uuid::Uuid::new_v4());
468 self.index.insert(id.clone(), vector)?;
469 Ok(id)
470 }
471
472 fn get_vector(&self, id: &VectorId) -> Result<Option<Vector>> {
473 Ok(self.index.get_vector(id).cloned())
474 }
475
476 fn get_all_vector_ids(&self) -> Result<Vec<VectorId>> {
477 Ok(self
482 .index
483 .iter_vectors()
484 .into_iter()
485 .map(|(id, _)| id)
486 .collect())
487 }
488
489 fn search_similar(&self, query: &Vector, k: usize) -> Result<Vec<(VectorId, f32)>> {
490 self.index.search_knn(query, k)
491 }
492
493 fn remove_vector(&mut self, id: &VectorId) -> Result<bool> {
494 match self.index.remove_vector(id.clone()) {
497 Ok(()) => Ok(true),
498 Err(_) => Ok(false),
499 }
500 }
501
502 fn len(&self) -> usize {
503 self.index.iter_vectors().len()
506 }
507}
508
509#[derive(Debug, Clone)]
511pub enum SearchQuery {
512 Text(String),
513 Vector(Vector),
514}
515
516#[derive(Debug, Clone)]
518pub enum SearchType {
519 KNN(usize),
520 Threshold(f32),
521}
522
523#[derive(Debug, Clone)]
525pub struct SearchOptions {
526 pub query: SearchQuery,
527 pub search_type: SearchType,
528}
529
530#[derive(Debug, Clone)]
532pub struct VectorOperationResult {
533 pub uri: String,
534 pub similarity: f32,
535 pub vector: Option<Vector>,
536 pub metadata: Option<std::collections::HashMap<String, String>>,
537 pub rank: usize,
538}
539
540pub struct DocumentBatchProcessor;
542
543impl DocumentBatchProcessor {
544 pub fn batch_index(
546 store: &mut VectorStore,
547 documents: &[(String, String)], ) -> Result<Vec<Result<()>>> {
549 let mut results = Vec::new();
550
551 for (uri, content) in documents {
552 let result = store.index_resource(uri.clone(), content);
553 results.push(result);
554 }
555
556 Ok(results)
557 }
558
559 pub fn batch_search(
561 store: &VectorStore,
562 queries: &[String],
563 limit: usize,
564 ) -> Result<BatchSearchResult> {
565 let mut results = Vec::new();
566
567 for query in queries {
568 let result = store.similarity_search(query, limit);
569 results.push(result);
570 }
571
572 Ok(results)
573 }
574}