vectorlite/
lib.rs

1//! # VectorLite
2//!
3//! A high-performance, in-memory vector database optimized for AI agent workloads with HTTP API and thread-safe concurrency.
4//!
5//! ## Overview
6//!
7//! VectorLite is designed for **single-instance, low-latency vector operations** in AI agent environments. It prioritizes **sub-millisecond search performance** over distributed scalability, making it ideal for:
8//!
9//! - **AI Agent Sessions**: Session-scoped vector storage with fast retrieval
10//! - **Real-time Search**: Sub-millisecond response requirements  
11//! - **Prototype Development**: Rapid iteration without infrastructure complexity
12//! - **Single-tenant Applications**: No multi-tenancy isolation requirements
13//!
14//! ## Key Features
15//!
16//! - **In-memory storage** for zero-latency access patterns
17//! - **Native Rust ML models** using Candle framework with pluggable architecture
18//! - **Thread-safe concurrency** with RwLock per collection and atomic ID generation
19//! - **HNSW indexing** for approximate nearest neighbor search with configurable accuracy
20//! - **HTTP API** for easy integration with AI agents and other services
21//!
22//! ## Quick Start
23//!
24//! ```rust
25//! use vectorlite::{VectorLiteClient, EmbeddingGenerator, IndexType, SimilarityMetric};
26//!
27//! // Create client with embedding function
28//! let client = VectorLiteClient::new(Box::new(EmbeddingGenerator::new()?));
29//!
30//! // Create collection
31//! client.create_collection("documents", IndexType::HNSW)?;
32//!
33//! // Add text (auto-generates embedding and ID)
34//! let id = client.add_text_to_collection("documents", "Hello world")?;
35//!
36//! // Search
37//! let results = client.search_text_in_collection(
38//!     "documents", 
39//!     "hello", 
40//!     5, 
41//!     SimilarityMetric::Cosine
42//! )?;
43//! ```
44//!
45//! ## Index Types
46//!
47//! ### FlatIndex
48//! - **Complexity**: O(n) search, O(1) insert
49//! - **Memory**: Linear with dataset size
50//! - **Use Case**: Small datasets (< 10K vectors) or exact search requirements
51//!
52//! ### HNSWIndex
53//! - **Complexity**: O(log n) search, O(log n) insert
54//! - **Memory**: ~2-3x vector size due to graph structure
55//! - **Use Case**: Large datasets with approximate search tolerance
56//!
57//! ## Similarity Metrics
58//!
59//! - **Cosine**: Default for normalized embeddings, scale-invariant
60//! - **Euclidean**: Geometric distance, sensitive to vector magnitude
61//! - **Manhattan**: L1 norm, robust to outliers
62//! - **Dot Product**: Raw similarity, requires consistent vector scaling
63//!
64//! ## HTTP Server
65//!
66//! ```rust
67//! use vectorlite::{VectorLiteClient, EmbeddingGenerator, start_server};
68//!
69//! #[tokio::main]
70//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
71//!     let client = VectorLiteClient::new(Box::new(EmbeddingGenerator::new()?));
72//!     start_server(client, "127.0.0.1", 3000).await?;
73//!     Ok(())
74//! }
75//! ```
76//!
77//! ## Configuration Profiles
78//!
79//! ```bash
80//! # Balanced (default)
81//! cargo build
82//!
83//! # Memory-constrained environments
84//! cargo build --features memory-optimized
85//!
86//! # High-precision search
87//! cargo build --features high-accuracy
88//! ```
89
90pub mod index;
91pub mod embeddings;
92pub mod client;
93pub mod server;
94
95pub use index::flat::FlatIndex;
96pub use index::hnsw::HNSWIndex;
97pub use embeddings::{EmbeddingGenerator, EmbeddingFunction};
98pub use client::{VectorLiteClient, Collection, Settings, IndexType};
99pub use server::{create_app, start_server};
100
101use serde::{Serialize, Deserialize};
102
103/// Default vector dimension for embedding models
104pub const DEFAULT_VECTOR_DIMENSION: usize = 768;
105
106/// Represents a vector with an ID and floating-point values
107///
108/// # Examples
109///
110/// ```rust
111/// use vectorlite::Vector;
112///
113/// let vector = Vector {
114///     id: 1,
115///     values: vec![0.1, 0.2, 0.3, 0.4],
116/// };
117/// ```
118#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct Vector {
120    /// Unique identifier for the vector
121    pub id: u64,
122    /// The vector values (embedding coordinates)
123    pub values: Vec<f64>,
124}
125
126/// Search result containing a vector ID and similarity score
127///
128/// Results are typically sorted by score in descending order (highest similarity first).
129///
130/// # Examples
131///
132/// ```rust
133/// use vectorlite::SearchResult;
134///
135/// let result = SearchResult {
136///     id: 42,
137///     score: 0.95,
138/// };
139/// ```
140#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct SearchResult {
142    /// The ID of the matching vector
143    pub id: u64,
144    /// Similarity score (higher is more similar)
145    pub score: f64,
146}
147
148/// Trait for vector indexing implementations
149///
150/// This trait defines the common interface for different vector indexing strategies,
151/// allowing for pluggable implementations like FlatIndex and HNSWIndex.
152///
153/// # Examples
154///
155/// ```rust
156/// use vectorlite::{VectorIndex, Vector, SimilarityMetric, FlatIndex};
157///
158/// let mut index = FlatIndex::new(3, Vec::new());
159/// let vector = Vector { id: 1, values: vec![1.0, 2.0, 3.0] };
160/// 
161/// index.add(vector)?;
162/// let results = index.search(&[1.1, 2.1, 3.1], 5, SimilarityMetric::Cosine);
163/// ```
164pub trait VectorIndex {
165    /// Add a vector to the index
166    fn add(&mut self, vector: Vector) -> Result<(), String>;
167    
168    /// Remove a vector from the index by ID
169    fn delete(&mut self, id: u64) -> Result<(), String>;
170    
171    /// Search for the k most similar vectors
172    fn search(&self, query: &[f64], k: usize, similarity_metric: SimilarityMetric) -> Vec<SearchResult>;
173    
174    /// Get the number of vectors in the index
175    fn len(&self) -> usize;
176    
177    /// Check if the index is empty
178    fn is_empty(&self) -> bool;
179    
180    /// Get a vector by its ID
181    fn get_vector(&self, id: u64) -> Option<&Vector>;
182    
183    /// Get the dimension of vectors in this index
184    fn dimension(&self) -> usize;
185}
186
187/// Wrapper enum for different vector index implementations
188///
189/// This allows for dynamic dispatch between different indexing strategies
190/// while maintaining a unified interface through the VectorIndex trait.
191///
192/// # Examples
193///
194/// ```rust
195/// use vectorlite::{VectorIndexWrapper, FlatIndex, HNSWIndex, Vector, SimilarityMetric};
196///
197/// // Create a flat index wrapper
198/// let mut wrapper = VectorIndexWrapper::Flat(FlatIndex::new(3, Vec::new()));
199/// 
200/// // Add a vector
201/// let vector = Vector { id: 1, values: vec![1.0, 2.0, 3.0] };
202/// wrapper.add(vector)?;
203/// 
204/// // Search using the wrapper
205/// let results = wrapper.search(&[1.1, 2.1, 3.1], 5, SimilarityMetric::Cosine);
206/// ```
207#[derive(Debug, Serialize, Deserialize)]
208pub enum VectorIndexWrapper {
209    /// Flat index for exact search (O(n) complexity)
210    Flat(FlatIndex),
211    /// HNSW index for approximate search (O(log n) complexity)
212    HNSW(HNSWIndex),
213}
214
215impl VectorIndex for VectorIndexWrapper {
216    fn add(&mut self, vector: Vector) -> Result<(), String> {
217        match self {
218            VectorIndexWrapper::Flat(index) => index.add(vector),
219            VectorIndexWrapper::HNSW(index) => index.add(vector),  
220        }
221    }
222
223    fn delete(&mut self, id: u64) -> Result<(), String> {
224        match self {
225            VectorIndexWrapper::Flat(index) => index.delete(id),
226            VectorIndexWrapper::HNSW(index) => index.delete(id),
227        }
228    }
229
230    fn search(&self, query: &[f64], k: usize, s: SimilarityMetric) -> Vec<SearchResult> {
231        match self {
232            VectorIndexWrapper::Flat(index) => index.search(query, k, s),
233            VectorIndexWrapper::HNSW(index) => index.search(query, k, s),
234        }
235    }
236
237    fn len(&self) -> usize {
238        match self {
239            VectorIndexWrapper::Flat(index) => index.len(),
240            VectorIndexWrapper::HNSW(index) => index.len(),
241        }
242    }
243
244    fn is_empty(&self) -> bool {
245        match self {
246            VectorIndexWrapper::Flat(index) => index.is_empty(),
247            VectorIndexWrapper::HNSW(index) => index.is_empty(),
248        }
249    }
250
251    fn get_vector(&self, id: u64) -> Option<&Vector> {
252        match self {
253            VectorIndexWrapper::Flat(index) => index.get_vector(id),
254            VectorIndexWrapper::HNSW(index) => index.get_vector(id),
255        }
256    }
257
258    fn dimension(&self) -> usize {
259        match self {
260            VectorIndexWrapper::Flat(index) => index.dimension(),
261            VectorIndexWrapper::HNSW(index) => index.dimension(),
262        }
263    }
264}
265
266/// Similarity metrics for vector comparison
267///
268/// Different metrics are suitable for different use cases and vector characteristics.
269///
270/// # Examples
271///
272/// ```rust
273/// use vectorlite::SimilarityMetric;
274///
275/// let a = vec![1.0, 2.0, 3.0];
276/// let b = vec![1.1, 2.1, 3.1];
277/// 
278/// let cosine_score = SimilarityMetric::Cosine.calculate(&a, &b);
279/// let euclidean_score = SimilarityMetric::Euclidean.calculate(&a, &b);
280/// ```
281#[derive(Debug, Clone, Copy, PartialEq)]
282pub enum SimilarityMetric {
283    /// Cosine similarity - scale-invariant, good for normalized embeddings
284    /// Range: [-1, 1], where 1 is identical
285    Cosine,
286    /// Euclidean similarity - geometric distance converted to similarity
287    /// Range: [0, 1], where 1 is identical
288    Euclidean,
289    /// Manhattan similarity - L1 norm distance converted to similarity
290    /// Range: [0, 1], where 1 is identical, robust to outliers
291    Manhattan,
292    /// Dot product - raw similarity without normalization
293    /// Range: unbounded, requires consistent vector scaling
294    DotProduct,
295}
296
297impl SimilarityMetric {
298    pub fn calculate(&self, a: &[f64], b: &[f64]) -> f64 {
299        assert_eq!(a.len(), b.len(), "Vectors must have the same length");
300        
301        match self {
302            SimilarityMetric::Cosine => cosine_similarity(a, b),
303            SimilarityMetric::Euclidean => euclidean_similarity(a, b),
304            SimilarityMetric::Manhattan => manhattan_similarity(a, b),
305            SimilarityMetric::DotProduct => dot_product(a, b),
306        }
307    }
308}
309
310impl Default for SimilarityMetric {
311    fn default() -> Self {
312        SimilarityMetric::Cosine
313    }
314}
315
316/// Calculate cosine similarity between two vectors
317///
318/// Cosine similarity measures the cosine of the angle between two vectors,
319/// making it scale-invariant and suitable for normalized embeddings.
320///
321/// # Arguments
322///
323/// * `a` - First vector
324/// * `b` - Second vector (must have same length as `a`)
325///
326/// # Returns
327///
328/// Similarity score in range [-1, 1] where:
329/// - 1.0 = identical vectors
330/// - 0.0 = orthogonal vectors  
331/// - -1.0 = opposite vectors
332///
333/// # Panics
334///
335/// Panics if vectors have different lengths.
336///
337/// # Examples
338///
339/// ```rust
340/// use vectorlite::cosine_similarity;
341///
342/// let a = vec![1.0, 2.0, 3.0];
343/// let b = vec![1.0, 2.0, 3.0];
344/// let similarity = cosine_similarity(&a, &b);
345/// assert!((similarity - 1.0).abs() < 1e-10); // Identical vectors
346/// ```
347pub fn cosine_similarity(a: &[f64], b: &[f64]) -> f64 {
348    assert_eq!(a.len(), b.len(), "Vectors must have the same length");
349
350    let (mut dot, mut norm_a_sq, mut norm_b_sq) = (0.0, 0.0, 0.0);
351
352    for (&x, &y) in a.iter().zip(b.iter()) {
353        dot += x * y;
354        norm_a_sq += x * x;
355        norm_b_sq += y * y;
356    }
357
358    let norm_a = norm_a_sq.sqrt();
359    let norm_b = norm_b_sq.sqrt();
360
361    if norm_a == 0.0 || norm_b == 0.0 {
362        0.0
363    } else {
364        dot / (norm_a * norm_b)
365    }
366}
367
368/// Calculate Euclidean similarity between two vectors
369///
370/// Euclidean similarity converts the Euclidean distance to a similarity score.
371/// It's sensitive to vector magnitude and suitable for vectors with consistent scaling.
372///
373/// # Arguments
374///
375/// * `a` - First vector
376/// * `b` - Second vector (must have same length as `a`)
377///
378/// # Returns
379///
380/// Similarity score in range [0, 1] where:
381/// - 1.0 = identical vectors
382/// - 0.0 = very distant vectors
383///
384/// # Panics
385///
386/// Panics if vectors have different lengths.
387///
388/// # Examples
389///
390/// ```rust
391/// use vectorlite::euclidean_similarity;
392///
393/// let a = vec![0.0, 0.0];
394/// let b = vec![3.0, 4.0];
395/// let similarity = euclidean_similarity(&a, &b);
396/// // Distance is 5.0, so similarity is 1/(1+5) = 1/6 ≈ 0.167
397/// ```
398pub fn euclidean_similarity(a: &[f64], b: &[f64]) -> f64 {
399    assert_eq!(a.len(), b.len(), "Vectors must have the same length");
400    
401    let sum_sq = a.iter()
402        .zip(b.iter())
403        .map(|(x, y)| (x - y).powi(2))
404        .sum::<f64>();
405    
406    let distance = sum_sq.sqrt();
407    
408    // Convert distance to similarity: 1 / (1 + distance)
409    // This ensures similarity is in range [0, 1] with 1 being identical
410    1.0 / (1.0 + distance)
411}
412
413/// Calculate Manhattan similarity between two vectors
414///
415/// Manhattan similarity converts the L1 norm (Manhattan distance) to a similarity score.
416/// It's robust to outliers and suitable for high-dimensional sparse vectors.
417///
418/// # Arguments
419///
420/// * `a` - First vector
421/// * `b` - Second vector (must have same length as `a`)
422///
423/// # Returns
424///
425/// Similarity score in range [0, 1] where:
426/// - 1.0 = identical vectors
427/// - 0.0 = very distant vectors
428///
429/// # Panics
430///
431/// Panics if vectors have different lengths.
432///
433/// # Examples
434///
435/// ```rust
436/// use vectorlite::manhattan_similarity;
437///
438/// let a = vec![0.0, 0.0];
439/// let b = vec![3.0, 4.0];
440/// let similarity = manhattan_similarity(&a, &b);
441/// // Distance is 7.0, so similarity is 1/(1+7) = 1/8 = 0.125
442/// ```
443pub fn manhattan_similarity(a: &[f64], b: &[f64]) -> f64 {
444    assert_eq!(a.len(), b.len(), "Vectors must have the same length");
445    
446    let distance = a.iter()
447        .zip(b.iter())
448        .map(|(x, y)| (x - y).abs())
449        .sum::<f64>();
450    
451    // Convert distance to similarity: 1 / (1 + distance)
452    // This ensures similarity is in range [0, 1] with 1 being identical
453    1.0 / (1.0 + distance)
454}
455
456/// Calculate dot product between two vectors
457///
458/// Dot product is the raw similarity without normalization.
459/// It requires consistent vector scaling and can produce unbounded results.
460///
461/// # Arguments
462///
463/// * `a` - First vector
464/// * `b` - Second vector (must have same length as `a`)
465///
466/// # Returns
467///
468/// Dot product value (unbounded range):
469/// - Positive values indicate similar direction
470/// - Zero indicates orthogonal vectors
471/// - Negative values indicate opposite direction
472///
473/// # Panics
474///
475/// Panics if vectors have different lengths.
476///
477/// # Examples
478///
479/// ```rust
480/// use vectorlite::dot_product;
481///
482/// let a = vec![1.0, 2.0, 3.0];
483/// let b = vec![1.0, 2.0, 3.0];
484/// let product = dot_product(&a, &b);
485/// assert!((product - 14.0).abs() < 1e-10); // 1*1 + 2*2 + 3*3 = 14
486/// ```
487pub fn dot_product(a: &[f64], b: &[f64]) -> f64 {
488    assert_eq!(a.len(), b.len(), "Vectors must have the same length");
489    
490    a.iter()
491        .zip(b.iter())
492        .map(|(x, y)| x * y)
493        .sum::<f64>()
494}
495
496#[cfg(test)]
497mod tests {
498    use super::*;
499
500    #[test]
501    fn test_cosine_similarity_identical_vectors() {
502        let a = vec![1.0, 2.0, 3.0];
503        let b = vec![1.0, 2.0, 3.0];
504        assert!((cosine_similarity(&a, &b) - 1.0).abs() < 1e-10);
505    }
506
507    #[test]
508    fn test_cosine_similarity_orthogonal_vectors() {
509        let a = vec![1.0, 0.0];
510        let b = vec![0.0, 1.0];
511        assert!((cosine_similarity(&a, &b) - 0.0).abs() < 1e-10);
512    }
513
514    #[test]
515    fn test_cosine_similarity_opposite_vectors() {
516        let a = vec![1.0, 2.0, 3.0];
517        let b = vec![-1.0, -2.0, -3.0];
518        assert!((cosine_similarity(&a, &b) - (-1.0)).abs() < 1e-10);
519    }
520
521    #[test]
522    fn test_euclidean_similarity_identical_vectors() {
523        let a = vec![1.0, 2.0, 3.0];
524        let b = vec![1.0, 2.0, 3.0];
525        assert!((euclidean_similarity(&a, &b) - 1.0).abs() < 1e-10);
526    }
527
528    #[test]
529    fn test_euclidean_similarity_different_vectors() {
530        let a = vec![0.0, 0.0];
531        let b = vec![3.0, 4.0];
532        let expected = 1.0 / (1.0 + 5.0); // 1 / (1 + sqrt(3^2 + 4^2))
533        assert!((euclidean_similarity(&a, &b) - expected).abs() < 1e-10);
534    }
535
536    #[test]
537    fn test_manhattan_similarity_identical_vectors() {
538        let a = vec![1.0, 2.0, 3.0];
539        let b = vec![1.0, 2.0, 3.0];
540        assert!((manhattan_similarity(&a, &b) - 1.0).abs() < 1e-10);
541    }
542
543    #[test]
544    fn test_manhattan_similarity_different_vectors() {
545        let a = vec![0.0, 0.0];
546        let b = vec![3.0, 4.0];
547        let expected = 1.0 / (1.0 + 7.0); // 1 / (1 + |0-3| + |0-4|)
548        assert!((manhattan_similarity(&a, &b) - expected).abs() < 1e-10);
549    }
550
551    #[test]
552    fn test_dot_product_identical_vectors() {
553        let a = vec![1.0, 2.0, 3.0];
554        let b = vec![1.0, 2.0, 3.0];
555        let expected = 1.0 + 4.0 + 9.0; // 14.0
556        assert!((dot_product(&a, &b) - expected).abs() < 1e-10);
557    }
558
559    #[test]
560    fn test_dot_product_orthogonal_vectors() {
561        let a = vec![1.0, 0.0];
562        let b = vec![0.0, 1.0];
563        assert!((dot_product(&a, &b) - 0.0).abs() < 1e-10);
564    }
565
566    #[test]
567    fn test_dot_product_opposite_vectors() {
568        let a = vec![1.0, 2.0, 3.0];
569        let b = vec![-1.0, -2.0, -3.0];
570        let expected = -1.0 - 4.0 - 9.0; // -14.0
571        assert!((dot_product(&a, &b) - expected).abs() < 1e-10);
572    }
573
574    #[test]
575    fn test_similarity_metric_enum() {
576        let a = vec![1.0, 2.0, 3.0];
577        let b = vec![1.0, 2.0, 3.0];
578        
579        // Test that all metrics work
580        assert!((SimilarityMetric::Cosine.calculate(&a, &b) - 1.0).abs() < 1e-10);
581        assert!((SimilarityMetric::Euclidean.calculate(&a, &b) - 1.0).abs() < 1e-10);
582        assert!((SimilarityMetric::Manhattan.calculate(&a, &b) - 1.0).abs() < 1e-10);
583        assert!((SimilarityMetric::DotProduct.calculate(&a, &b) - 14.0).abs() < 1e-10);
584    }
585
586    #[test]
587    fn test_similarity_metric_default() {
588        assert_eq!(SimilarityMetric::default(), SimilarityMetric::Cosine);
589    }
590
591    #[test]
592    fn test_vector_store_creation() {
593        let vectors = vec![
594            Vector { id: 0, values: vec![1.0, 2.0, 3.0] },
595            Vector { id: 1, values: vec![4.0, 5.0, 6.0] },
596        ];
597        let store = FlatIndex::new(3, vectors);
598        assert_eq!(store.len(), 2);
599        assert!(!store.is_empty());
600    }
601
602    #[test]
603    fn test_vector_store_search() {
604        let vectors = vec![
605            Vector { id: 0, values: vec![1.0, 0.0, 0.0] },
606            Vector { id: 1, values: vec![0.0, 1.0, 0.0] },
607            Vector { id: 2, values: vec![0.0, 0.0, 1.0] },
608        ];
609        let store = FlatIndex::new(3, vectors);
610        let query = vec![1.0, 0.0, 0.0];
611        let results = store.search(&query, 2, SimilarityMetric::Cosine);
612        
613        assert_eq!(results.len(), 2);
614        assert_eq!(results[0].id, 0);
615        assert!((results[0].score - 1.0).abs() < 1e-10);
616    }
617
618    #[test]
619    fn test_vector_index_wrapper_serialization() {
620        use serde_json;
621        
622        // Test FlatIndex wrapper
623        let vectors = vec![
624            Vector { id: 1, values: vec![1.0, 0.0, 0.0] },
625            Vector { id: 2, values: vec![0.0, 1.0, 0.0] },
626        ];
627        let flat_index = FlatIndex::new(3, vectors);
628        let wrapper = VectorIndexWrapper::Flat(flat_index);
629        
630        // Serialize wrapper
631        let serialized = serde_json::to_string(&wrapper).expect("Serialization should work");
632        
633        // Deserialize wrapper
634        let deserialized: VectorIndexWrapper = serde_json::from_str(&serialized).expect("Deserialization should work");
635        
636        // Verify the deserialized wrapper works
637        assert_eq!(deserialized.len(), 2);
638        assert_eq!(deserialized.dimension(), 3);
639        assert!(!deserialized.is_empty());
640        
641        // Test search through the wrapper
642        let query = vec![1.1, 0.1, 0.1];
643        let results = deserialized.search(&query, 1, SimilarityMetric::Cosine);
644        assert_eq!(results.len(), 1);
645        assert_eq!(results[0].id, 1);
646    }
647
648}