diff --git a/proto/points.proto b/proto/points.proto index 921e85a..58db3e2 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 0409727..a95cde6 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/error.rs b/src/error.rs index 4421b2e..962f2b3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -60,7 +60,7 @@ impl Debug for NotA { impl Display for NotA { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str(concat!("not a bool")) + f.write_str("not a bool") } } @@ -87,7 +87,7 @@ impl Debug for NotA { impl Display for NotA { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str(concat!("not an i64")) + f.write_str("not an i64") } } @@ -114,7 +114,7 @@ impl Debug for NotA { impl Display for NotA { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str(concat!("not a f64")) + f.write_str("not a f64") } } @@ -141,7 +141,7 @@ impl Debug for NotA { impl Display for NotA { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str(concat!("not a String")) + f.write_str("not a String") } } diff --git a/src/grpc_conversions/mod.rs b/src/grpc_conversions/mod.rs index 27c3615..6a21bd3 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 0000000..ddb5ee4 --- /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.is_empty() { + 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 52148d4..dbfe073 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 599eea3..a5d41cf 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 828222a..7e57fb5 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 0000000..73ba4d0 --- /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 0000000..a0da285 --- /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