Skip to content

Qdrant

rig::qdrant (feature qdrant) implements Rig’s InsertDocuments and VectorStoreIndex traits on top of Qdrant, a dedicated vector database written in Rust. Use it when you want a persistent, filterable store that scales beyond the in-memory one.

[dependencies]
rig = { version = "0.44.0", features = ["qdrant"] }
qdrant-client = "1"
tokio = { version = "1", features = ["full"] }

qdrant-client is Qdrant’s own Rust client, which you use to connect and create collections. Start a local Qdrant with Docker:

Terminal window
docker run -p 6333:6333 -p 6334:6334 qdrant/qdrant

Create a collection, embed some documents, insert them, then search. The collection’s vector size must match your embedding model (1536 for text-embedding-3-small).

use qdrant_client::{
Qdrant,
qdrant::{CreateCollectionBuilder, Distance, QueryPointsBuilder, VectorParamsBuilder},
};
use rig::prelude::*;
use serde::{Deserialize, Serialize};
use rig::providers::openai::{self, OpenAI};
use rig::qdrant::QdrantVectorStore;
use rig::vector_store::InsertDocuments;
const COLLECTION_NAME: &str = "rig-collection";
#[derive(Embed, Serialize, Deserialize, Debug)]
struct Word {
id: String,
#[embed]
definition: String,
}
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let client = Qdrant::from_url("http://localhost:6334").build()?;
if !client.collection_exists(COLLECTION_NAME).await? {
client
.create_collection(
CreateCollectionBuilder::new(COLLECTION_NAME)
.vectors_config(VectorParamsBuilder::new(1536, Distance::Cosine)),
)
.await?;
}
let model = OpenAI::from_env()?.embedding(openai::TEXT_EMBEDDING_3_SMALL, None);
let documents = EmbeddingsBuilder::new(model.clone())
.document(Word {
id: "doc0".to_string(),
definition: "A flurbo is a green alien that lives on cold planets.".to_string(),
})?
.document(Word {
id: "doc1".to_string(),
definition: "A linglingdong is a term used to describe humans.".to_string(),
})?
.build()
.await?;
// The store queries the collection named in these parameters.
let query_params = QueryPointsBuilder::new(COLLECTION_NAME).with_payload(true);
let vector_store = QdrantVectorStore::new(client, model, query_params.build());
vector_store.insert_documents(documents).await?;
let req = VectorSearchRequest::builder()
.query("What is a linglingdong?")
.samples(1)
.build();
for result in vector_store.top_n::<Word>(req).await? {
println!("{:.3} {} {}", result.score, result.id, result.document.definition);
}
Ok(())
}
  • QdrantVectorStore::new(client, model, query_params) takes a Qdrant client, the embedding model, and a QueryPoints built with QueryPointsBuilder. The query parameters name the collection; each search reuses them and sets the query vector, limit, threshold and filter. Use .with_payload(true) so the stored documents come back with each hit.
  • insert_documents upserts one point per embedding, with the serialized document as its payload. Point ids are fresh UUIDs, so filter on your own payload fields (such as id below) to find a document.
  • top_n::<T>(req) embeds the query, runs the search, and deserializes each payload into T. The request’s threshold becomes Qdrant’s score threshold.
  • client() returns the underlying Qdrant client for anything else.

Qdrant filters run on the server. Build the request with QdrantFilter as its filter type:

use rig::qdrant::QdrantFilter;
use rig::vector_store::request::SearchFilter;
let req = VectorSearchRequest::<QdrantFilter>::builder()
.query("What is a linglingdong?")
.samples(1)
.filter(QdrantFilter::eq("id", json!("doc1")))
.build();

Beyond eq, gt, lt, and and or, QdrantFilter has not, exists, is_null, is_empty and range conditions (range_inclusive, range_exclusive, …).