Qdrant & Rust Client

Once you have Docker installed, you can simply run Qdrant with an API key
For testing, just make your own value
sudo docker run -d \
-p 6333:6333 -p 6334:6334 \
-v "$(pwd)/qdrant_storage:/qdrant/storage:z" \
--name qdrant \
--restart unless-stopped \
-e QDRANT__SERVICE__API_KEY=my-secret-key \
qdrant/qdrant
curl http://localhost:6333curl http://localhost:6333/collections \
-H "api-key: my-secret-key"
{"result":{"collections":[]},"status":"ok","time":0.000022774}
without the API key you chose ->
Must provide an API key or an Authorization bearer token
Qdrant Rust Client
cargo add qdrant-client
Create a “collection” and “upsert” some data
use qdrant_client::Qdrant;
use qdrant_client::qdrant::Distance;
use qdrant_client::qdrant::{PointStruct, UpsertPointsBuilder};
use qdrant_client::qdrant::{CreateCollectionBuilder, VectorParamsBuilder};
use qdrant_client::qdrant::SearchPoints;
#[tokio::main]
async fn main()-> Result<(), Box<dyn std::error::Error>> {
// The Rust client uses Qdrant's gRPC interface
let client = Qdrant::from_url("http://localhost:6334")
.api_key("my-secret-key") // π Add your API key
.build()?;
let collections = client.list_collections().await?.collections;
let collection_exists = collections.iter().any(|c| c.name == "test_collection");
if ! collection_exists{
client
.create_collection(
CreateCollectionBuilder::new("test_collection")
.vectors_config(VectorParamsBuilder::new(4, Distance::Dot)),
)
.await?;
}
let points = vec![
PointStruct::new(1, vec![0.05, 0.61, 0.76, 0.74], [("city", "Berlin".into())]),
PointStruct::new(2, vec![0.19, 0.81, 0.75, 0.11], [("city", "London".into())]),
PointStruct::new(3, vec![0.36, 0.55, 0.47, 0.94], [("city", "Moscow".into())]),
// ..truncated
];
let response = client
.upsert_points(UpsertPointsBuilder::new("test_collection", points).wait(true))
.await?;
dbg!(response);
Ok(())
}
[src/main.rs:40:5] response = PointsOperationResponse {
result: Some(
UpdateResult {
operation_id: Some(
2,
),
status: Completed,
},
),
time: 0.007099355,
usage: Some(
Usage {
hardware: None,
inference: None,
},
),
}
Do a vector search
use qdrant_client::Qdrant;
use qdrant_client::qdrant::Distance;
use qdrant_client::qdrant::QueryPointsBuilder;
use qdrant_client::qdrant::{CreateCollectionBuilder, VectorParamsBuilder};
use qdrant_client::qdrant::{PointStruct, UpsertPointsBuilder};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// The Rust client uses Qdrant's gRPC interface
let client = Qdrant::from_url("http://localhost:6334")
.api_key("my-secret-key") // π Add your API key
.build()?;
let search_result = client
.query(QueryPointsBuilder::new("test_collection").query(vec![0.2, 0.2, 0.9, 0.7]))
.await?;
dbg!(search_result);
Ok(())
}
[src/main.rs:33:5] search_result = QueryResponse {
result: [
ScoredPoint {
id: Some(
PointId {
point_id_options: Some(
Num(
1,
),
),
},
),
payload: {},
score: 1.334,
version: 3,
vectors: None,
shard_key: None,
order_value: None,
},
ScoredPoint {
id: Some(
PointId {
point_id_options: Some(
Num(
3,
),
),
},
),
payload: {},
score: 1.263,
version: 3,
vectors: None,
shard_key: None,
order_value: None,
},
ScoredPoint {
id: Some(
PointId {
point_id_options: Some(
Num(
2,
),
),
},
),
payload: {},
score: 0.95199996,
version: 3,
vectors: None,
shard_key: None,
order_value: None,
},
],
time: 0.001076587,
usage: None,
}
Filter search
use qdrant_client::Qdrant;
use qdrant_client::qdrant::{Condition, Filter, QueryPointsBuilder};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// The Rust client uses Qdrant's gRPC interface
let client = Qdrant::from_url("http://localhost:6334")
.api_key("my-secret-key") // π Add your API key
.build()?;
let search_result = client
.query(
QueryPointsBuilder::new("test_collection")
.query(vec![0.2, 0.1, 0.9, 0.7])
.filter(Filter::must([Condition::matches(
"city",
"London".to_string(),
)]))
.with_payload(true),
)
.await?;
dbg!(search_result);
Ok(())
}
[src/main.rs:23:5] search_result = QueryResponse {
result: [
ScoredPoint {
id: Some(
PointId {
point_id_options: Some(
Num(
2,
),
),
},
),
payload: {
"city": Value {
kind: Some(
StringValue(
"London",
),
),
},
},
score: 0.871,
version: 3,
vectors: None,
shard_key: None,
order_value: None,
},
],
time: 0.000704456,
usage: None,
}
Prepare a model to do embeddings with Qdrant (fastembed)
*Each model will have its own dimensions and recommended distance
VectorParamsBuilder::new(384, Distance::Cosine)

