@@ -62,8 +62,7 @@ def __init__(
6262 Args:
6363 text: The text to search for.
6464 text_field_name: The text field name to search in.
65- vector: The vector to perform vector similarity search, converted to bytes (e.g.
66- using `redisvl.redis.utils.array_to_buffer`).
65+ vector: The vector to perform vector similarity search.
6766 vector_field_name: The vector field name to search in.
6867 text_scorer: The text scorer to use. Options are {TFIDF, TFIDF.DOCNORM,
6968 BM25STD, BM25STD.NORM, BM25STD.TANH, DISMAX, DOCSCORE, HAMMING}. Defaults to "BM25STD". For more
@@ -130,7 +129,7 @@ def __init__(
130129 text , text_field_name , text_filter_expression
131130 )
132131
133- self .query = self . build_query (
132+ self .query = build_base_query (
134133 text_query = query_string ,
135134 vector = vector ,
136135 vector_field_name = vector_field_name ,
@@ -148,7 +147,7 @@ def __init__(
148147
149148 if combination_method :
150149 self .combination_method : Optional [CombineResultsMethod ] = (
151- self . build_combination_method (
150+ build_combination_method (
152151 combination_method = combination_method ,
153152 rrf_window = rrf_window ,
154153 rrf_constant = rrf_constant ,
@@ -160,124 +159,175 @@ def __init__(
160159 else :
161160 self .combination_method = None
162161
163- @staticmethod
164- def build_query (
165- text_query : str ,
166- vector : bytes | List [float ],
167- vector_field_name : str ,
168- text_scorer : str = "BM25STD" ,
169- yield_text_score_as : Optional [str ] = None ,
170- vector_search_method : Optional [Literal ["KNN" , "RANGE" ]] = None ,
171- knn_k : Optional [int ] = None ,
172- knn_ef_runtime : Optional [int ] = None ,
173- range_radius : Optional [int ] = None ,
174- range_epsilon : Optional [float ] = None ,
175- yield_vsim_score_as : Optional [str ] = None ,
176- vector_filter_expression : Optional [Union [str , FilterExpression ]] = None ,
177- dtype : str = "float32" ,
178- ) -> RedisHybridQuery :
179- """Build a Redis HybridQuery for the hybrid search."""
180-
181- # Serialize the full-text search query
182- search_query = HybridSearchQuery (
183- query_string = text_query ,
184- scorer = text_scorer ,
185- yield_score_as = yield_text_score_as ,
186- )
187-
188- if isinstance (vector , bytes ):
189- vector_data = vector
190- else :
191- vector_data = array_to_buffer (vector , dtype )
192-
193- # Serialize vector similarity search method and params, if specified
194- vsim_search_method : Optional [VectorSearchMethods ] = None
195- vsim_search_method_params : Dict [str , Any ] = {}
196- if vector_search_method == "KNN" :
197- vsim_search_method = VectorSearchMethods .KNN
198- if not knn_k :
199- raise ValueError ("Must provide K if vector_search_method is KNN" )
200-
201- vsim_search_method_params ["K" ] = knn_k
202- if knn_ef_runtime :
203- vsim_search_method_params ["EF_RUNTIME" ] = knn_ef_runtime
204-
205- elif vector_search_method == "RANGE" :
206- vsim_search_method = VectorSearchMethods .RANGE
207- if not range_radius :
208- raise ValueError ("Must provide RADIUS if vector_search_method is RANGE" )
209-
210- vsim_search_method_params ["RADIUS" ] = range_radius
211- if range_epsilon :
212- vsim_search_method_params ["EPSILON" ] = range_epsilon
213-
214- elif vector_search_method is not None :
215- raise ValueError (f"Unknown vector search method: { vector_search_method } " )
216-
217- if vector_filter_expression :
218- vsim_filter = Filter ("FILTER" , str (vector_filter_expression ))
219- else :
220- vsim_filter = None
221-
222- # Serialize the vector similarity query
223- vsim_query = HybridVsimQuery (
224- vector_field_name = "@" + vector_field_name ,
225- vector_data = vector_data ,
226- vsim_search_method = vsim_search_method ,
227- vsim_search_method_params = vsim_search_method_params ,
228- filter = vsim_filter ,
229- yield_score_as = yield_vsim_score_as ,
230- )
231162
232- return RedisHybridQuery (
233- search_query = search_query ,
234- vector_similarity_query = vsim_query ,
235- )
163+ def build_base_query (
164+ text_query : str ,
165+ vector : bytes | List [float ],
166+ vector_field_name : str ,
167+ text_scorer : str = "BM25STD" ,
168+ yield_text_score_as : Optional [str ] = None ,
169+ vector_search_method : Optional [Literal ["KNN" , "RANGE" ]] = None ,
170+ knn_k : Optional [int ] = None ,
171+ knn_ef_runtime : Optional [int ] = None ,
172+ range_radius : Optional [int ] = None ,
173+ range_epsilon : Optional [float ] = None ,
174+ yield_vsim_score_as : Optional [str ] = None ,
175+ vector_filter_expression : Optional [Union [str , FilterExpression ]] = None ,
176+ dtype : str = "float32" ,
177+ ) -> RedisHybridQuery :
178+ """Build a Redis HybridQuery for performing hybrid search.
179+
180+ Args:
181+ text_query: The query for the text search.
182+ vector: The vector to perform vector similarity search.
183+ vector_field_name: The vector field name to search in.
184+ text_scorer: The text scorer to use. Options are {TFIDF, TFIDF.DOCNORM,
185+ BM25STD, BM25STD.NORM, BM25STD.TANH, DISMAX, DOCSCORE, HAMMING}. Defaults to "BM25STD". For more
186+ information about supported scroring algorithms,
187+ see https://redis.io/docs/latest/develop/ai/search-and-query/advanced-concepts/scoring/
188+ yield_text_score_as: The name of the field to yield the text score as.
189+ vector_search_method: The vector search method to use. Options are {KNN, RANGE}. Defaults to None.
190+ knn_k: The number of nearest neighbors to return, required if `vector_search_method` is "KNN".
191+ knn_ef_runtime: The exploration factor parameter for HNSW, optional if `vector_search_method` is "KNN".
192+ range_radius: The search radius to use, required if `vector_search_method` is "RANGE".
193+ range_epsilon: The epsilon value to use, optional if `vector_search_method` is "RANGE"; defines the
194+ accuracy of the search.
195+ yield_vsim_score_as: The name of the field to yield the vector similarity score as.
196+ vector_filter_expression: The filter expression to use for the vector similarity search. Defaults to None.
197+ dtype: The data type of the vector. Defaults to "float32".
198+
199+ Notes:
200+ If RRF combination method is used, then at least one of `rrf_window` or `rrf_constant` must be provided.
201+ If LINEAR combination method is used, then at least one of `linear_alpha` or `linear_beta` must be provided.
202+
203+ Raises:
204+ ValueError: If `vector_search_method` is defined and isn't one of {KNN, RANGE}.
205+ ValueError: If `vector_search_method` is "KNN" and `knn_k` is not provided.
206+ ValueError: If `vector_search_method` is "RANGE" and `range_radius` is not provided.
207+
208+ Returns:
209+ A Redis HybridQuery object that defines the text and vector searches to be performed.
210+ """
236211
237- @staticmethod
238- def build_combination_method (
239- combination_method : Literal ["RRF" , "LINEAR" ],
240- rrf_window : Optional [int ] = None ,
241- rrf_constant : Optional [float ] = None ,
242- linear_alpha : Optional [float ] = None ,
243- linear_beta : Optional [float ] = None ,
244- yield_score_as : Optional [str ] = None ,
245- ) -> CombineResultsMethod :
246- """Build a configuration for combining hybrid search scores."""
247- method_params : Dict [str , Any ] = {}
248- if combination_method == "RRF" :
249- method = CombinationMethods .RRF
250- if rrf_window :
251- method_params ["WINDOW" ] = rrf_window
252- if rrf_constant :
253- method_params ["CONSTANT" ] = rrf_constant
254-
255- elif combination_method == "LINEAR" :
256- method = CombinationMethods .LINEAR
257- if linear_alpha :
258- method_params ["ALPHA" ] = linear_alpha
259- if not linear_beta :
260- method_params ["BETA" ] = 1 - linear_alpha
261-
262- if linear_beta :
263- if not linear_alpha : # Defined first to preserve consistent ordering
264- method_params ["ALPHA" ] = 1 - linear_beta
265- method_params ["BETA" ] = linear_beta
266-
267- # TODO: Warn if alpha + beta != 1
212+ # Serialize the full-text search query
213+ search_query = HybridSearchQuery (
214+ query_string = text_query ,
215+ scorer = text_scorer ,
216+ yield_score_as = yield_text_score_as ,
217+ )
268218
269- else :
270- raise ValueError (f"Unknown combination method: { combination_method } " )
219+ if isinstance (vector , bytes ):
220+ vector_data = vector
221+ else :
222+ vector_data = array_to_buffer (vector , dtype )
223+
224+ # Serialize vector similarity search method and params, if specified
225+ vsim_search_method : Optional [VectorSearchMethods ] = None
226+ vsim_search_method_params : Dict [str , Any ] = {}
227+ if vector_search_method == "KNN" :
228+ vsim_search_method = VectorSearchMethods .KNN
229+ if not knn_k :
230+ raise ValueError ("Must provide K if vector_search_method is KNN" )
231+
232+ vsim_search_method_params ["K" ] = knn_k
233+ if knn_ef_runtime :
234+ vsim_search_method_params ["EF_RUNTIME" ] = knn_ef_runtime
235+
236+ elif vector_search_method == "RANGE" :
237+ vsim_search_method = VectorSearchMethods .RANGE
238+ if not range_radius :
239+ raise ValueError ("Must provide RADIUS if vector_search_method is RANGE" )
240+
241+ vsim_search_method_params ["RADIUS" ] = range_radius
242+ if range_epsilon :
243+ vsim_search_method_params ["EPSILON" ] = range_epsilon
244+
245+ elif vector_search_method is not None :
246+ raise ValueError (f"Unknown vector search method: { vector_search_method } " )
247+
248+ if vector_filter_expression :
249+ vsim_filter = Filter ("FILTER" , str (vector_filter_expression ))
250+ else :
251+ vsim_filter = None
252+
253+ # Serialize the vector similarity query
254+ vsim_query = HybridVsimQuery (
255+ vector_field_name = "@" + vector_field_name ,
256+ vector_data = vector_data ,
257+ vsim_search_method = vsim_search_method ,
258+ vsim_search_method_params = vsim_search_method_params ,
259+ filter = vsim_filter ,
260+ yield_score_as = yield_vsim_score_as ,
261+ )
271262
272- if yield_score_as :
273- method_params ["YIELD_SCORE_AS" ] = yield_score_as
263+ return RedisHybridQuery (
264+ search_query = search_query ,
265+ vector_similarity_query = vsim_query ,
266+ )
274267
275- if not method_params :
276- raise ValueError (
277- "No parameters provided for combination method - must provide at least one parameter."
278- )
279268
280- return CombineResultsMethod (
281- method = method ,
282- ** method_params ,
269+ def build_combination_method (
270+ combination_method : Literal ["RRF" , "LINEAR" ],
271+ rrf_window : Optional [int ] = None ,
272+ rrf_constant : Optional [float ] = None ,
273+ linear_alpha : Optional [float ] = None ,
274+ linear_beta : Optional [float ] = None ,
275+ yield_score_as : Optional [str ] = None ,
276+ ) -> CombineResultsMethod :
277+ """Build a configuration for combining hybrid search scores.
278+
279+ Args:
280+ combination_method: The combination method to use. Options are {RRF, LINEAR}.
281+ rrf_window: The window size to use for the reciprocal rank fusion (RRF) combination method. Limits
282+ fusion scope.
283+ rrf_constant: The constant to use for the reciprocal rank fusion (RRF) combination method. Controls decay
284+ of rank influence.
285+ linear_alpha: The weight of the first query for the linear combination method (LINEAR).
286+ linear_beta: The weight of the second query for the linear combination method (LINEAR).
287+ yield_score_as: The name of the field to yield the combined score as.
288+
289+ Raises:
290+ ValueError: If `combination_method` is defined and isn't one of {RRF, LINEAR}.
291+ ValueError: If `combination_method` is "RRF" and neither `rrf_window` nor `rrf_constant` is provided.
292+ ValueError: If `combination_method` is "LINEAR" and neither `linear_alpha` nor `linear_beta` is provided.
293+
294+ Returns:
295+ A CombineResultsMethod object that defines how the text and vector scores should be combined.
296+ """
297+ method_params : Dict [str , Any ] = {}
298+ if combination_method == "RRF" :
299+ method = CombinationMethods .RRF
300+ if rrf_window :
301+ method_params ["WINDOW" ] = rrf_window
302+ if rrf_constant :
303+ method_params ["CONSTANT" ] = rrf_constant
304+
305+ elif combination_method == "LINEAR" :
306+ method = CombinationMethods .LINEAR
307+ if linear_alpha :
308+ method_params ["ALPHA" ] = linear_alpha
309+ if not linear_beta :
310+ method_params ["BETA" ] = 1 - linear_alpha
311+
312+ if linear_beta :
313+ if not linear_alpha : # Defined first to preserve consistent ordering
314+ method_params ["ALPHA" ] = 1 - linear_beta
315+ method_params ["BETA" ] = linear_beta
316+
317+ # TODO: Warn if alpha + beta != 1
318+
319+ else :
320+ raise ValueError (f"Unknown combination method: { combination_method } " )
321+
322+ if yield_score_as :
323+ method_params ["YIELD_SCORE_AS" ] = yield_score_as
324+
325+ if not method_params :
326+ raise ValueError (
327+ "No parameters provided for combination method - must provide at least one parameter."
283328 )
329+
330+ return CombineResultsMethod (
331+ method = method ,
332+ ** method_params ,
333+ )
0 commit comments