From 76d171ed09d6076c0e00c888ac5a8317045b4b9f Mon Sep 17 00:00:00 2001 From: generall Date: Fri, 29 Aug 2025 21:57:51 +0200 Subject: [PATCH 1/4] add into_vector interface for VectorOutput --- proto/points.proto | 6 +- src/builders/multi_dense_vector_builder.rs | 11 +++ src/grpc_conversions/mod.rs | 1 + src/grpc_conversions/vectors.rs | 73 +++++++++++++++++++ src/qdrant.rs | 12 ++- tests/protos.rs | 14 ++++ tests/snippet_tests/mod.rs | 1 + .../test_scroll_points_with_vectors.rs | 32 ++++++++ tests/snippets/scroll_points_with_vectors.rs | 22 ++++++ 9 files changed, 167 insertions(+), 5 deletions(-) create mode 100644 src/grpc_conversions/vectors.rs create mode 100644 tests/snippet_tests/test_scroll_points_with_vectors.rs create mode 100644 tests/snippets/scroll_points_with_vectors.rs diff --git a/proto/points.proto b/proto/points.proto index 921e85a9..58db3e2c 100644 --- a/proto/points.proto +++ b/proto/points.proto @@ -80,9 +80,9 @@ message Vector { } message VectorOutput { - repeated float data = 1; // Vector data (flatten for multi vectors), deprecated - optional SparseIndices indices = 2; // Sparse indices for sparse vectors, deprecated - optional uint32 vectors_count = 3; // Number of vectors per multi vector, deprecated + repeated float data = 1 [deprecated=true]; // Vector data (flatten for multi vectors), deprecated + optional SparseIndices indices = 2 [deprecated=true]; // Sparse indices for sparse vectors, deprecated + optional uint32 vectors_count = 3 [deprecated=true]; // Number of vectors per multi vector, deprecated oneof vector { DenseVector dense = 101; // Dense vector SparseVector sparse = 102; // Sparse vector diff --git a/src/builders/multi_dense_vector_builder.rs b/src/builders/multi_dense_vector_builder.rs index 0409727b..a95cde6c 100644 --- a/src/builders/multi_dense_vector_builder.rs +++ b/src/builders/multi_dense_vector_builder.rs @@ -47,6 +47,17 @@ impl From>> for MultiDenseVector { } } +impl From> for MultiDenseVector { + fn from(vectors: Vec<&[f32]>) -> Self { + Self::from( + vectors + .into_iter() + .map(|subvector| DenseVector::from(subvector.to_vec())) + .collect::>(), + ) + } +} + impl From> for MultiDenseVector { fn from(vectors: Vec) -> Self { MultiDenseVectorBuilder::new(vectors).build() diff --git a/src/grpc_conversions/mod.rs b/src/grpc_conversions/mod.rs index 27c3615e..6a21bd3d 100644 --- a/src/grpc_conversions/mod.rs +++ b/src/grpc_conversions/mod.rs @@ -1,5 +1,6 @@ mod extensions; mod primitives; +pub mod vectors; use crate::client::Payload; use crate::qdrant::point_id::PointIdOptions; diff --git a/src/grpc_conversions/vectors.rs b/src/grpc_conversions/vectors.rs new file mode 100644 index 00000000..072d2c91 --- /dev/null +++ b/src/grpc_conversions/vectors.rs @@ -0,0 +1,73 @@ +use crate::qdrant::vector_output::Vector; +use crate::qdrant::vectors_output::VectorsOptions; +use crate::qdrant::{MultiDenseVector, SparseVector, VectorOutput, VectorsOutput}; + +impl VectorOutput { + #[allow(deprecated)] + pub fn into_vector(self) -> Vector { + let VectorOutput { + data, + indices, + vectors_count, + vector, + } = self; + + if let Some(v) = vector { + return v; + } + + if let Some(indices) = indices { + return Vector::Sparse(SparseVector::from((indices.data, data))); + } + + if let Some(vectors_count) = vectors_count { + let vectors: Vec<_> = data + .chunks(data.len() / vectors_count as usize) + .collect::>(); + + return Vector::MultiDense(MultiDenseVector::from(vectors)); + } + + Vector::Dense(crate::qdrant::DenseVector { data }) + } +} + +impl VectorsOutput { + /// Get default (unnamed) vector from VectorsOutput. + /// + /// Return `None` if the default vector is not present. + /// + /// Use `Self::get_vector_by_name` to get named vectors from VectorsOutput. + pub fn get_vector(&self) -> Option { + self.vectors_options + .as_ref() + .and_then(|option| match option { + VectorsOptions::Vector(vector) => Some(vector.clone().into_vector()), + VectorsOptions::Vectors(_) => None, + }) + } + + /// Get vector by name from VectorsOutput. + /// + /// Return `None` if the named vector is not present or is a default (unnamed) vector. + /// + /// Use `Self::get_vector` to get the default (unnamed) vector from VectorsOutput. + pub fn get_vector_by_name(&self, name: &str) -> Option { + self.vectors_options + .as_ref() + .and_then(|option| match option { + VectorsOptions::Vector(vector) => { + if name == "" { + Some(vector.clone().into_vector()) + } else { + None + } + } + VectorsOptions::Vectors(vectors) => vectors + .vectors + .get(name) + .cloned() + .map(|vector| vector.into_vector()), + }) + } +} diff --git a/src/qdrant.rs b/src/qdrant.rs index 52148d4b..dbfe073c 100644 --- a/src/qdrant.rs +++ b/src/qdrant.rs @@ -284,6 +284,8 @@ pub struct OptimizersConfigDiff { #[prost(uint64, optional, tag = "3")] pub default_segment_number: ::core::option::Option, /// + /// Deprecated: + /// /// Do not create segments larger this size (in kilobytes). /// Large segments might require disproportionately long indexation times, /// therefore it makes sense to limit the size of segments. @@ -557,7 +559,7 @@ pub struct CreateCollection { /// How many replicas should apply the operation for us to consider it successful, default = 1 #[prost(uint32, optional, tag = "12")] pub write_consistency_factor: ::core::option::Option, - /// Specify name of the other collection to copy data from + /// Deprecated: specify name of the other collection to copy data from #[prost(string, optional, tag = "13")] pub init_from_collection: ::core::option::Option<::prost::alloc::string::String>, /// Quantization configuration of vector @@ -3167,13 +3169,19 @@ pub mod vector { #[derive(Clone, PartialEq, ::prost::Message)] pub struct VectorOutput { /// Vector data (flatten for multi vectors), deprecated - #[prost(float, repeated, tag = "1")] + #[deprecated] + #[prost(float, repeated, packed = "false", tag = "1")] + ///This field is deprecated since 1.16.0, use `into_vector` method instead pub data: ::prost::alloc::vec::Vec, /// Sparse indices for sparse vectors, deprecated + #[deprecated] #[prost(message, optional, tag = "2")] + ///This field is deprecated since 1.16.0, use `into_vector` method instead pub indices: ::core::option::Option, /// Number of vectors per multi vector, deprecated + #[deprecated] #[prost(uint32, optional, tag = "3")] + ///This field is deprecated since 1.16.0, use `into_vector` method instead pub vectors_count: ::core::option::Option, #[prost(oneof = "vector_output::Vector", tags = "101, 102, 103")] pub vector: ::core::option::Option, diff --git a/tests/protos.rs b/tests/protos.rs index 599eea30..a5d41cfe 100644 --- a/tests/protos.rs +++ b/tests/protos.rs @@ -51,6 +51,8 @@ trait BuilderExt { impl BuilderExt for Builder { fn configure_deprecations(self) -> Self { + // Clear deprecated field for VectorOutput.data + self.field_attribute( "PointsUpdateOperation.operation.delete_deprecated", "#[deprecated(since = \"1.7.0\", note = \"use `DeletePoints` instead\")]", @@ -59,6 +61,18 @@ impl BuilderExt for Builder { "PointsUpdateOperation.operation.clear_payload_deprecated", "#[deprecated(since = \"1.7.0\", note = \"use `ClearPayload` instead\")]", ) + .field_attribute( + "VectorOutput.data", + "#[doc = \"This field is deprecated since 1.16.0, use `into_vector` method instead\"]", + ) + .field_attribute( + "VectorOutput.indices", + "#[doc = \"This field is deprecated since 1.16.0, use `into_vector` method instead\"]", + ) + .field_attribute( + "VectorOutput.vectors_count", + "#[doc = \"This field is deprecated since 1.16.0, use `into_vector` method instead\"]", + ) } } diff --git a/tests/snippet_tests/mod.rs b/tests/snippet_tests/mod.rs index 828222a8..7e57fb59 100644 --- a/tests/snippet_tests/mod.rs +++ b/tests/snippet_tests/mod.rs @@ -36,6 +36,7 @@ mod test_recommend_batch_points; mod test_recommend_point_groups; mod test_recommend_points; mod test_scroll_points; +mod test_scroll_points_with_vectors; mod test_search_batch_points; mod test_search_matrix_offsets; mod test_search_matrix_pairs; diff --git a/tests/snippet_tests/test_scroll_points_with_vectors.rs b/tests/snippet_tests/test_scroll_points_with_vectors.rs new file mode 100644 index 00000000..11c93564 --- /dev/null +++ b/tests/snippet_tests/test_scroll_points_with_vectors.rs @@ -0,0 +1,32 @@ + +#[tokio::test] +async fn test_scroll_points_with_vectors() { + async fn scroll_points_with_vectors() -> Result<(), Box> { + // WARNING: This is a generated test snippet. + // Please, modify the snippet in the `../snippets/scroll_points_with_vectors.rs` file + use qdrant_client::qdrant::{Condition, Filter, ScrollPointsBuilder}; + use qdrant_client::Qdrant; + + let client = Qdrant::from_url("http://localhost:6334").build()?; + + let scroll_response = client + .scroll( + ScrollPointsBuilder::new("{collection_name}") + .filter(Filter::must([Condition::matches( + "color", + "red".to_string(), + )])) + .limit(1) + .with_payload(true) + .with_vectors(true), + ) + .await?; + + for point in scroll_response.result { + let vector = point.vectors.unwrap().get_vector(); + println!("vector: {:?}", vector); + } + Ok(()) + } + let _ = scroll_points_with_vectors().await; +} diff --git a/tests/snippets/scroll_points_with_vectors.rs b/tests/snippets/scroll_points_with_vectors.rs new file mode 100644 index 00000000..405608a2 --- /dev/null +++ b/tests/snippets/scroll_points_with_vectors.rs @@ -0,0 +1,22 @@ +use qdrant_client::qdrant::{Condition, Filter, ScrollPointsBuilder}; +use qdrant_client::Qdrant; + +let client = Qdrant::from_url("http://localhost:6334").build()?; + +let scroll_response = client + .scroll( + ScrollPointsBuilder::new("{collection_name}") + .filter(Filter::must([Condition::matches( + "color", + "red".to_string(), + )])) + .limit(1) + .with_payload(true) + .with_vectors(true), + ) + .await?; + +for point in scroll_response.result { + let vector = point.vectors.unwrap().get_vector(); + println!("vector: {:?}", vector); +} \ No newline at end of file From e204be19c9a91ee533293dda94bc20f476e7f056 Mon Sep 17 00:00:00 2001 From: generall Date: Fri, 29 Aug 2025 22:06:16 +0200 Subject: [PATCH 2/4] fix clippy --- src/grpc_conversions/vectors.rs | 2 +- tests/snippet_tests/test_scroll_points_with_vectors.rs | 2 +- tests/snippets/scroll_points_with_vectors.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/grpc_conversions/vectors.rs b/src/grpc_conversions/vectors.rs index 072d2c91..ddb5ee48 100644 --- a/src/grpc_conversions/vectors.rs +++ b/src/grpc_conversions/vectors.rs @@ -57,7 +57,7 @@ impl VectorsOutput { .as_ref() .and_then(|option| match option { VectorsOptions::Vector(vector) => { - if name == "" { + if name.is_empty() { Some(vector.clone().into_vector()) } else { None diff --git a/tests/snippet_tests/test_scroll_points_with_vectors.rs b/tests/snippet_tests/test_scroll_points_with_vectors.rs index 11c93564..73ba4d0c 100644 --- a/tests/snippet_tests/test_scroll_points_with_vectors.rs +++ b/tests/snippet_tests/test_scroll_points_with_vectors.rs @@ -24,7 +24,7 @@ async fn test_scroll_points_with_vectors() { for point in scroll_response.result { let vector = point.vectors.unwrap().get_vector(); - println!("vector: {:?}", vector); + println!("vector: {vector:?}"); } Ok(()) } diff --git a/tests/snippets/scroll_points_with_vectors.rs b/tests/snippets/scroll_points_with_vectors.rs index 405608a2..a0da2859 100644 --- a/tests/snippets/scroll_points_with_vectors.rs +++ b/tests/snippets/scroll_points_with_vectors.rs @@ -18,5 +18,5 @@ let scroll_response = client for point in scroll_response.result { let vector = point.vectors.unwrap().get_vector(); - println!("vector: {:?}", vector); + println!("vector: {vector:?}"); } \ No newline at end of file From 38b4a873dbe9be4abf19942aedcedbc0e01c3d18 Mon Sep 17 00:00:00 2001 From: timvisee Date: Wed, 12 Nov 2025 14:53:44 +0100 Subject: [PATCH 3/4] Also deprecated vector input fields --- proto/points.proto | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/proto/points.proto b/proto/points.proto index 58db3e2c..73d76741 100644 --- a/proto/points.proto +++ b/proto/points.proto @@ -64,11 +64,10 @@ message InferenceObject { map options = 3; // Model options } -// Legacy vector format, which determines the vector type by the configuration of its fields. message Vector { - repeated float data = 1; // Vector data (flatten for multi vectors), deprecated - optional SparseIndices indices = 2; // Sparse indices for sparse vectors, deprecated - optional uint32 vectors_count = 3; // Number of vectors per multi vector, deprecated + repeated float data = 1 [deprecated=true]; // Vector data (flatten for multi vectors), deprecated + optional SparseIndices indices = 2 [deprecated=true]; // Sparse indices for sparse vectors, deprecated + optional uint32 vectors_count = 3 [deprecated=true]; // Number of vectors per multi vector, deprecated oneof vector { DenseVector dense = 101; // Dense vector SparseVector sparse = 102; // Sparse vector From 317a0cf6c381dec08cba0aea6ea773d9031de397 Mon Sep 17 00:00:00 2001 From: timvisee Date: Wed, 12 Nov 2025 15:01:29 +0100 Subject: [PATCH 4/4] Bump deprecation notice --- tests/protos.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/protos.rs b/tests/protos.rs index a5d41cfe..559a41ac 100644 --- a/tests/protos.rs +++ b/tests/protos.rs @@ -61,6 +61,18 @@ impl BuilderExt for Builder { "PointsUpdateOperation.operation.clear_payload_deprecated", "#[deprecated(since = \"1.7.0\", note = \"use `ClearPayload` instead\")]", ) + .field_attribute( + "Vector.data", + "#[doc = \"This field is deprecated since 1.16.0\"]", + ) + .field_attribute( + "Vector.indices", + "#[doc = \"This field is deprecated since 1.16.0\"]", + ) + .field_attribute( + "Vector.vectors_count", + "#[doc = \"This field is deprecated since 1.16.0\"]", + ) .field_attribute( "VectorOutput.data", "#[doc = \"This field is deprecated since 1.16.0, use `into_vector` method instead\"]",