Installation
Add to yourCargo.toml:
[dependencies]
qdrant-client = "1.11"
tokio = { version = "1", features = ["full"] }
Quick Start
use qdrant_client::{
client::QdrantClient,
qdrant::{
vectors_config::Config, CreateCollection, Distance, PointStruct,
SearchPoints, UpsertPoints, VectorParams, VectorsConfig,
},
};
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Connect to Qdrant
let client = QdrantClient::from_url("http://localhost:6334")
.build()
.await?;
// Create collection
client
.create_collection(&CreateCollection {
collection_name: "my_collection".into(),
vectors_config: Some(VectorsConfig {
config: Some(Config::Params(VectorParams {
size: 384,
distance: Distance::Cosine.into(),
..Default::default()
})),
}),
..Default::default()
})
.await?;
// Upsert points
client
.upsert_points_blocking(
"my_collection",
vec![
PointStruct::new(
1,
vec![0.05, 0.61, 0.76, 0.74],
json!({"city": "Berlin", "country": "Germany"}).try_into()?,
),
PointStruct::new(
2,
vec![0.19, 0.81, 0.75, 0.11],
json!({"city": "London", "country": "UK"}).try_into()?,
),
],
None,
)
.await?;
// Search
let search_result = client
.search_points(&SearchPoints {
collection_name: "my_collection".into(),
vector: vec![0.2, 0.1, 0.9, 0.7],
limit: 3,
with_payload: Some(true.into()),
..Default::default()
})
.await?;
for point in search_result.result {
println!("ID: {:?}, Score: {}", point.id, point.score);
println!("Payload: {:?}", point.payload);
}
Ok(())
}
Repository
GitHub Repository
Official Rust client:
qdrant/rust-clientClient Initialization
Basic Connection
use qdrant_client::client::QdrantClient;
// Connect via gRPC (default)
let client = QdrantClient::from_url("http://localhost:6334")
.build()
.await?;
// Connect with API key
let client = QdrantClient::from_url("https://your-cluster.cloud.qdrant.io")
.api_key("your-api-key")
.build()
.await?;
With Custom Configuration
use qdrant_client::client::{QdrantClient, QdrantClientConfig};
use std::time::Duration;
let config = QdrantClientConfig::from_url("http://localhost:6334")
.timeout(Duration::from_secs(30))
.api_key(Some("your-api-key".to_string()))
.compression(Some(tonic::codec::CompressionEncoding::Gzip));
let client = QdrantClient::new(Some(config)).await?;
Collection Management
Create Collection
use qdrant_client::qdrant::{
vectors_config::Config, CreateCollection, Distance, HnswConfigDiff,
VectorParams, VectorsConfig,
};
client
.create_collection(&CreateCollection {
collection_name: "my_collection".into(),
vectors_config: Some(VectorsConfig {
config: Some(Config::Params(VectorParams {
size: 768,
distance: Distance::Cosine.into(),
hnsw_config: Some(HnswConfigDiff {
m: Some(16),
ef_construct: Some(100),
full_scan_threshold: Some(10000),
..Default::default()
}),
on_disk: Some(false),
..Default::default()
})),
}),
replication_factor: Some(2),
write_consistency_factor: Some(1),
..Default::default()
})
.await?;
Create with Multiple Vectors
use qdrant_client::qdrant::{
vectors_config::Config, CreateCollection, Distance, VectorParams,
VectorParamsMap, VectorsConfig,
};
use std::collections::HashMap;
let mut vectors_map = HashMap::new();
vectors_map.insert(
"text".to_string(),
VectorParams {
size: 768,
distance: Distance::Cosine.into(),
..Default::default()
},
);
vectors_map.insert(
"image".to_string(),
VectorParams {
size: 512,
distance: Distance::Euclid.into(),
..Default::default()
},
);
client
.create_collection(&CreateCollection {
collection_name: "multi_vector".into(),
vectors_config: Some(VectorsConfig {
config: Some(Config::ParamsMap(VectorParamsMap {
map: vectors_map,
})),
}),
..Default::default()
})
.await?;
Get Collection Info
let info = client.collection_info("my_collection").await?;
println!("Status: {:?}", info.status);
println!("Vectors count: {:?}", info.vectors_count);
println!("Points count: {:?}", info.points_count);
List Collections
let collections = client.list_collections().await?;
for collection in collections.collections {
println!("Collection: {}", collection.name);
}
Delete Collection
client.delete_collection("my_collection").await?;
Point Operations
Upsert Points
use qdrant_client::qdrant::{PointStruct, UpsertPoints};
use serde_json::json;
let points = vec![
PointStruct::new(
1,
vec![0.05, 0.61, 0.76, 0.74],
json!({
"title": "Document 1",
"category": "technology",
"views": 1500
})
.try_into()?,
),
PointStruct::new(
2,
vec![0.19, 0.81, 0.75, 0.11],
json!({"title": "Document 2"}).try_into()?,
),
];
client
.upsert_points_blocking("my_collection", points, None)
.await?;
Upsert with UUID
use qdrant_client::qdrant::{point_id::PointIdOptions, PointId, PointStruct};
use uuid::Uuid;
let id = Uuid::new_v4();
let point = PointStruct::new(
PointId {
point_id_options: Some(PointIdOptions::Uuid(id.to_string())),
},
vec![0.1, 0.2, 0.3],
json!({"key": "value"}).try_into()?,
);
client
.upsert_points_blocking("my_collection", vec![point], None)
.await?;
Batch Upsert
use qdrant_client::qdrant::PointStruct;
use serde_json::json;
let batch_size = 100;
let total_points = 10000;
for batch_start in (0..total_points).step_by(batch_size) {
let points: Vec<PointStruct> = (batch_start..batch_start + batch_size)
.map(|i| {
PointStruct::new(
i as u64,
vec![0.1; 384], // Random vectors in production
json!({"index": i}).try_into().unwrap(),
)
})
.collect();
client
.upsert_points("my_collection", points, None)
.await?;
}
Get Points
use qdrant_client::qdrant::{GetPoints, PointId};
let points = client
.get_points(
"my_collection",
&[1u64.into(), 2u64.into(), 3u64.into()],
Some(true), // with_payload
Some(true), // with_vectors
None,
)
.await?;
for point in points.result {
println!("ID: {:?}", point.id);
println!("Vector: {:?}", point.vectors);
println!("Payload: {:?}", point.payload);
}
Delete Points
use qdrant_client::qdrant::{
points_selector::PointsSelectorOneOf, Condition, Filter, PointsIdsList,
PointsSelector,
};
// Delete by IDs
client
.delete_points(
"my_collection",
&[1u64.into(), 2u64.into(), 3u64.into()],
None,
)
.await?;
// Delete by filter
client
.delete_points_blocking(
"my_collection",
&PointsSelector {
points_selector_one_of: Some(PointsSelectorOneOf::Filter(Filter {
must: vec![Condition::matches(
"category",
"outdated".to_string(),
)],
..Default::default()
})),
},
None,
)
.await?;
Search Operations
Basic Search
use qdrant_client::qdrant::SearchPoints;
let search_result = client
.search_points(&SearchPoints {
collection_name: "my_collection".into(),
vector: vec![0.2, 0.1, 0.9, 0.7],
limit: 10,
with_payload: Some(true.into()),
with_vectors: Some(false.into()),
..Default::default()
})
.await?;
for point in search_result.result {
println!("ID: {:?}, Score: {}", point.id, point.score);
println!("Payload: {:?}", point.payload);
}
Search with Filtering
use qdrant_client::qdrant::{Condition, Filter, Range, SearchPoints};
let search_result = client
.search_points(&SearchPoints {
collection_name: "my_collection".into(),
vector: vec![0.2, 0.1, 0.9, 0.7],
filter: Some(Filter {
must: vec![
Condition::matches("category", "technology".to_string()),
Condition::range("views", Range {
gte: Some(1000.0),
lt: Some(10000.0),
..Default::default()
}),
],
..Default::default()
}),
limit: 5,
score_threshold: Some(0.7),
..Default::default()
})
.await?;
Search with Parameters
use qdrant_client::qdrant::{SearchParams, SearchPoints, QuantizationSearchParams};
let search_result = client
.search_points(&SearchPoints {
collection_name: "my_collection".into(),
vector: vec![0.2, 0.1, 0.9, 0.7],
limit: 20,
params: Some(SearchParams {
hnsw_ef: Some(128),
exact: Some(false),
quantization: Some(QuantizationSearchParams {
ignore: Some(false),
rescore: Some(true),
oversampling: Some(2.0),
}),
..Default::default()
}),
..Default::default()
})
.await?;
Batch Search
use qdrant_client::qdrant::{SearchBatchPoints, SearchPoints};
let search_result = client
.search_batch_points(&SearchBatchPoints {
collection_name: "my_collection".into(),
search_points: vec![
SearchPoints {
vector: vec![0.2, 0.1, 0.9],
limit: 5,
..Default::default()
},
SearchPoints {
vector: vec![0.5, 0.3, 0.2],
limit: 10,
..Default::default()
},
],
..Default::default()
})
.await?;
for (i, batch) in search_result.result.iter().enumerate() {
println!("Results for query {}:", i);
for point in &batch.result {
println!(" ID: {:?}, Score: {}", point.id, point.score);
}
}
Recommendations
use qdrant_client::qdrant::RecommendPoints;
let recommend_result = client
.recommend(&RecommendPoints {
collection_name: "my_collection".into(),
positive: vec![1u64.into(), 2u64.into(), 3u64.into()],
negative: vec![10u64.into()],
limit: 10,
with_payload: Some(true.into()),
..Default::default()
})
.await?;
for point in recommend_result.result {
println!("ID: {:?}, Score: {}", point.id, point.score);
}
Query API (Universal)
use qdrant_client::qdrant::{
query::Query as QueryEnum, vector_input::Variant, PrefetchQuery, Query,
QueryPoints, VectorInput,
};
let query_result = client
.query(&QueryPoints {
collection_name: "my_collection".into(),
query: Some(Query {
variant: Some(QueryEnum::Nearest(VectorInput {
variant: Some(Variant::Dense(vec![0.1, 0.2, 0.3].into())),
})),
}),
limit: Some(10),
with_payload: Some(true.into()),
..Default::default()
})
.await?;
Payload Operations
Set Payload
use qdrant_client::qdrant::{points_selector::PointsSelectorOneOf, PointsIdsList, PointsSelector};
use serde_json::json;
client
.set_payload(
"my_collection",
&[1u64.into(), 2u64.into()],
json!({
"new_field": "value",
"updated_at": "2024-01-01"
})
.try_into()?,
None,
)
.await?;
Overwrite Payload
client
.overwrite_payload(
"my_collection",
&[1u64.into(), 2u64.into()],
json!({"only_field": "value"}).try_into()?,
None,
)
.await?;
Delete Payload Keys
client
.delete_payload(
"my_collection",
&[1u64.into(), 2u64.into()],
vec!["old_field".to_string(), "deprecated".to_string()],
None,
)
.await?;
Clear Payload
client
.clear_payload(
"my_collection",
&[1u64.into(), 2u64.into()],
None,
)
.await?;
Scroll (Pagination)
use qdrant_client::qdrant::{point_id::PointIdOptions, PointId, ScrollPoints};
let mut offset: Option<PointId> = None;
let mut all_points = Vec::new();
loop {
let scroll_result = client
.scroll(&ScrollPoints {
collection_name: "my_collection".into(),
limit: Some(100),
offset: offset.clone(),
with_payload: Some(true.into()),
..Default::default()
})
.await?;
all_points.extend(scroll_result.result);
if let Some(next_offset) = scroll_result.next_page_offset {
offset = Some(next_offset);
} else {
break;
}
}
println!("Total points: {}", all_points.len());
Payload Indexing
use qdrant_client::qdrant::{
payload_index_params::IndexParams, CreateFieldIndexCollection, FieldType,
KeywordIndexParams, PayloadIndexParams, TextIndexParams, TokenizerType,
};
// Create keyword index
client
.create_field_index(
"my_collection",
"category",
FieldType::Keyword,
Some(&PayloadIndexParams {
index_params: Some(IndexParams::KeywordIndexParams(KeywordIndexParams {
..Default::default()
})),
}),
None,
)
.await?;
// Create text index
client
.create_field_index(
"my_collection",
"description",
FieldType::Text,
Some(&PayloadIndexParams {
index_params: Some(IndexParams::TextIndexParams(TextIndexParams {
tokenizer: TokenizerType::Word.into(),
lowercase: Some(true),
min_token_len: Some(2),
max_token_len: Some(20),
..Default::default()
})),
}),
None,
)
.await?;
// Delete index
client
.delete_field_index("my_collection", "category", None)
.await?;
Snapshots
// Create snapshot
let snapshot = client.create_snapshot("my_collection").await?;
println!("Snapshot name: {}", snapshot.name);
// List snapshots
let snapshots = client.list_snapshots("my_collection").await?;
for snapshot in snapshots {
println!("Snapshot: {}", snapshot.name);
}
Count Points
use qdrant_client::qdrant::{Condition, CountPoints, Filter};
// Count all points
let count = client
.count(&CountPoints {
collection_name: "my_collection".into(),
exact: Some(true),
..Default::default()
})
.await?;
println!("Total points: {:?}", count.result.map(|r| r.count));
// Count with filter
let count = client
.count(&CountPoints {
collection_name: "my_collection".into(),
filter: Some(Filter {
must: vec![Condition::matches("category", "technology".to_string())],
..Default::default()
}),
exact: Some(true),
..Default::default()
})
.await?;
Error Handling
use qdrant_client::client::QdrantClient;
use qdrant_client::qdrant::QdrantError;
let client = QdrantClient::from_url("http://localhost:6334")
.build()
.await?;
match client.collection_info("nonexistent").await {
Ok(info) => println!("Collection info: {:?}", info),
Err(e) => {
eprintln!("Error: {}", e);
// Handle specific error types
if e.to_string().contains("Not found") {
eprintln!("Collection not found");
}
}
}
Type Safety
The Rust client provides strong type safety:use qdrant_client::qdrant::{
Distance, PointStruct, ScoredPoint, VectorParams,
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct DocumentPayload {
title: String,
category: String,
views: i64,
tags: Vec<String>,
}
// Type-safe point creation
let payload = DocumentPayload {
title: "Document 1".to_string(),
category: "technology".to_string(),
views: 1500,
tags: vec!["ai".to_string(), "ml".to_string()],
};
let point = PointStruct::new(
1,
vec![0.1, 0.2, 0.3],
serde_json::to_value(payload)?.try_into()?,
);
// Type-safe payload extraction
let search_result = client
.search_points(&SearchPoints {
collection_name: "my_collection".into(),
vector: vec![0.2, 0.1, 0.9],
limit: 10,
with_payload: Some(true.into()),
..Default::default()
})
.await?;
for scored_point in search_result.result {
if let Some(payload) = scored_point.payload.get("title") {
println!("Title: {:?}", payload);
}
}
Async Runtime
The client requires an async runtime (Tokio):#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = QdrantClient::from_url("http://localhost:6334")
.build()
.await?;
// Your async operations here
Ok(())
}
// Or use tokio::spawn for concurrent operations
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = QdrantClient::from_url("http://localhost:6334")
.build()
.await?;
let handle1 = tokio::spawn(async move {
// Operation 1
});
let handle2 = tokio::spawn(async move {
// Operation 2
});
tokio::try_join!(handle1, handle2)?;
Ok(())
}
Best Practices
Performance optimization:
- Use batch operations for bulk inserts
- Leverage zero-copy gRPC for large payloads
- Reuse client connections across requests
- Use async/await for concurrent operations
Common pitfalls:
- Always handle Result types properly
- Use appropriate point ID types (u64 or UUID string)
- Ensure vector dimensions match collection config
- Handle pagination properly with scroll operations
Next Steps
Go Client
Go client documentation
gRPC API
Learn about gRPC protocol