Skip to main content

qdrant_client/
lib.rs

1//! The [Qdrant](https://qdrant.tech/) - High-Performance Vector Search at Scale - client for Rust.
2//!
3//! This crate connects to your Qdrant server over gRPC and provides an easy to use API interface
4//! for it.
5//!
6//! # Connect
7//!
8//! First you'll need to [set up](Qdrant#set-up) a [`Qdrant`] client, used to connect to a Qdrant
9//! instance:
10//!
11//! ```no_run
12//! use qdrant_client::Qdrant;
13//!# use qdrant_client::QdrantError;
14//!
15//!# fn establish_connection(url: &str) -> Result<Qdrant, QdrantError> {
16//! let client = Qdrant::from_url("http://localhost:6334")
17//!     .api_key(std::env::var("QDRANT_API_KEY"))
18//!     .build()?;
19//!# Ok(client)
20//!# }
21//! ```
22//!
23//! # Create collection
24//!
25//! Qdrant works with [Collections ⧉ ](https://qdrant.tech/documentation/concepts/collections/) of
26//! [Points ⧉ ](https://qdrant.tech/documentation/concepts/points/). To add vector data, you first
27//! [create a collection](Qdrant::create_collection):
28//!
29//! ```no_run
30//!# use qdrant_client::{Qdrant, QdrantError};
31//! use qdrant_client::qdrant::{CreateCollectionBuilder, Distance, VectorParamsBuilder};
32//!
33//!# async fn create_collection(client: &Qdrant)
34//!# -> Result<(), QdrantError> {
35//! let response = client
36//!     .create_collection(
37//!         CreateCollectionBuilder::new("my_collection")
38//!             .vectors_config(VectorParamsBuilder::new(512, Distance::Cosine)),
39//!     )
40//!     .await?;
41//!# Ok(())
42//!# }
43//! ```
44//!
45//! The most interesting parts are the two arguments of
46//! [`VectorParamsBuilder::new`](qdrant::VectorParamsBuilder::new). The first one (`512`) is the
47//! length of vectors to store and the second one ([`Distance::Cosine`](qdrant::Distance::Cosine))
48//! is the Distance, which is the [`Distance`](qdrant::Distance) measure to gauge similarity for
49//! the nearest neighbors search.
50//!
51//! Documentation: <https://qdrant.tech/documentation/concepts/collections/#create-a-collection>
52//!
53//! # Upsert points
54//!
55//! Now we have a collection, we can insert (or rather upsert) points.
56//! Points have an id, one or more vectors and a payload.
57//! We can usually do that in bulk, but for this example, we'll add a
58//! single point:
59//!
60//! ```no_run
61//!# use qdrant_client::{Qdrant, QdrantError};
62//! use qdrant_client::qdrant::{PointStruct, UpsertPointsBuilder};
63//!
64//!# async fn do_upsert(client: &Qdrant)
65//!# -> Result<(), QdrantError> {
66//! let points = vec![
67//!     PointStruct::new(
68//!         42,                 // Unique point ID
69//!         vec![0.0_f32; 512], // Vector to upsert
70//!         // Attached payload
71//!         [
72//!             ("great", true.into()),
73//!             ("level", 9000.into()),
74//!             ("text", "Hi Qdrant!".into()),
75//!             ("list", vec![1.234f32, 0.815].into()),
76//!         ],
77//!     ),
78//! ];
79//!
80//! let response = client
81//!     .upsert_points(UpsertPointsBuilder::new("my_collection", points))
82//!     .await?;
83//!# Ok(())
84//!# }
85//! ```
86//!
87//! Documentation: <https://qdrant.tech/documentation/concepts/points/#upload-points>
88//!
89//! # Query (search)
90//!
91//! Finally, we can retrieve points in various ways, the common one being a plain similarity
92//! search:
93//!
94//! ```no_run
95//!# use qdrant_client::{Qdrant, QdrantError};
96//! use qdrant_client::qdrant::QueryPointsBuilder;
97//!
98//!# async fn query(client: &Qdrant)
99//!# -> Result<(), QdrantError> {
100//! let query_request = QueryPointsBuilder::new("my_collection") // Collection name
101//!     .query(vec![0.0_f32; 512])                               // Query vector
102//!     .limit(4)                                                // Search limit, number of results to return
103//!     .with_payload(true);                                     // Include full payload in the result
104//!
105//! let response = client.query(query_request).await?;
106//!# Ok(())
107//!# }
108//! ```
109//!
110//! The parameter for [`QueryPointsBuilder::new()`](qdrant::QueryPointsBuilder::new) is pretty
111//! straightforward: the name of the collection to query in. It is combined with other
112//! [functions](qdrant::QueryPointsBuilder#implementations) to further specialize your query to
113//! cover all query flavors.
114//!
115//! In this example [`query(...)`](qdrant::QueryPointsBuilder::query) is used to enable vector
116//! similarity search on the given vector. [`limit(4)`](qdrant::QueryPointsBuilder::limit)
117//! specifies we only want up to 4 top-k results. And
118//! [`with_payload(true)`](qdrant::QueryPointsBuilder::with_payload) tells Qdrant to also return
119//! the (full) payload data for each point. [`filter()`](qdrant::QueryPointsBuilder::filter) is
120//! also commonly used to apply payload based filtering. See the [`Filter`](qdrant::Filter)
121//! documentation for details.
122//!
123//! Documentation: <https://qdrant.tech/documentation/concepts/search/#query-api>
124
125#![doc(html_logo_url = "https://qdrant.tech/favicon/android-chrome-192x192.png")]
126#![doc(issue_tracker_base_url = "https://github.com/qdrant/rust-client/issues/")]
127
128// Generated Qdrant API types
129/// API types
130#[allow(deprecated, clippy::all)]
131#[rustfmt::skip]
132pub mod qdrant;
133
134// Internal modules
135mod auth;
136mod builder_ext;
137mod builder_types;
138mod builders;
139mod channel_pool;
140mod expressions;
141mod filters;
142mod grpc_conversions;
143mod grpc_macros;
144mod manual_builder;
145mod payload;
146mod qdrant_client;
147#[cfg(feature = "serde")]
148mod serde_impl;
149/// Client for Qdrant Serverless (**in development — do not use yet**)
150pub mod serverless;
151
152#[cfg(feature = "serde")]
153pub mod serde_deser;
154
155// Re-exports
156pub use crate::payload::Payload;
157pub use crate::qdrant_client::error::QdrantError;
158pub use crate::qdrant_client::{Qdrant, QdrantBuilder};
159
160/// Client configuration
161pub mod config {
162    pub use crate::qdrant_client::config::{
163        AsOptionApiKey, AsTimeout, CompressionEncoding, QdrantConfig,
164    };
165}
166
167#[cfg(test)]
168mod tests {
169    use std::collections::HashMap;
170
171    use crate::builders::CreateCollectionBuilder;
172    use crate::payload::Payload;
173    use crate::qdrant::value::Kind::*;
174    use crate::qdrant::{
175        Condition, CreateFieldIndexCollection, DeletePayloadPointsBuilder, DeletePointsBuilder,
176        Distance, FieldType, Filter, GetPointsBuilder, ListValue, PointStruct, SearchPointsBuilder,
177        SetPayloadPointsBuilder, SnapshotDownloadBuilder, Struct, UpsertPointsBuilder, Value,
178        VectorParamsBuilder,
179    };
180    use crate::Qdrant;
181
182    #[test]
183    fn display() {
184        let value = Value {
185            kind: Some(StructValue(Struct {
186                fields: [
187                    ("text", StringValue("Hi Qdrant!".into())),
188                    ("int", IntegerValue(42)),
189                    ("float", DoubleValue(1.23)),
190                    (
191                        "list",
192                        ListValue(ListValue {
193                            values: vec![Value {
194                                kind: Some(NullValue(0)),
195                            }],
196                        }),
197                    ),
198                    (
199                        "struct",
200                        StructValue(Struct {
201                            fields: [(
202                                "bool".into(),
203                                Value {
204                                    kind: Some(BoolValue(true)),
205                                },
206                            )]
207                            .into(),
208                        }),
209                    ),
210                ]
211                .into_iter()
212                .map(|(k, v)| (k.into(), Value { kind: Some(v) }))
213                .collect(),
214            })),
215        };
216        let text = format!("{value}");
217        assert!([
218            "\"float\":1.23",
219            "\"list\":[null]",
220            "\"struct\":{\"bool\":true}",
221            "\"int\":42",
222            "\"text\":\"Hi Qdrant!\""
223        ]
224        .into_iter()
225        .all(|item| text.contains(item)));
226    }
227
228    #[tokio::test]
229    async fn test_qdrant_queries() -> anyhow::Result<()> {
230        let client = Qdrant::from_url("http://localhost:6334")
231            .timeout(10u64) // larger timeout to account for the slow snapshot creation
232            .build()?;
233
234        let health = client.health_check().await?;
235        println!("{health:?}");
236
237        let collections_list = client.list_collections().await?;
238        println!("{collections_list:?}");
239
240        let collection_name = "test_qdrant_queries";
241        client.delete_collection(collection_name).await?;
242
243        client
244            .create_collection(
245                CreateCollectionBuilder::new(collection_name)
246                    .vectors_config(VectorParamsBuilder::new(10, Distance::Cosine)),
247            )
248            .await?;
249
250        let exists = client.collection_exists(collection_name).await?;
251        assert!(exists);
252
253        let collection_info = client.collection_info(collection_name).await?;
254        println!("{collection_info:#?}");
255
256        let mut sub_payload = Payload::new();
257        sub_payload.insert("foo", "Not bar");
258
259        let payload: Payload = vec![
260            ("foo", "Bar".into()),
261            ("bar", 12.into()),
262            ("sub_payload", sub_payload.into()),
263        ]
264        .into_iter()
265        .collect::<HashMap<_, Value>>()
266        .into();
267
268        let points = vec![PointStruct::new(0, vec![12.; 10], payload)];
269        client
270            .upsert_points(UpsertPointsBuilder::new(collection_name, points).wait(true))
271            .await?;
272
273        let mut search_points =
274            SearchPointsBuilder::new(collection_name, vec![11.; 10], 10).build();
275
276        // Keyword filter result
277        search_points.filter = Some(Filter::all([Condition::matches("foo", "Bar".to_string())]));
278        let search_result = client.search_points(search_points.clone()).await?;
279        eprintln!("search_result = {search_result:#?}");
280        assert!(!search_result.result.is_empty());
281
282        // Existing implementations full text search filter result (`Condition::matches`)
283        search_points.filter = Some(Filter::all([Condition::matches(
284            "sub_payload.foo",
285            "Not ".to_string(),
286        )]));
287        let search_result = client.search_points(search_points.clone()).await?;
288        eprintln!("search_result = {search_result:#?}");
289        assert!(!search_result.result.is_empty());
290
291        // Full text search filter result (`Condition::matches_text`)
292        search_points.filter = Some(Filter::all([Condition::matches_text(
293            "sub_payload.foo",
294            "Not",
295        )]));
296        let search_result = client.search_points(search_points).await?;
297        eprintln!("search_result = {search_result:#?}");
298        assert!(!search_result.result.is_empty());
299
300        // Override payload of the existing point
301        let new_payload: Payload = vec![("foo", "BAZ".into())]
302            .into_iter()
303            .collect::<HashMap<_, Value>>()
304            .into();
305
306        let payload_result = client
307            .set_payload(
308                SetPayloadPointsBuilder::new(collection_name, new_payload).points_selector([0]),
309            )
310            .await?;
311        eprintln!("payload_result = {payload_result:#?}");
312
313        // Delete some payload fields
314        client
315            .delete_payload(
316                DeletePayloadPointsBuilder::new(collection_name, ["sub_payload".into()])
317                    .points_selector([0]),
318            )
319            .await?;
320
321        let get_points_result = client
322            .get_points(
323                GetPointsBuilder::new(collection_name, [0.into()])
324                    .with_vectors(true)
325                    .with_payload(true),
326            )
327            .await?;
328        eprintln!("get_points_result = {get_points_result:#?}");
329        assert_eq!(get_points_result.result.len(), 1);
330        let point = get_points_result.result[0].clone();
331        assert!(point.payload.contains_key("foo"));
332        assert!(!point.payload.contains_key("sub_payload"));
333
334        let delete_points_result = client
335            .delete_points(
336                DeletePointsBuilder::new(collection_name)
337                    .points([0])
338                    .wait(true),
339            )
340            .await?;
341        eprintln!("delete_points_result = {delete_points_result:#?}");
342
343        // Access raw point api with client
344        client
345            .with_points_client(|mut client| async move {
346                client
347                    .create_field_index(CreateFieldIndexCollection {
348                        collection_name: collection_name.to_string(),
349                        wait: None,
350                        field_name: "foo".to_string(),
351                        field_type: Some(FieldType::Keyword as i32),
352                        field_index_params: None,
353                        ordering: None,
354                        timeout: None,
355                    })
356                    .await
357            })
358            .await?;
359
360        // slow operation
361        let snapshot_result = client.create_snapshot(collection_name).await?;
362        eprintln!("snapshot_result = {snapshot_result:#?}");
363
364        #[cfg(feature = "download_snapshots")]
365        client
366            .download_snapshot(SnapshotDownloadBuilder::new("test.tar", collection_name))
367            .await?;
368
369        Ok(())
370    }
371}