mod chunk;
mod memory;
#[cfg(feature = "pgvector")]
mod pg;
#[cfg(feature = "pinecone")]
mod pinecone;
#[cfg(feature = "qdrant")]
mod qdrant;
pub use chunk::chunk_text;
pub use memory::InMemoryVectorStore;
#[cfg(feature = "pgvector")]
#[cfg_attr(docsrs, doc(cfg(feature = "pgvector")))]
pub use pg::PgVectorStore;
#[cfg(feature = "pinecone")]
#[cfg_attr(docsrs, doc(cfg(feature = "pinecone")))]
pub use pinecone::PineconeStore;
#[cfg(feature = "qdrant")]
#[cfg_attr(docsrs, doc(cfg(feature = "qdrant")))]
pub use qdrant::QdrantStore;
use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::error::Result;
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct MetadataFilter {
pub equals: BTreeMap<String, Value>,
}
impl MetadataFilter {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn eq(mut self, key: impl Into<String>, value: Value) -> Self {
self.equals.insert(key.into(), value);
self
}
pub fn is_empty(&self) -> bool {
self.equals.is_empty()
}
pub fn matches(&self, metadata: &Value) -> bool {
self.equals
.iter()
.all(|(key, expected)| metadata.get(key) == Some(expected))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Document {
pub id: String,
pub vector: Vec<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
#[serde(default)]
pub metadata: Value,
}
impl Document {
pub fn new(id: impl Into<String>, vector: Vec<f32>) -> Self {
Self {
id: id.into(),
vector,
text: None,
metadata: Value::Null,
}
}
#[must_use]
pub fn with_text(mut self, text: impl Into<String>) -> Self {
self.text = Some(text.into());
self
}
#[must_use]
pub fn with_metadata(mut self, metadata: Value) -> Self {
self.metadata = metadata;
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchResult {
pub id: String,
pub score: f32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
#[serde(default)]
pub metadata: Value,
}
#[async_trait::async_trait]
pub trait VectorStore: Send + Sync {
async fn upsert(&self, documents: Vec<Document>) -> Result<()>;
async fn search(&self, vector: Vec<f32>, top_k: usize) -> Result<Vec<SearchResult>>;
async fn search_filtered(
&self,
vector: Vec<f32>,
top_k: usize,
filter: &MetadataFilter,
) -> Result<Vec<SearchResult>> {
if filter.is_empty() {
return self.search(vector, top_k).await;
}
let fetch = top_k.saturating_mul(4).max(top_k);
let results = self.search(vector, fetch).await?;
Ok(results
.into_iter()
.filter(|r| filter.matches(&r.metadata))
.take(top_k)
.collect())
}
async fn upsert_batched(&self, documents: Vec<Document>, batch_size: usize) -> Result<()> {
let batch_size = batch_size.max(1);
let mut documents = documents;
while !documents.is_empty() {
let rest = documents.split_off(documents.len().min(batch_size));
self.upsert(documents).await?;
documents = rest;
}
Ok(())
}
async fn delete(&self, ids: &[String]) -> Result<()>;
}