Skip to content

Commit 4d3ba70

Browse files
committed
Update docstrings
1 parent 4b3a1fe commit 4d3ba70

File tree

2 files changed

+222
-118
lines changed

2 files changed

+222
-118
lines changed

redisvl/index/index.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,6 +1023,33 @@ def search(self, *args, **kwargs) -> "Result":
10231023
if REDIS_HYBRID_AVAILABLE:
10241024

10251025
def hybrid_search(self, query: HybridQuery, **kwargs) -> List[Dict[str, Any]]:
1026+
"""Perform a hybrid search against the index, combining text and vector search.
1027+
1028+
Args:
1029+
query: The text+vector search query to be performed, with configurable fusion methods and
1030+
post-processing.
1031+
kwargs: Additional arguments to pass to the redis-py hybrid_search method (e.g. timeout).
1032+
1033+
Returns:
1034+
List[Dict[str, Any]]: The search results ordered by combined score unless otherwise specified.
1035+
1036+
Notes:
1037+
Hybrid search is only available in Redis 8.4.0+, and requires redis-py >= 7.1.0.
1038+
1039+
.. code-block:: python
1040+
1041+
from redisvl.query.hybrid import HybridQuery
1042+
1043+
hybrid_query = HybridQuery(
1044+
text="lorem ipsum dolor sit amet",
1045+
text_field_name="description",
1046+
vector=[0.1, 0.2, 0.3],
1047+
vector_field_name="embedding"
1048+
)
1049+
1050+
results = index.hybrid_search(hybrid_query)
1051+
1052+
"""
10261053
results: HybridResult = self._redis_client.ft(
10271054
self.schema.index.name
10281055
).hybrid_search(
@@ -1863,6 +1890,33 @@ async def search(self, *args, **kwargs) -> "Result":
18631890
async def hybrid_search(
18641891
self, query: HybridQuery, **kwargs
18651892
) -> List[Dict[str, Any]]:
1893+
"""Perform a hybrid search against the index, combining text and vector search.
1894+
1895+
Args:
1896+
query: The text+vector search query to be performed, with configurable fusion methods and
1897+
post-processing.
1898+
kwargs: Additional arguments to pass to the redis-py hybrid_search method (e.g. timeout).
1899+
1900+
Returns:
1901+
List[Dict[str, Any]]: The search results ordered by combined score unless otherwise specified.
1902+
1903+
Notes:
1904+
Hybrid search is only available in Redis 8.4.0+, and requires redis-py >= 7.1.0.
1905+
1906+
.. code-block:: python
1907+
1908+
from redisvl.query.hybrid import HybridQuery
1909+
1910+
hybrid_query = HybridQuery(
1911+
text="lorem ipsum dolor sit amet",
1912+
text_field_name="description",
1913+
vector=[0.1, 0.2, 0.3],
1914+
vector_field_name="embedding"
1915+
)
1916+
1917+
results = index.hybrid_search(hybrid_query)
1918+
1919+
"""
18661920
client = await self._get_client()
18671921
results: HybridResult = await client.ft(
18681922
self.schema.index.name

redisvl/query/hybrid.py

Lines changed: 168 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)