Skip to main content

lc_vector_stores/
lib.rs

1#![warn(missing_docs)]
2// lc-vector-stores/src/lib.rs
3//! Vector store implementations.
4//!
5//! Provides document vector storage and retrieval functionality.
6
7pub mod chromadb;
8pub mod chunked_vector_store;
9pub mod document_store;
10mod file_store;
11pub mod filter;
12pub mod lancedb;
13mod memory;
14pub mod neo4j;
15mod provider;
16
17#[cfg(feature = "mongodb-persistence")]
18mod mongo_document_store;
19
20#[cfg(feature = "qdrant-integration")]
21mod qdrant;
22
23#[cfg(feature = "qdrant-integration")]
24pub mod hybrid_native;
25
26#[cfg(feature = "redis-storage")]
27pub mod redis_store;
28
29#[cfg(feature = "sqlite-storage")]
30pub mod sqlite_store;
31
32#[cfg(feature = "pgvector-storage")]
33pub mod pgvector;
34
35pub mod pinecone;
36
37pub use chunked_vector_store::ChunkedVectorStore;
38pub use document_store::{
39    ChunkedDocumentStore, ChunkedDocumentStoreTrait, DocumentStore, InMemoryChunkedDocumentStore,
40    InMemoryDocumentStore,
41};
42pub use file_store::FileVectorStore;
43pub use filter::{FilterOp, MetadataFilter};
44pub use lancedb::{LanceDBConfig, LanceDBVectorStore};
45pub use memory::InMemoryVectorStore;
46pub use neo4j::{Neo4jConfig, Neo4jVectorStore};
47pub use pinecone::PineconeStore;
48pub use provider::{VectorStoreBuilder, VectorStoreProvider, VectorStoreType};
49
50#[cfg(feature = "mongodb-persistence")]
51pub use mongo_document_store::{MongoChunkedDocumentStore, MongoStoreConfig};
52
53#[cfg(feature = "qdrant-integration")]
54pub use qdrant::{QdrantConfig, QdrantDistance, QdrantVectorStore};
55
56#[cfg(feature = "qdrant-integration")]
57pub use hybrid_native::{FusionMethod, NativeHybridQuery, NativeHybridSearch};
58
59pub use chromadb::{ChromaDBConfig, ChromaDBVectorStore};
60
61#[cfg(feature = "redis-storage")]
62pub use redis_store::{RedisDocumentStore, RedisStoreConfig};
63
64#[cfg(feature = "sqlite-storage")]
65pub use sqlite_store::{SQLiteDocumentStore, SQLiteStoreConfig};
66
67#[cfg(feature = "pgvector-storage")]
68pub use pgvector::{build_filter_sql, FilterBinding, FilterSql, PGVectorConfig, PGVectorStore};
69
70use async_trait::async_trait;
71
72// Re-export shared document types from lc-shared
73pub use lc_shared::document::{ChunkDocument, Document, SearchResult, VectorDocument};
74
75// Re-export cosine_similarity from lc-core
76pub use lc_core::math::cosine_similarity;
77
78/// Vector store error types.
79#[derive(Debug, thiserror::Error)]
80#[non_exhaustive]
81pub enum VectorStoreError {
82    /// Document not found.
83    #[error("Document not found: {0}")]
84    DocumentNotFound(String),
85
86    /// Embedding error.
87    #[error("Embedding error: {0}")]
88    EmbeddingError(String),
89
90    /// Storage error.
91    #[error("Storage error: {0}")]
92    StorageError(String),
93
94    /// Connection error (for remote vector databases).
95    #[error("Connection error: {0}")]
96    ConnectionError(String),
97
98    /// Configuration error (e.g. missing environment variables, invalid settings).
99    #[error("Configuration error: {0}")]
100    ConfigError(String),
101
102    /// Metadata filtering is not supported (the backend does not override filtered retrieval).
103    #[error("Metadata filter not supported: {0}")]
104    UnsupportedFilter(String),
105}
106
107/// Vector store trait.
108#[async_trait]
109pub trait VectorStore: Send + Sync {
110    /// Adds documents.
111    ///
112    /// # Arguments
113    /// * `documents` - Document list.
114    /// * `embeddings` - Embedding vectors for documents.
115    ///
116    /// # Returns
117    /// Document ID list.
118    async fn add_documents(
119        &self,
120        documents: Vec<Document>,
121        embeddings: Vec<Vec<f32>>,
122    ) -> Result<Vec<String>, VectorStoreError>;
123
124    /// Searches similar documents.
125    ///
126    /// # Arguments
127    /// * `query_embedding` - Query vector.
128    /// * `k` - Number of documents to return.
129    ///
130    /// # Returns
131    /// Similar document list (sorted by similarity descending).
132    async fn similarity_search(
133        &self,
134        query_embedding: &[f32],
135        k: usize,
136    ) -> Result<Vec<SearchResult>, VectorStoreError>;
137
138    /// Returns this vector store's built-in text embedder, if any.
139    ///
140    /// Q1: previously the trait only took `query_embedding: &[f32]`, so callers had to embed
141    /// the query themselves without a contract saying which embedder to use. With this getter,
142    /// implementations that embed internally can accept text directly via
143    /// [`similarity_search_text`](Self::similarity_search_text); those without one return
144    /// `None`, and the caller gets an explicit error instead of silently using the wrong model.
145    fn embed_query(&self) -> Option<&dyn Embeddings> {
146        None
147    }
148
149    /// Text similarity search: vectorizes `query` with the embedder returned by
150    /// [`embed_query`](Self::embed_query), then searches.
151    ///
152    /// Returns [`VectorStoreError::EmbeddingError`] when no embedder is configured, suggesting
153    /// [`similarity_search`](Self::similarity_search) with a query vector instead.
154    async fn similarity_search_text(
155        &self,
156        query: &str,
157        k: usize,
158    ) -> Result<Vec<SearchResult>, VectorStoreError> {
159        let Some(embeddings) = self.embed_query() else {
160            return Err(VectorStoreError::EmbeddingError(
161                "this vector store has no embedder configured; cannot auto-vectorize the query \
162                 text; call similarity_search with a query vector instead"
163                    .to_string(),
164            ));
165        };
166        let query_embedding = embeddings
167            .embed_query(query)
168            .await
169            .map_err(|e| VectorStoreError::EmbeddingError(e.to_string()))?;
170        self.similarity_search(&query_embedding, k).await
171    }
172
173    /// Similarity search with metadata filtering.
174    ///
175    /// - `filter: None` — no filtering, equivalent to [`similarity_search`](Self::similarity_search).
176    /// - `filter: Some(f)` — returns only documents matching the filter, at most `k` entries.
177    ///
178    /// Default implementation: delegates to [`similarity_search`](Self::similarity_search) when
179    /// there is no filter; returns [`VectorStoreError::UnsupportedFilter`] when a filter is given
180    /// but the backend does not override it, **without silently ignoring** the filter. Backends
181    /// that support filtering should override this method and translate [`MetadataFilter`] into
182    /// their native query syntax (Qdrant payload filter / Pinecone filter / Chroma where / …).
183    async fn similarity_search_with_filter(
184        &self,
185        query_embedding: &[f32],
186        k: usize,
187        filter: Option<&MetadataFilter>,
188    ) -> Result<Vec<SearchResult>, VectorStoreError> {
189        match filter {
190            None => self.similarity_search(query_embedding, k).await,
191            Some(_) => Err(VectorStoreError::UnsupportedFilter(
192                "this vector store does not support metadata filtering; pass filter: None or \
193                 switch to a store that implements similarity_search_with_filter"
194                    .to_string(),
195            )),
196        }
197    }
198
199    /// Similarity search with a minimum score threshold.
200    ///
201    /// - `min_score: None` — no filtering, returns the store's full top-k (even negative scores).
202    /// - `min_score: Some(t)` — returns only results with `score >= t`, at most `k` entries.
203    ///
204    /// Q2: the default implementation re-filters the results of
205    /// [`similarity_search`](Self::similarity_search), the best approximation for backends that
206    /// cannot threshold directly at retrieval time. Implementations that compute similarity
207    /// locally should override this method for the precise "filter first, then take top-k"
208    /// semantics.
209    async fn similarity_search_with_min_score(
210        &self,
211        query_embedding: &[f32],
212        k: usize,
213        min_score: Option<f32>,
214    ) -> Result<Vec<SearchResult>, VectorStoreError> {
215        let results = self.similarity_search(query_embedding, k).await?;
216        match min_score {
217            Some(threshold) => Ok(results
218                .into_iter()
219                .filter(|r| r.score >= threshold)
220                .collect()),
221            None => Ok(results),
222        }
223    }
224
225    /// Gets document by ID.
226    async fn get_document(&self, id: &str) -> Result<Option<Document>, VectorStoreError>;
227
228    /// Gets document embedding by ID.
229    async fn get_embedding(&self, id: &str) -> Result<Option<Vec<f32>>, VectorStoreError>;
230
231    /// Deletes document.
232    async fn delete_document(&self, id: &str) -> Result<(), VectorStoreError>;
233
234    /// Returns document count.
235    async fn count(&self) -> usize;
236
237    /// Clears store.
238    async fn clear(&self) -> Result<(), VectorStoreError>;
239}
240
241/// Embedding model trait — re-exported from lc-embeddings.
242///
243/// Used by vector store implementations that need to embed documents on the fly
244/// (e.g., Pinecone's `upsert` method).
245pub use lc_embeddings::{EmbeddingError, Embeddings};
246
247#[cfg(test)]
248mod tests {
249    use super::*;
250
251    #[test]
252    fn test_document_creation() {
253        let doc = Document::new("Hello, world!")
254            .with_metadata("source", "test")
255            .with_id("doc-1");
256
257        assert_eq!(doc.content, "Hello, world!");
258        assert_eq!(
259            doc.metadata.get("source"),
260            Some(&serde_json::Value::String("test".to_string()))
261        );
262        assert_eq!(doc.id, Some("doc-1".to_string()));
263    }
264
265    #[test]
266    fn test_document_page_content() {
267        let doc = Document::new("Test content");
268        assert_eq!(doc.page_content(), "Test content");
269    }
270}