Skip to content

Commit

Permalink
feat: Qdrant vector store (#130)
Browse files Browse the repository at this point in the history
  • Loading branch information
Anush008 authored Apr 21, 2024
1 parent 9cb6d5b commit 5611e28
Show file tree
Hide file tree
Showing 7 changed files with 361 additions and 1 deletion.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ tree-sitter-javascript = { version = "0.21", optional = true }
tree-sitter-c = { version = "0.21", optional = true }
tree-sitter-go = { version = "0.21", optional = true }
tree-sitter-python = { version = "0.21", optional = true }
qdrant-client = {version = "1.8.0", optional = true }

[features]
default = []
Expand All @@ -93,6 +94,7 @@ surrealdb = ["dep:surrealdb"]
sqlite = ["sqlx"]
git = ["gix"]
opensearch = ["dep:opensearch", "aws-config"]
qdrant = ["qdrant-client", "uuid"]

[dev-dependencies]
tokio-test = "0.4.4"
Expand Down
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ This is the Rust language implementation of [LangChain](https://github.com/langc

- [x] [OpenSearch](https://github.com/Abraxas-365/langchain-rust/blob/main/examples/vector_store_opensearch.rs)
- [x] [Postgres](https://github.com/Abraxas-365/langchain-rust/blob/main/examples/vector_store_postgres.rs)
- [x] [Qdrant](https://github.com/Abraxas-365/langchain-rust/blob/main/examples/vector_store_qdrant.rs)
- [x] [Sqlite](https://github.com/Abraxas-365/langchain-rust/blob/main/examples/vector_store_sqlite.rs)
- [x] [SurrealDB](https://github.com/Abraxas-365/langchain-rust/blob/main/examples/vector_store_surrealdb/src/main.rs)

Expand Down Expand Up @@ -219,7 +220,7 @@ cargo add langchain-rust
cargo add langchain-rust --features sqlite
```

Download additional sqlite_vss libraries from https://github.com/asg017/sqlite-vss
Download additional sqlite_vss libraries from <https://github.com/asg017/sqlite-vss>

#### With Postgres

Expand All @@ -233,6 +234,12 @@ cargo add langchain-rust --features postgres
cargo add langchain-rust --features surrealdb
```

#### With Qdrant

```bash
cargo add langchain-rust --features qdrant
```

Please remember to replace the feature flags `sqlite`, `postgres` or `surrealdb` based on your
specific use case.

Expand Down
81 changes: 81 additions & 0 deletions examples/vector_store_qdrant.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// To run this example execute: cargo run --example vector_store_qdrant --features qdrant

#[cfg(feature = "qdrant")]
use langchain_rust::{
embedding::openai::openai_embedder::OpenAiEmbedder,
schemas::Document,
vectorstore::qdrant::{QdrantClient, StoreBuilder},
vectorstore::VectorStore,
};
#[cfg(feature = "qdrant")]
use std::io::Write;

#[cfg(feature = "qdrant")]
#[tokio::main]
async fn main() {
// Initialize Embedder

use langchain_rust::vectorstore::VecStoreOptions;

// Requires OpenAI API key to be set in the environment variable OPENAI_API_KEY
let embedder = OpenAiEmbedder::default();

// Initialize the qdrant_client::QdrantClient
// Ensure Qdrant is running at localhost, with gRPC port at 6334
// docker run -p 6334:6334 qdrant/qdrant
let client = QdrantClient::from_url("http://localhost:6334")
.build()
.unwrap();

let store = StoreBuilder::new()
.embedder(embedder)
.client(client)
.collection_name("langchain-rs")
.build()
.await
.unwrap();

// Add documents to the database
let doc1 = Document::new(
"langchain-rust is a port of the langchain python library to rust and was written in 2024.",
);
let doc2 = Document::new(
"langchaingo is a port of the langchain python library to go language and was written in 2023."
);
let doc3 = Document::new(
"Capital of United States of America (USA) is Washington D.C. and the capital of France is Paris."
);
let doc4 = Document::new("Capital of France is Paris.");

store
.add_documents(&vec![doc1, doc2, doc3, doc4], &VecStoreOptions::default())
.await
.unwrap();

// Ask for user input
print!("Query> ");
std::io::stdout().flush().unwrap();
let mut query = String::new();
std::io::stdin().read_line(&mut query).unwrap();

let results = store
.similarity_search(&query, 2, &VecStoreOptions::default())
.await
.unwrap();

if results.is_empty() {
println!("No results found.");
return;
} else {
results.iter().for_each(|r| {
println!("Document: {}", r.page_content);
});
}
}

#[cfg(not(feature = "qdrant"))]
fn main() {
println!("This example requires the 'qdrant' feature to be enabled.");
println!("Please run the command as follows:");
println!("cargo run --example vector_store_qdrant --features qdrant");
}
3 changes: 3 additions & 0 deletions src/vectorstore/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ pub mod surrealdb;
#[cfg(feature = "opensearch")]
pub mod opensearch;

#[cfg(feature = "qdrant")]
pub mod qdrant;

mod vectorstore;

pub use options::*;
Expand Down
139 changes: 139 additions & 0 deletions src/vectorstore/qdrant/builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
use crate::embedding::Embedder;
use crate::vectorstore::qdrant::Store;
use qdrant_client::client::QdrantClient;
use qdrant_client::qdrant::vectors_config::Config;
use qdrant_client::qdrant::{CreateCollection, Distance, Filter, VectorParams, VectorsConfig};
use std::error::Error;
use std::sync::Arc;

pub struct StoreBuilder {
client: Option<QdrantClient>,
embedder: Option<Arc<dyn Embedder>>,
collection_name: Option<String>,
content_field: String,
metadata_field: String,
recreate_collection: bool,
search_filter: Option<Filter>,
}

impl Default for StoreBuilder {
fn default() -> Self {
Self::new()
}
}

impl StoreBuilder {
/// Create a new StoreBuilder object with default values.
pub fn new() -> Self {
StoreBuilder {
client: None,
embedder: None,
collection_name: None,
search_filter: None,
content_field: "page_content".to_string(),
metadata_field: "metadata".to_string(),
recreate_collection: false,
}
}

/// An instance of `qdrant_client::QdrantClient` for the Store. REQUIRED.
pub fn client(mut self, client: QdrantClient) -> Self {
self.client = Some(client);
self
}

/// Embeddings provider for the Store. REQUIRED.
pub fn embedder<E: Embedder + 'static>(mut self, embedder: E) -> Self {
self.embedder = Some(Arc::new(embedder));
self
}

/// Name of the collection in Qdrant. REQUIRED.
/// It is recommended to create a collection in advance, with the required configurations.
/// https://qdrant.tech/documentation/concepts/collections/#create-a-collection
///
/// If the collection doesn't exist, it will be created with the embedding provider's dimension
/// and Cosine similarity metric.
pub fn collection_name(mut self, collection_name: &str) -> Self {
self.collection_name = Some(collection_name.to_string());
self
}

/// Name of the field in the Qdrant point's payload that will store the metadata of the documents.
/// Default: "metadata"
pub fn metadata_field(mut self, metadata_field: &str) -> Self {
self.metadata_field = metadata_field.to_string();
self
}

/// Name of the field in the Qdrant point's payload that will store the content of the documents.
/// Default: "page_content"
pub fn content_field(mut self, content_field: &str) -> Self {
self.content_field = content_field.to_string();
self
}

/// If set to true, the collection will be deleted and recreated using
/// the embedding provider's dimension and Cosine similarity metric.
pub fn recreate_collection(mut self, recreate_collection: bool) -> Self {
self.recreate_collection = recreate_collection;
self
}

/// Filter to be applied to the search results.
/// https://qdrant.tech/documentation/concepts/filtering/
/// Instance of use `qdrant_client::qdrant::Filter`
pub fn search_filter(mut self, search_filter: Filter) -> Self {
self.search_filter = Some(search_filter);
self
}

/// Build the Store object.
pub async fn build(mut self) -> Result<Store, Box<dyn Error>> {
let client = self.client.take().ok_or("'client' is required")?;
let embedder = self.embedder.take().ok_or("'embedder' is required")?;
let collection_name = self
.collection_name
.take()
.ok_or("'collection_name' is required")?;

let collection_exists = client.collection_exists(&collection_name).await?;

// Delete the collection if it exists and recreate_collection flag is set
if collection_exists && self.recreate_collection {
client.delete_collection(&collection_name).await?;
}

// Create the collection if it doesn't exist or recreate_collection flag is set
if !collection_exists || self.recreate_collection {
// Embed some text to get the dimension of the embeddings
let embeddings = embedder
.embed_query("Text to retrieve embeddings dimension")
.await?;
let embeddings_dimension = embeddings.len() as u64;

client
.create_collection(&CreateCollection {
collection_name: collection_name.clone(),
vectors_config: Some(VectorsConfig {
config: Some(Config::Params(VectorParams {
size: embeddings_dimension,
distance: Distance::Cosine.into(),
..Default::default()
})),
}),
..Default::default()
})
.await?;
}

Ok(Store {
client,
embedder,
collection_name,
search_filter: self.search_filter,
content_field: self.content_field,
metadata_field: self.metadata_field,
})
}
}
5 changes: 5 additions & 0 deletions src/vectorstore/qdrant/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod builder;
mod qdrant;

pub use builder::*;
pub use qdrant::*;
Loading

0 comments on commit 5611e28

Please sign in to comment.