@@ -6,7 +6,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
66
77use anyhow:: Context ;
88use qdrant_client:: qdrant:: {
9- Condition , Filter , GeoPoint , GeoRadius , Range , RepeatedStrings , SparseIndices ,
9+ Condition , Filter , GeoPoint , GeoRadius , Range , RepeatedStrings , SparseIndices , VectorInput ,
1010 r#match:: MatchValue ,
1111} ;
1212use rand:: Rng ;
@@ -32,10 +32,34 @@ const GEO_SPREAD_DEG: f64 = 1.0;
3232const GEO_RADIUS_METERS_MIN : f64 = 1000.0 ;
3333const GEO_RADIUS_METERS_MAX : f64 = 50000.0 ;
3434
35+ /// A dense query: one vector, or several sub-vectors for a multivector.
36+ #[ derive( Debug , Clone , PartialEq ) ]
37+ pub enum DenseQuery {
38+ Single ( Vec < f32 > ) ,
39+ Multi ( Vec < Vec < f32 > > ) ,
40+ }
41+
42+ impl DenseQuery {
43+ pub fn into_vector_input ( self ) -> VectorInput {
44+ match self {
45+ DenseQuery :: Single ( vector) => VectorInput :: new_dense ( vector) ,
46+ DenseQuery :: Multi ( vectors) => VectorInput :: new_multi ( vectors) ,
47+ }
48+ }
49+ }
50+
51+ /// The first stage of a two-stage query.
52+ #[ derive( Debug , Clone ) ]
53+ pub struct GeneratedPrefetch {
54+ pub vector : Vec < f32 > ,
55+ pub using : String ,
56+ pub limit : u64 ,
57+ }
58+
3559/// One query vector plus optional filter, ready to be turned into a gRPC request.
3660#[ derive( Debug , Clone ) ]
3761pub struct GeneratedQuery {
38- pub dense : Option < ( Vec < f32 > , Option < String > ) > ,
62+ pub dense : Option < ( DenseQuery , Option < String > ) > ,
3963 pub sparse : Option < ( Vec < f32 > , SparseIndices , String ) > ,
4064 pub filter : Option < Filter > ,
4165 /// Sparse-vector IDF corpus: restricts which points the IDF statistics are
@@ -45,6 +69,8 @@ pub struct GeneratedQuery {
4569 /// when the request draws queries from a reference dataset. Used to measure
4670 /// search accuracy (recall) against the dataset's known answers.
4771 pub expected_ids : Option < Vec < u64 > > ,
72+ /// Present for a two-stage query: its candidates are what the main query rescores.
73+ pub prefetch : Option < GeneratedPrefetch > ,
4874}
4975
5076/// A reference dataset's query set, held in memory, with a cursor that hands out
@@ -492,10 +518,20 @@ impl ConfigSearchGenerator {
492518 datatype,
493519 source,
494520 filters : _,
521+ multivector,
522+ prefetch,
495523 } => {
496524 let ( vector, expected_ids, dataset_filter) =
497525 if let Some ( query_dataset) = & state. query_dataset {
498- Self :: read_dense_query ( query_dataset)
526+ let ( vector, ids, filter) = Self :: read_dense_query ( query_dataset) ;
527+ ( DenseQuery :: Single ( vector) , ids, filter)
528+ } else if let Some ( multivector) = multivector {
529+ // Validation allows multivector only with random queries.
530+ let is_uint = * datatype == DatatypeKind :: Uint8 ;
531+ let vectors = ( 0 ..multivector. count )
532+ . map ( |_| random_dense_vector ( rng, * size as usize , is_uint) )
533+ . collect ( ) ;
534+ ( DenseQuery :: Multi ( vectors) , None , None )
499535 } else {
500536 let vector = Self :: gen_dense_vector (
501537 rng,
@@ -505,14 +541,24 @@ impl ConfigSearchGenerator {
505541 state. dense_reader . as_ref ( ) ,
506542 req_id,
507543 ) ;
508- ( vector, None , None )
544+ ( DenseQuery :: Single ( vector) , None , None )
509545 } ;
546+ let prefetch = prefetch. as_ref ( ) . map ( |p| GeneratedPrefetch {
547+ vector : random_dense_vector (
548+ rng,
549+ p. size as usize ,
550+ p. datatype == DatatypeKind :: Uint8 ,
551+ ) ,
552+ using : p. using . clone ( ) ,
553+ limit : p. limit ,
554+ } ) ;
510555 GeneratedQuery {
511556 dense : Some ( ( vector, using. clone ( ) ) ) ,
512557 sparse : None ,
513558 filter : dataset_filter. or_else ( || state. filters . build ( rng) ) ,
514559 idf_corpus : None ,
515560 expected_ids,
561+ prefetch,
516562 }
517563 }
518564 SearchRequestConfig :: Sparse {
@@ -537,6 +583,7 @@ impl ConfigSearchGenerator {
537583 filter : dataset_filter. or_else ( || state. filters . build ( rng) ) ,
538584 idf_corpus : state. idf_corpus . build ( rng) ,
539585 expected_ids,
586+ prefetch : None ,
540587 }
541588 }
542589 }
@@ -645,6 +692,26 @@ mod tests {
645692 ConfigSearchGenerator :: new ( & config) . unwrap ( )
646693 }
647694
695+ #[ test]
696+ fn generates_multivector_query_with_prefetch ( ) {
697+ let generator = build_gen (
698+ "collection:\n name: x\n requests:\n - kind: dense\n using: colbert\n size: 8\n multivector: { count: 4 }\n prefetch: { using: dense, size: 6, limit: 50 }\n " ,
699+ ) ;
700+ let q = generator. make_query ( 0 , & mut rand:: rng ( ) ) ;
701+ let ( query, using) = q. dense . unwrap ( ) ;
702+ assert_eq ! ( using. as_deref( ) , Some ( "colbert" ) ) ;
703+ match query {
704+ DenseQuery :: Multi ( vectors) => {
705+ assert_eq ! ( vectors. len( ) , 4 ) ;
706+ assert ! ( vectors. iter( ) . all( |v| v. len( ) == 8 ) ) ;
707+ }
708+ other => panic ! ( "expected a multivector query, got {other:?}" ) ,
709+ }
710+ let prefetch = q. prefetch . unwrap ( ) ;
711+ assert_eq ! ( ( prefetch. using. as_str( ) , prefetch. limit) , ( "dense" , 50 ) ) ;
712+ assert_eq ! ( prefetch. vector. len( ) , 6 ) ;
713+ }
714+
648715 #[ test]
649716 fn generates_dense_and_sparse_queries ( ) {
650717 let generator = build_gen (
@@ -696,11 +763,17 @@ mod tests {
696763 let q0 = generator. make_query_for ( 0 , 0 , & mut rng) ;
697764 let q1 = generator. make_query_for ( 0 , 0 , & mut rng) ;
698765 let q2 = generator. make_query_for ( 0 , 0 , & mut rng) ;
699- assert_eq ! ( q0. dense. as_ref( ) . unwrap( ) . 0 , vec![ 0.0 , 1.0 , 2.0 , 3.0 ] ) ;
766+ assert_eq ! (
767+ q0. dense. as_ref( ) . unwrap( ) . 0 ,
768+ DenseQuery :: Single ( vec![ 0.0 , 1.0 , 2.0 , 3.0 ] )
769+ ) ;
700770 assert_eq ! ( q0. expected_ids, Some ( vec![ 0 , 2 ] ) ) ;
701771 assert_eq ! ( q1. expected_ids, Some ( vec![ 1 , 2 ] ) ) ;
702772 // Wrapped back to the first query.
703- assert_eq ! ( q2. dense. as_ref( ) . unwrap( ) . 0 , vec![ 0.0 , 1.0 , 2.0 , 3.0 ] ) ;
773+ assert_eq ! (
774+ q2. dense. as_ref( ) . unwrap( ) . 0 ,
775+ DenseQuery :: Single ( vec![ 0.0 , 1.0 , 2.0 , 3.0 ] )
776+ ) ;
704777 assert_eq ! ( q2. expected_ids, Some ( vec![ 0 , 2 ] ) ) ;
705778 }
706779
0 commit comments