Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 17 additions & 17 deletions docs/user_guide/11_advanced_queries.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"In this notebook, we will explore advanced query types available in RedisVL:\n",
"\n",
"1. **`TextQuery`**: Full text search with advanced scoring\n",
"2. **`HybridQuery`**: Combines text and vector search for hybrid retrieval\n",
"2. **`AggregateHybridQuery`**: Combines text and vector search for hybrid retrieval\n",
"3. **`MultiVectorQuery`**: Search over multiple vector fields simultaneously\n",
"\n",
"These query types are powerful tools for building sophisticated search applications that go beyond simple vector similarity search.\n",
Expand Down Expand Up @@ -550,9 +550,9 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"## 2. HybridQuery: Combining Text and Vector Search\n",
"## 2. AggregateHybridQuery: Combining Text and Vector Search\n",
"\n",
"The `HybridQuery` combines text search and vector similarity to provide the best of both worlds:\n",
"The `AggregateHybridQuery` combines text search and vector similarity to provide the best of both worlds:\n",
"- **Text search**: Finds exact keyword matches\n",
"- **Vector search**: Captures semantic similarity\n",
"\n",
Expand All @@ -569,7 +569,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"### Basic Hybrid Query\n",
"### Basic Aggregate Hybrid Query\n",
"\n",
"Let's search for \"running\" with both text and semantic search:"
]
Expand All @@ -593,10 +593,10 @@
}
],
"source": [
"from redisvl.query import HybridQuery\n",
"from redisvl.query import AggregateHybridQuery\n",
"\n",
"# Create a hybrid query\n",
"hybrid_query = HybridQuery(\n",
"hybrid_query = AggregateHybridQuery(\n",
" text=\"running shoes\",\n",
" text_field_name=\"brief_description\",\n",
" vector=[0.1, 0.2, 0.1], # Query vector\n",
Expand Down Expand Up @@ -648,7 +648,7 @@
],
"source": [
"# More emphasis on vector search (alpha=0.9)\n",
"vector_heavy_query = HybridQuery(\n",
"vector_heavy_query = AggregateHybridQuery(\n",
" text=\"comfortable\",\n",
" text_field_name=\"brief_description\",\n",
" vector=[0.15, 0.25, 0.15],\n",
Expand All @@ -667,7 +667,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"### Hybrid Query with Filters\n",
"### Aggregate Hybrid Query with Filters\n",
"\n",
"You can also combine hybrid search with filters:"
]
Expand All @@ -692,7 +692,7 @@
],
"source": [
"# Hybrid search with a price filter\n",
"filtered_hybrid_query = HybridQuery(\n",
"filtered_hybrid_query = AggregateHybridQuery(\n",
" text=\"professional equipment\",\n",
" text_field_name=\"brief_description\",\n",
" vector=[0.9, 0.1, 0.05],\n",
Expand All @@ -712,7 +712,7 @@
"source": [
"### Using Different Text Scorers\n",
"\n",
"HybridQuery supports the same text scoring algorithms as TextQuery:"
"AggregateHybridQuery supports the same text scoring algorithms as TextQuery:"
]
},
{
Expand All @@ -734,8 +734,8 @@
}
],
"source": [
"# Hybrid query with TFIDF scorer\n",
"hybrid_tfidf = HybridQuery(\n",
"# Aggregate Hybrid query with TFIDF scorer\n",
"hybrid_tfidf = AggregateHybridQuery(\n",
" text=\"shoes support\",\n",
" text_field_name=\"brief_description\",\n",
" vector=[0.12, 0.18, 0.12],\n",
Expand Down Expand Up @@ -999,7 +999,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"HybridQuery Results (text + vector):\n"
"AggregateHybridQuery Results (text + vector):\n"
]
},
{
Expand All @@ -1023,8 +1023,8 @@
}
],
"source": [
"# HybridQuery - combines text and vector search\n",
"hybrid_q = HybridQuery(\n",
"# AggregateHybridQuery - combines text and vector search\n",
"hybrid_q = AggregateHybridQuery(\n",
" text=\"shoes\",\n",
" text_field_name=\"brief_description\",\n",
" vector=[0.1, 0.2, 0.1],\n",
Expand All @@ -1033,7 +1033,7 @@
" num_results=3\n",
")\n",
"\n",
"print(\"HybridQuery Results (text + vector):\")\n",
"print(\"AggregateHybridQuery Results (text + vector):\")\n",
"result_print(index.query(hybrid_q))\n",
"print()"
]
Expand Down Expand Up @@ -1103,7 +1103,7 @@
" - When text relevance scoring is important\n",
" - Example: Product search, document retrieval\n",
"\n",
"2. **`HybridQuery`**:\n",
"2. **`AggregateHybridQuery`**:\n",
" - When you want to combine keyword and semantic search\n",
" - For improved search quality over pure text or vector search\n",
" - When you have both text and vector representations of your data\n",
Expand Down
2 changes: 2 additions & 0 deletions redisvl/query/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from redisvl.query.aggregate import (
AggregateHybridQuery,
AggregationQuery,
HybridQuery,
MultiVectorQuery,
Expand All @@ -25,6 +26,7 @@
"CountQuery",
"TextQuery",
"AggregationQuery",
"AggregateHybridQuery",
"HybridQuery",
"MultiVectorQuery",
"Vector",
Expand Down
31 changes: 26 additions & 5 deletions redisvl/query/aggregate.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import warnings
from typing import Any, Dict, List, Optional, Set, Tuple, Union

from pydantic import BaseModel, field_validator, model_validator
Expand Down Expand Up @@ -53,20 +54,20 @@ def __init__(self, query_string):
super().__init__(query_string)


class HybridQuery(AggregationQuery):
class AggregateHybridQuery(AggregationQuery):
"""
HybridQuery combines text and vector search in Redis.
AggregateHybridQuery combines text and vector search in Redis.
It allows you to perform a hybrid search using both text and vector similarity.
It scores documents based on a weighted combination of text and vector similarity.

.. code-block:: python

from redisvl.query import HybridQuery
from redisvl.query import AggregateHybridQuery
from redisvl.index import SearchIndex

index = SearchIndex.from_yaml("path/to/index.yaml")

query = HybridQuery(
query = AggregateHybridQuery(
text="example text",
text_field_name="text_field",
vector=[0.1, 0.2, 0.3],
Expand Down Expand Up @@ -105,7 +106,7 @@ def __init__(
text_weights: Optional[Dict[str, float]] = None,
):
"""
Instantiates a HybridQuery object.
Instantiates a AggregateHybridQuery object.

Args:
text (str): The text to search for.
Expand Down Expand Up @@ -313,6 +314,26 @@ def __str__(self) -> str:
return " ".join([str(x) for x in self.build_args()])


class HybridQuery(AggregateHybridQuery):
"""Backward compatibility wrapper for AggregateHybridQuery.

.. deprecated::
HybridQuery is a backward compatibility wrapper around AggregateHybridQuery
and will eventually be replaced with a new hybrid query implementation.
To maintain current functionality please use AggregateHybridQuery directly.",
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove trailing comma and quotation mark. The docstring should end with a period, not \",.

Suggested change
To maintain current functionality please use AggregateHybridQuery directly.",
To maintain current functionality please use AggregateHybridQuery directly.

Copilot uses AI. Check for mistakes.
"""

def __init__(self, *args, **kwargs):
warnings.warn(
"HybridQuery is a backward compatibility wrapper around AggregateHybridQuery "
"and will eventually be replaced with a new hybrid query implementation. "
"To maintain current functionality please use AggregateHybridQuery directly.",
DeprecationWarning,
stacklevel=2,
)
super().__init__(*args, **kwargs)


class MultiVectorQuery(AggregationQuery):
"""
MultiVectorQuery allows for search over multiple vector fields in a document simultaneously.
Expand Down
71 changes: 56 additions & 15 deletions tests/integration/test_aggregation.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import pytest

from redisvl.index import SearchIndex
from redisvl.query import HybridQuery, MultiVectorQuery, Vector
from redisvl.query import AggregateHybridQuery, HybridQuery, MultiVectorQuery, Vector
from redisvl.query.filter import FilterExpression, Geo, GeoRadius, Num, Tag, Text
from redisvl.redis.utils import array_to_buffer
from tests.conftest import skip_if_redis_version_below
Expand Down Expand Up @@ -89,7 +89,7 @@ def test_hybrid_query(index):
vector_field = "user_embedding"
return_fields = ["user", "credit_score", "age", "job", "location", "description"]

hybrid_query = HybridQuery(
hybrid_query = AggregateHybridQuery(
text=text,
text_field_name=text_field,
vector=vector,
Expand All @@ -115,7 +115,7 @@ def test_hybrid_query(index):
assert doc["job"] in ["engineer", "doctor", "dermatologist", "CEO", "dentist"]
assert doc["credit_score"] in ["high", "low", "medium"]

hybrid_query = HybridQuery(
hybrid_query = AggregateHybridQuery(
text=text,
text_field_name=text_field,
vector=vector,
Expand All @@ -141,7 +141,7 @@ def test_empty_query_string():

# test if text is empty
with pytest.raises(ValueError):
hybrid_query = HybridQuery(
hybrid_query = AggregateHybridQuery(
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assignment to 'hybrid_query' is unnecessary as it is redefined before this value is used.

Copilot uses AI. Check for mistakes.
text=text,
text_field_name=text_field,
vector=vector,
Expand All @@ -151,7 +151,7 @@ def test_empty_query_string():
# test if text becomes empty after stopwords are removed
text = "with a for but and" # will all be removed as default stopwords
with pytest.raises(ValueError):
hybrid_query = HybridQuery(
hybrid_query = AggregateHybridQuery(
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable hybrid_query is not used.

Copilot uses AI. Check for mistakes.
text=text,
text_field_name=text_field,
vector=vector,
Expand All @@ -169,7 +169,7 @@ def test_hybrid_query_with_filter(index):
return_fields = ["user", "credit_score", "age", "job", "location", "description"]
filter_expression = (Tag("credit_score") == ("high")) & (Num("age") > 30)

hybrid_query = HybridQuery(
hybrid_query = AggregateHybridQuery(
text=text,
text_field_name=text_field,
vector=vector,
Expand All @@ -195,7 +195,7 @@ def test_hybrid_query_with_geo_filter(index):
return_fields = ["user", "credit_score", "age", "job", "location", "description"]
filter_expression = Geo("location") == GeoRadius(-122.4194, 37.7749, 1000, "m")

hybrid_query = HybridQuery(
hybrid_query = AggregateHybridQuery(
text=text,
text_field_name=text_field,
vector=vector,
Expand All @@ -219,7 +219,7 @@ def test_hybrid_query_alpha(index, alpha):
vector = [0.1, 0.1, 0.5]
vector_field = "user_embedding"

hybrid_query = HybridQuery(
hybrid_query = AggregateHybridQuery(
text=text,
text_field_name=text_field,
vector=vector,
Expand Down Expand Up @@ -247,7 +247,7 @@ def test_hybrid_query_stopwords(index):
vector_field = "user_embedding"
alpha = 0.5

hybrid_query = HybridQuery(
hybrid_query = AggregateHybridQuery(
text=text,
text_field_name=text_field,
vector=vector,
Expand Down Expand Up @@ -282,7 +282,7 @@ def test_hybrid_query_with_text_filter(index):
filter_expression = Text(text_field) == ("medical")

# make sure we can still apply filters to the same text field we are querying
hybrid_query = HybridQuery(
hybrid_query = AggregateHybridQuery(
text=text,
text_field_name=text_field,
vector=vector,
Expand All @@ -300,7 +300,7 @@ def test_hybrid_query_with_text_filter(index):
filter_expression = (Text(text_field) == ("medical")) & (
(Text(text_field) != ("research"))
)
hybrid_query = HybridQuery(
hybrid_query = AggregateHybridQuery(
text=text,
text_field_name=text_field,
vector=vector,
Expand Down Expand Up @@ -330,7 +330,7 @@ def test_hybrid_query_word_weights(index, scorer):
weights = {"medical": 3.4, "cancers": 5}

# test we can run a query with text weights
weighted_query = HybridQuery(
weighted_query = AggregateHybridQuery(
text=text,
text_field_name=text_field,
vector=vector,
Expand All @@ -344,7 +344,7 @@ def test_hybrid_query_word_weights(index, scorer):
assert len(weighted_results) == 7

# test that weights do change the scores on results
unweighted_query = HybridQuery(
unweighted_query = AggregateHybridQuery(
text=text,
text_field_name=text_field,
vector=vector,
Expand All @@ -363,7 +363,7 @@ def test_hybrid_query_word_weights(index, scorer):

# test that weights do change the document score and order of results
weights = {"medical": 5, "cancers": 3.4} # switch the weights
weighted_query = HybridQuery(
weighted_query = AggregateHybridQuery(
text=text,
text_field_name=text_field,
vector=vector,
Expand All @@ -377,7 +377,7 @@ def test_hybrid_query_word_weights(index, scorer):
assert weighted_results != unweighted_results

# test assigning weights on construction is equivalent to setting them on the query object
new_query = HybridQuery(
new_query = AggregateHybridQuery(
text=text,
text_field_name=text_field,
vector=vector,
Expand Down Expand Up @@ -743,3 +743,44 @@ def test_multivector_query_mixed_index(index):
assert (
float(r["combined_score"]) - score <= 0.0001
) # allow for small floating point error


def test_hybrid_query_backward_compatibility(index):
skip_if_redis_version_below(index.client, "7.2.0")

text = "a medical professional with expertise in lung cancer"
text_field = "description"
vector = [0.1, 0.1, 0.5]
vector_field = "user_embedding"
return_fields = ["user", "credit_score", "age", "job", "location", "description"]

hybrid_query = AggregateHybridQuery(
text=text,
text_field_name=text_field,
vector=vector,
vector_field_name=vector_field,
return_fields=return_fields,
)

results = index.query(hybrid_query)
assert len(results) == 7
for result in results:
assert result["user"] in [
"john",
"derrick",
"nancy",
"tyler",
"tim",
"taimur",
"joe",
"mary",
]

with pytest.warns(DeprecationWarning):
_ = HybridQuery(
text=text,
text_field_name=text_field,
vector=vector,
vector_field_name=vector_field,
return_fields=return_fields,
)
Loading
Loading