cargo add fastembed
Component | Purpose |
---|---|
fastembed::TextEmbedding | Embeds sentences into vectors on-device |
EmbeddingModel::AllMiniLML6V2 | Pretrained model that outputs 384-dim sentence embeddings |
VectorParamsBuilder::new(384, Distance::Cosine) | Configures Qdrant collection to store 384-dim vectors and compare using cosine |
with_show_download_progress(true) | Shows download progress for the embedding model |
let collection_exists = collections.iter().any(|c| c.name == "knowledge_base");
if !collection_exists {
client
.create_collection(
CreateCollectionBuilder::new("knowledge_base")
.vectors_config(VectorParamsBuilder::new(384, Distance::Cosine)),
)
.await?;
}
let model = TextEmbedding::try_new(
InitOptions::new(EmbeddingModel::AllMiniLML6V2).with_show_download_progress(true),
)?;
Full code (so far) :
use qdrant_client::Qdrant;
use qdrant_client::qdrant::Distance;
use qdrant_client::qdrant::{CreateCollectionBuilder, VectorParamsBuilder};
use fastembed::{EmbeddingModel, InitOptions, TextEmbedding};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// The Rust client uses Qdrant's gRPC interface
let client = Qdrant::from_url("http://localhost:6334")
.api_key("my-secret-key") // π Add your API key
.build()?;
let collections = client.list_collections().await?.collections;
dbg!(&collections);
let collection_exists = collections.iter().any(|c| c.name == "knowledge_base");
if !collection_exists {
client
.create_collection(
CreateCollectionBuilder::new("knowledge_base")
.vectors_config(VectorParamsBuilder::new(384, Distance::Cosine)),
)
.await?;
}
let model = TextEmbedding::try_new(
InitOptions::new(EmbeddingModel::AllMiniLML6V2).with_show_download_progress(true),
)?;
Ok(())
}
PointStruct
pub struct PointStruct {
pub id: Option<PointId>,
pub payload: HashMap<String, Value>,
pub vectors: Option<Vectors>,
}
https://docs.rs/qdrant-client/latest/qdrant_client/qdrant/struct.PointStruct.html
Field | Type | Purpose |
---|---|---|
id | Option<PointId> | Unique identifier for the point |
payload | HashMap<String, Value> | Extra searchable/filterable metadata |
vectors | Option<Vectors> | The embedding vector used for similarity search |
The payload
is the human-readable metadata that you attach to a vector β it’s what gives context and semantic meaning to the raw numbers in the vector.
- Vectors are for finding similar things
- Payload is for understanding what those things are
We’ll put all of the “Points” into a Vec and that’s how they get “Upserted”
let points: Vec = /* your points */;
use qdrant_client::qdrant::{PointStruct, UpsertPoints};
...
let upsert = UpsertPoints {
collection_name: collection_name.to_string(),
points,
..Default::default()
};
client.upsert_points(upsert).await?;
...
Embeddings
We still need to runs the embedding model on our chosen text chunks to get some vectors however…
So the workflow looks like this:
- Load your raw texts (e.g., from AG News or any dataset).
- Run the embedding model on each text β get a vector of floats.
- Build
PointStruct
s using those vectors plus any metadata/payload. - Create an
UpsertPoints
struct with your collection name and the vector points. - Call
client.upsert_points(upsert).await?
to upload.
Assuming your texts are short enough to embed as-is (like news headlines or short articles from AG News), you donβt need to chunk them.
Embedding works best on reasonably sized text pieces β typically up to a few hundred tokens. AG News entries are short enough that embedding the whole text at once is perfectly fine.