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//! # 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::SearchPointsBuilder;
97//!
98//!# async fn search(client: &Qdrant)
99//!# -> Result<(), QdrantError> {
100//! let search_request = SearchPointsBuilder::new(
101//!     "my_collection",    // Collection name
102//!     vec![0.0_f32; 512], // Search vector
103//!     4,                  // Search limit, number of results to return
104//! ).with_payload(true);
105//!
106//! let response = client.search_points(search_request).await?;
107//!# Ok(())
108//!# }
109//! ```
110//!
111//! The parameter for [`SearchPointsBuilder::new()`](qdrant::SearchPointsBuilder::new) constructor
112//! are pretty straightforward: name of the collection, the vector and how many top-k results to
113//! return. The [`with_payload(true)`](qdrant::SearchPointsBuilder::with_payload) call tells qdrant
114//! to also return the (full) payload data for each point. You can also add a
115//! [`filter()`](qdrant::SearchPointsBuilder::filter) call to the
116//! [`SearchPointsBuilder`](qdrant::SearchPointsBuilder) to filter the result. See the
117//! [`Filter`](qdrant::Filter) documentation for details.
118//!
119//! Documentation: <https://qdrant.tech/documentation/concepts/search/>
120
121#![doc(html_logo_url = "https://qdrant.tech/favicon/android-chrome-192x192.png")]
122#![doc(issue_tracker_base_url = "https://github.com/qdrant/rust-client/issues/")]
123
124// Generated Qdrant API types
125/// API types
126#[allow(deprecated, clippy::all)]
127#[rustfmt::skip]
128pub mod qdrant;
129
130// Internal modules
131mod auth;
132mod builder_ext;
133mod builder_types;
134mod builders;
135mod channel_pool;
136mod expressions;
137mod filters;
138mod grpc_conversions;
139mod grpc_macros;
140mod manual_builder;
141mod payload;
142mod qdrant_client;
143// Deprecated modules
144/// Deprecated Qdrant client
145#[deprecated(
146    since = "1.10.0",
147    note = "use new client at `qdrant_client::Qdrant` instead"
148)]
149#[doc(hidden)]
150pub mod client;
151/// Deprecated error type
152#[deprecated(
153    since = "1.10.0",
154    note = "use new error type at `qdrant_client::Error` instead"
155)]
156#[doc(hidden)]
157pub mod error;
158/// Deprecated prelude
159#[deprecated(since = "1.10.0", note = "use types directly")]
160#[doc(hidden)]
161pub mod prelude;
162/// Deprecated serde helper
163#[cfg(feature = "serde")]
164#[deprecated(since = "1.10.0", note = "use `Payload::try_from` instead")]
165#[doc(hidden)]
166pub mod serde;
167
168#[cfg(feature = "serde")]
169pub mod serde_deser;
170
171// Re-exports
172pub use crate::payload::Payload;
173pub use crate::qdrant_client::error::QdrantError;
174pub use crate::qdrant_client::{Qdrant, QdrantBuilder};
175
176/// Client configuration
177pub mod config {
178    pub use crate::qdrant_client::config::{
179        AsOptionApiKey, AsTimeout, CompressionEncoding, QdrantConfig,
180    };
181}
182
183#[cfg(test)]
184mod tests {
185    use std::collections::HashMap;
186
187    use crate::builders::CreateCollectionBuilder;
188    use crate::payload::Payload;
189    use crate::qdrant::value::Kind::*;
190    use crate::qdrant::{
191        Condition, CreateFieldIndexCollection, DeletePayloadPointsBuilder, DeletePointsBuilder,
192        Distance, FieldType, Filter, GetPointsBuilder, ListValue, PointStruct, SearchPointsBuilder,
193        SetPayloadPointsBuilder, SnapshotDownloadBuilder, Struct, UpsertPointsBuilder, Value,
194        VectorParamsBuilder,
195    };
196    use crate::Qdrant;
197
198    #[test]
199    fn display() {
200        let value = Value {
201            kind: Some(StructValue(Struct {
202                fields: [
203                    ("text", StringValue("Hi Qdrant!".into())),
204                    ("int", IntegerValue(42)),
205                    ("float", DoubleValue(1.23)),
206                    (
207                        "list",
208                        ListValue(ListValue {
209                            values: vec![Value {
210                                kind: Some(NullValue(0)),
211                            }],
212                        }),
213                    ),
214                    (
215                        "struct",
216                        StructValue(Struct {
217                            fields: [(
218                                "bool".into(),
219                                Value {
220                                    kind: Some(BoolValue(true)),
221                                },
222                            )]
223                            .into(),
224                        }),
225                    ),
226                ]
227                .into_iter()
228                .map(|(k, v)| (k.into(), Value { kind: Some(v) }))
229                .collect(),
230            })),
231        };
232        let text = format!("{value}");
233        assert!([
234            "\"float\":1.23",
235            "\"list\":[null]",
236            "\"struct\":{\"bool\":true}",
237            "\"int\":42",
238            "\"text\":\"Hi Qdrant!\""
239        ]
240        .into_iter()
241        .all(|item| text.contains(item)));
242    }
243
244    #[tokio::test]
245    async fn test_qdrant_queries() -> anyhow::Result<()> {
246        let client = Qdrant::from_url("http://localhost:6334")
247            .timeout(10u64) // larger timeout to account for the slow snapshot creation
248            .build()?;
249
250        let health = client.health_check().await?;
251        println!("{health:?}");
252
253        let collections_list = client.list_collections().await?;
254        println!("{collections_list:?}");
255
256        let collection_name = "test_qdrant_queries";
257        client.delete_collection(collection_name).await?;
258
259        client
260            .create_collection(
261                CreateCollectionBuilder::new(collection_name)
262                    .vectors_config(VectorParamsBuilder::new(10, Distance::Cosine)),
263            )
264            .await?;
265
266        let exists = client.collection_exists(collection_name).await?;
267        assert!(exists);
268
269        let collection_info = client.collection_info(collection_name).await?;
270        println!("{collection_info:#?}");
271
272        let mut sub_payload = Payload::new();
273        sub_payload.insert("foo", "Not bar");
274
275        let payload: Payload = vec![
276            ("foo", "Bar".into()),
277            ("bar", 12.into()),
278            ("sub_payload", sub_payload.into()),
279        ]
280        .into_iter()
281        .collect::<HashMap<_, Value>>()
282        .into();
283
284        let points = vec![PointStruct::new(0, vec![12.; 10], payload)];
285        client
286            .upsert_points(UpsertPointsBuilder::new(collection_name, points).wait(true))
287            .await?;
288
289        let mut search_points =
290            SearchPointsBuilder::new(collection_name, vec![11.; 10], 10).build();
291
292        // Keyword filter result
293        search_points.filter = Some(Filter::all([Condition::matches("foo", "Bar".to_string())]));
294        let search_result = client.search_points(search_points.clone()).await?;
295        eprintln!("search_result = {search_result:#?}");
296        assert!(!search_result.result.is_empty());
297
298        // Existing implementations full text search filter result (`Condition::matches`)
299        search_points.filter = Some(Filter::all([Condition::matches(
300            "sub_payload.foo",
301            "Not ".to_string(),
302        )]));
303        let search_result = client.search_points(search_points.clone()).await?;
304        eprintln!("search_result = {search_result:#?}");
305        assert!(!search_result.result.is_empty());
306
307        // Full text search filter result (`Condition::matches_text`)
308        search_points.filter = Some(Filter::all([Condition::matches_text(
309            "sub_payload.foo",
310            "Not",
311        )]));
312        let search_result = client.search_points(search_points).await?;
313        eprintln!("search_result = {search_result:#?}");
314        assert!(!search_result.result.is_empty());
315
316        // Override payload of the existing point
317        let new_payload: Payload = vec![("foo", "BAZ".into())]
318            .into_iter()
319            .collect::<HashMap<_, Value>>()
320            .into();
321
322        let payload_result = client
323            .set_payload(
324                SetPayloadPointsBuilder::new(collection_name, new_payload).points_selector([0]),
325            )
326            .await?;
327        eprintln!("payload_result = {payload_result:#?}");
328
329        // Delete some payload fields
330        client
331            .delete_payload(
332                DeletePayloadPointsBuilder::new(collection_name, ["sub_payload".into()])
333                    .points_selector([0]),
334            )
335            .await?;
336
337        let get_points_result = client
338            .get_points(
339                GetPointsBuilder::new(collection_name, [0.into()])
340                    .with_vectors(true)
341                    .with_payload(true),
342            )
343            .await?;
344        eprintln!("get_points_result = {get_points_result:#?}");
345        assert_eq!(get_points_result.result.len(), 1);
346        let point = get_points_result.result[0].clone();
347        assert!(point.payload.contains_key("foo"));
348        assert!(!point.payload.contains_key("sub_payload"));
349
350        let delete_points_result = client
351            .delete_points(
352                DeletePointsBuilder::new(collection_name)
353                    .points([0])
354                    .wait(true),
355            )
356            .await?;
357        eprintln!("delete_points_result = {delete_points_result:#?}");
358
359        // Access raw point api with client
360        client
361            .with_points_client(|mut client| async move {
362                client
363                    .create_field_index(CreateFieldIndexCollection {
364                        collection_name: collection_name.to_string(),
365                        wait: None,
366                        field_name: "foo".to_string(),
367                        field_type: Some(FieldType::Keyword as i32),
368                        field_index_params: None,
369                        ordering: None,
370                    })
371                    .await
372            })
373            .await?;
374
375        // slow operation
376        let snapshot_result = client.create_snapshot(collection_name).await?;
377        eprintln!("snapshot_result = {snapshot_result:#?}");
378
379        #[cfg(feature = "download_snapshots")]
380        client
381            .download_snapshot(SnapshotDownloadBuilder::new("test.tar", collection_name))
382            .await?;
383
384        Ok(())
385    }
386}