Advanced Query Types

In this notebook, we will explore advanced query types available in RedisVL:

  1. TextQuery: Full text search with advanced scoring
  2. AggregateHybridQuery and HybridQuery: Combines text and vector search for hybrid retrieval
  3. MultiVectorQuery: Search over multiple vector fields simultaneously

These query types are powerful tools for building sophisticated search applications that go beyond simple vector similarity search.

Prerequisites:

  • Ensure redisvl is installed in your Python environment.
  • Have a running instance of Redis Stack or Redis Cloud.
  • For HybridQuery, we will need Redis >= 8.4.0 and redis-py >= 7.1.0.

Setup and Data Preparation

First, let's create a schema and prepare sample data that includes text fields, numeric fields, and vector fields.

import numpy as np
from jupyterutils import result_print

# Sample data with text descriptions, categories, and vectors
data = [
    {
        'product_id': 'prod_1',
        'brief_description': 'comfortable running shoes for athletes',
        'full_description': 'Engineered with a dual-layer EVA foam midsole and FlexWeave breathable mesh upper, these running shoes deliver responsive cushioning for long-distance runs. The anatomical footbed adapts to your stride while the carbon rubber outsole provides superior traction on varied terrain.',
        'category': 'footwear',
        'price': 89.99,
        'rating': 4.5,
        'text_embedding': np.array([0.1, 0.2, 0.1], dtype=np.float32).tobytes(),
        'image_embedding': np.array([0.8, 0.1], dtype=np.float32).tobytes(),
    },
    {
        'product_id': 'prod_2',
        'brief_description': 'lightweight running jacket with water resistance',
        'full_description': 'Stay protected with this ultralight 2.5-layer DWR-coated shell featuring laser-cut ventilation zones and reflective piping for low-light visibility. Packs into its own chest pocket and weighs just 4.2 oz, making it ideal for unpredictable weather conditions.',
        'category': 'outerwear',
        'price': 129.99,
        'rating': 4.8,
        'text_embedding': np.array([0.2, 0.3, 0.2], dtype=np.float32).tobytes(),
        'image_embedding': np.array([0.7, 0.2], dtype=np.float32).tobytes(),
    },
    {
        'product_id': 'prod_3',
        'brief_description': 'professional tennis racket for competitive players',
        'full_description': 'Competition-grade racket featuring a 98 sq in head size, 16x19 string pattern, and aerospace-grade graphite frame that delivers explosive power with pinpoint control. Tournament-approved specs include 315g weight and 68 RA stiffness rating for advanced baseline play.',
        'category': 'equipment',
        'price': 199.99,
        'rating': 4.9,
        'text_embedding': np.array([0.9, 0.1, 0.05], dtype=np.float32).tobytes(),
        'image_embedding': np.array([0.1, 0.9], dtype=np.float32).tobytes(),
    },
    {
        'product_id': 'prod_4',
        'brief_description': 'yoga mat with extra cushioning for comfort',
        'full_description': 'Premium 8mm thick TPE yoga mat with dual-texture surface - smooth side for hot yoga flow and textured side for maximum grip during balancing poses. Closed-cell technology prevents moisture absorption while alignment markers guide proper positioning in asanas.',
        'category': 'accessories',
        'price': 39.99,
        'rating': 4.3,
        'text_embedding': np.array([0.15, 0.25, 0.15], dtype=np.float32).tobytes(),
        'image_embedding': np.array([0.5, 0.5], dtype=np.float32).tobytes(),
    },
    {
        'product_id': 'prod_5',
        'brief_description': 'basketball shoes with excellent ankle support',
        'full_description': 'High-top basketball sneakers with Zoom Air units in forefoot and heel, reinforced lateral sidewalls for explosive cuts, and herringbone traction pattern optimized for hardwood courts. The internal bootie construction and extended ankle collar provide lockdown support during aggressive drives.',
        'category': 'footwear',
        'price': 139.99,
        'rating': 4.7,
        'text_embedding': np.array([0.12, 0.18, 0.12], dtype=np.float32).tobytes(),
        'image_embedding': np.array([0.75, 0.15], dtype=np.float32).tobytes(),
    },
    {
        'product_id': 'prod_6',
        'brief_description': 'swimming goggles with anti-fog coating',
        'full_description': 'Low-profile competition goggles with curved polycarbonate lenses offering 180-degree peripheral vision and UV protection. Hydrophobic anti-fog coating lasts 10x longer than standard treatments, while the split silicone strap and interchangeable nose bridges ensure a watertight, custom fit.',
        'category': 'accessories',
        'price': 24.99,
        'rating': 4.4,
        'text_embedding': np.array([0.3, 0.1, 0.2], dtype=np.float32).tobytes(),
        'image_embedding': np.array([0.2, 0.8], dtype=np.float32).tobytes(),
    },
]

Define the Schema

Our schema includes:

  • Tag fields: product_id, category
  • Text fields: brief_description and full_description for full-text search
  • Numeric fields: price, rating
  • Vector fields: text_embedding (3 dimensions) and image_embedding (2 dimensions) for semantic search
schema = {
    "index": {
        "name": "advanced_queries",
        "prefix": "products",
        "storage_type": "hash",
    },
    "fields": [
        {"name": "product_id", "type": "tag"},
        {"name": "category", "type": "tag"},
        {"name": "brief_description", "type": "text"},
        {"name": "full_description", "type": "text"},
        {"name": "price", "type": "numeric"},
        {"name": "rating", "type": "numeric"},
        {
            "name": "text_embedding",
            "type": "vector",
            "attrs": {
                "dims": 3,
                "distance_metric": "cosine",
                "algorithm": "flat",
                "datatype": "float32"
            }
        },
        {
            "name": "image_embedding",
            "type": "vector",
            "attrs": {
                "dims": 2,
                "distance_metric": "cosine",
                "algorithm": "flat",
                "datatype": "float32"
            }
        }
    ],
}

Create Index and Load Data

from redisvl.index import SearchIndex

# Create the search index
index = SearchIndex.from_dict(schema, redis_url="redis://localhost:6379")

# Create the index and load data
index.create(overwrite=True)
keys = index.load(data)

print(f"Loaded {len(keys)} products into the index")
Loaded 6 products into the index

The TextQuery class enables full text search with advanced scoring algorithms. It's ideal for keyword-based search with relevance ranking.

Let's search for products related to "running shoes":

from redisvl.query import TextQuery

# Create a text query
text_query = TextQuery(
    text="running shoes",
    text_field_name="brief_description",
    return_fields=["product_id", "brief_description", "category", "price"],
    num_results=5
)

results = index.query(text_query)
result_print(results)
scoreproduct_idbrief_descriptioncategoryprice
6.134713688880119prod_1comfortable running shoes for athletesfootwear89.99
6.134713688880119prod_1comfortable running shoes for athletesfootwear89.99
2.148612199701887prod_5basketball shoes with excellent ankle supportfootwear139.99
2.148612199701887prod_5basketball shoes with excellent ankle supportfootwear139.99
2.102960001841964prod_2lightweight running jacket with water resistanceouterwear129.99

Text Search with Different Scoring Algorithms

RedisVL supports multiple text scoring algorithms. Let's compare BM25STD and TFIDF:

# BM25 standard scoring (default)
bm25_query = TextQuery(
    text="comfortable shoes",
    text_field_name="brief_description",
    text_scorer="BM25STD",
    return_fields=["product_id", "brief_description", "price"],
    num_results=3
)

print("Results with BM25 scoring:")
results = index.query(bm25_query)
result_print(results)
Results with BM25 scoring:
scoreproduct_idbrief_descriptionprice
6.340446315760713prod_1comfortable running shoes for athletes89.99
6.340446315760713prod_1comfortable running shoes for athletes89.99
2.148612199701887prod_5basketball shoes with excellent ankle support139.99
# TFIDF scoring
tfidf_query = TextQuery(
    text="comfortable shoes",
    text_field_name="brief_description",
    text_scorer="TFIDF",
    return_fields=["product_id", "brief_description", "price"],
    num_results=3
)

print("Results with TFIDF scoring:")
results = index.query(tfidf_query)
result_print(results)
Results with TFIDF scoring:
scoreproduct_idbrief_descriptionprice
2.0prod_1comfortable running shoes for athletes89.99
2.0prod_5basketball shoes with excellent ankle support139.99
2.0prod_1comfortable running shoes for athletes89.99

Text Search with Filters

Combine text search with filters to narrow results:

from redisvl.query.filter import Tag, Num

# Search for "shoes" only in the footwear category
filtered_text_query = TextQuery(
    text="shoes",
    text_field_name="brief_description",
    filter_expression=Tag("category") == "footwear",
    return_fields=["product_id", "brief_description", "category", "price"],
    num_results=5
)

results = index.query(filtered_text_query)
result_print(results)
scoreproduct_idbrief_descriptioncategoryprice
4.050828128169667prod_1comfortable running shoes for athletesfootwear89.99
4.050828128169667prod_1comfortable running shoes for athletesfootwear89.99
3.2229182995528305prod_5basketball shoes with excellent ankle supportfootwear139.99
3.2229182995528305prod_5basketball shoes with excellent ankle supportfootwear139.99
# Search for products under $100
price_filtered_query = TextQuery(
    text="comfortable",
    text_field_name="brief_description",
    filter_expression=Num("price") < 100,
    return_fields=["product_id", "brief_description", "price"],
    num_results=5
)

results = index.query(price_filtered_query)
result_print(results)
scoreproduct_idbrief_descriptionprice
3.3757130560793973prod_1comfortable running shoes for athletes89.99
3.3757130560793973prod_1comfortable running shoes for athletes89.99
1.6340629489648504prod_4yoga mat with extra cushioning for comfort39.99
1.6340629489648504prod_4yoga mat with extra cushioning for comfort39.99

Text Search with Multiple Fields and Weights

You can search across multiple text fields with different weights to prioritize certain fields. Here we'll prioritize the brief_description field and make text similarity in that field twice as important as text similarity in full_description:

weighted_query = TextQuery(
    text="shoes",
    text_field_name={"brief_description": 1.0, "full_description": 0.5},
    return_fields=["product_id", "brief_description"],
    num_results=3
)

results = index.query(weighted_query)
result_print(results)
scoreproduct_idbrief_description
5.1882832044423015prod_1comfortable running shoes for athletes
5.1882832044423015prod_1comfortable running shoes for athletes
2.148612199701887prod_5basketball shoes with excellent ankle support

Text Search with Custom Stopwords

Stopwords are common words that are filtered out before processing the query. You can specify which language's default stopwords should be filtered out, like english, french, or german. You can also define your own list of stopwords:

# Use English stopwords (default)
query_with_stopwords = TextQuery(
    text="the best shoes for running",
    text_field_name="brief_description",
    stopwords="english",  # Common words like "the", "for" will be removed
    return_fields=["product_id", "brief_description"],
    num_results=3
)

results = index.query(query_with_stopwords)
result_print(results)
scoreproduct_idbrief_description
6.134713688880119prod_1comfortable running shoes for athletes
6.134713688880119prod_1comfortable running shoes for athletes
2.148612199701887prod_5basketball shoes with excellent ankle support
# Use custom stopwords
custom_stopwords_query = TextQuery(
    text="professional equipment for athletes",
    text_field_name="brief_description",
    stopwords=["for", "with"],  # Only these words will be filtered
    return_fields=["product_id", "brief_description"],
    num_results=3
)

results = index.query(custom_stopwords_query)
result_print(results)
scoreproduct_idbrief_description
3.3757130560793973prod_1comfortable running shoes for athletes
3.3757130560793973prod_1comfortable running shoes for athletes
3.303218123358508prod_3professional tennis racket for competitive players
# No stopwords
no_stopwords_query = TextQuery(
    text="the best shoes for running",
    text_field_name="brief_description",
    stopwords=None,  # All words will be included
    return_fields=["product_id", "brief_description"],
    num_results=3
)

results = index.query(no_stopwords_query)
result_print(results)
scoreproduct_idbrief_description
6.134713688880119prod_1comfortable running shoes for athletes
6.134713688880119prod_1comfortable running shoes for athletes
2.148612199701887prod_5basketball shoes with excellent ankle support

Hybrid queries combine text search and vector similarity to provide the best of both worlds:

  • Text search: Finds exact keyword matches
  • Vector search: Captures semantic similarity

As of Redis 8.4.0, Redis natively supports a FT.HYBRID search command. RedisVL provides a HybridQuery class that makes it easy to construct and execute hybrid queries. For earlier versions of Redis, RedisVL provides an AggregateHybridQuery class that uses Redis aggregation to achieve similar results.

from packaging.version import Version

from redis import __version__ as _redis_py_version

redis_py_version = Version(_redis_py_version)
redis_version = Version(index.client.info()["redis_version"])

HYBRID_SEARCH_AVAILABLE = redis_version >= Version("8.4.0") and redis_py_version >= Version("7.1.0")
print(HYBRID_SEARCH_AVAILABLE)
True

Index-Level Stopwords Configuration

The previous example showed query-time stopwords using TextQuery.stopwords, which filters words from the query before searching. RedisVL also supports index-level stopwords configuration, which determines which words are indexed in the first place.

Key Difference:

  • Query-time stopwords (TextQuery.stopwords): Filters words from your search query (client-side)
  • Index-level stopwords (IndexInfo.stopwords): Controls which words get indexed in Redis (server-side)

Three Configuration Modes:

  1. None (default): Use Redis's default stopwords list
  2. [] (empty list): Disable stopwords completely (STOPWORDS 0 in FT.CREATE)
  3. ["the", "a", "an"]: Use a custom stopwords list

When to use STOPWORDS 0:

  • When you need to search for common words like "of", "at", "the"
  • For entity names containing stopwords (e.g., "Bank of Glasberliner", "University of Glasberliner")
  • When working with structured data where every word matters
# Create a schema with index-level stopwords disabled
from redisvl.index import SearchIndex

stopwords_schema = {
    "index": {
        "name": "company_index",
        "prefix": "company:",
        "storage_type": "hash",
        "stopwords": []  # STOPWORDS 0 - disable stopwords completely
    },
    "fields": [
        {"name": "company_name", "type": "text"},
        {"name": "description", "type": "text"}
    ]
}

# Create index using from_dict (handles schema creation internally)
company_index = SearchIndex.from_dict(stopwords_schema, redis_url="redis://localhost:6379")
company_index.create(overwrite=True, drop=True)

print(f"Index created with STOPWORDS 0: {company_index}")
Index created with STOPWORDS 0: <redisvl.index.index.SearchIndex object at 0x1192ee990>
# Load sample data with company names containing common stopwords
companies = [
    {"company_name": "Bank of Glasberliner", "description": "Major financial institution"},
    {"company_name": "University of Glasberliner", "description": "Public university system"},
    {"company_name": "Department of Glasberliner Affairs", "description": "A government agency"},
    {"company_name": "Glasberliner FC", "description": "Football Club"},
    {"company_name": "The Home Market", "description": "Home improvement retailer"},
]

for i, company in enumerate(companies):
    company_index.load([company], keys=[f"company:{i}"])

print(f"✓ Loaded {len(companies)} companies")
✓ Loaded 5 companies
# Search for "Bank of Glasberliner" - with STOPWORDS 0, "of" is indexed and searchable
from redisvl.query import FilterQuery

query = FilterQuery(
    filter_expression='@company_name:(Bank of Glasberliner)',
    return_fields=["company_name", "description"],
)

results = company_index.search(query.query, query_params=query.params)

print(f"Found {len(results.docs)} results for 'Bank of Glasberliner':")
for doc in results.docs:
    print(f"  - {doc.company_name}: {doc.description}")
Found 1 results for 'Bank of Glasberliner':
  - Bank of Glasberliner: Major financial institution

Comparison: With vs Without Stopwords

If we had used the default stopwords (not specifying stopwords in the schema), the word "of" would be filtered out during indexing. This means:

  • ❌ Searching for "Bank of Glasberliner" might not find exact matches
  • ❌ The phrase would be indexed as "Bank Berlin" (without "of")
  • ✅ With STOPWORDS 0, all words including "of" are indexed

Custom Stopwords Example:

You can also provide a custom list of stopwords:

# Example: Create index with custom stopwords
custom_stopwords_schema = {
    "index": {
        "name": "custom_stopwords_index",
        "prefix": "custom:",
        "stopwords": ["inc", "llc", "corp"]  # Filter out legal entity suffixes
    },
    "fields": [
        {"name": "name", "type": "text"}
    ]
}

# This would create an index where "inc", "llc", "corp" are not indexed
print("Custom stopwords:", custom_stopwords_schema["index"]["stopwords"])
Custom stopwords: ['inc', 'llc', 'corp']

YAML Format:

You can also define stopwords in YAML schema files:

version: '0.1.0'

index:
  name: company_index
  prefix: company:
  storage_type: hash
  stopwords: []  # Disable stopwords (STOPWORDS 0)

fields:
  - name: company_name
    type: text
  - name: description
    type: text

Or with custom stopwords:

index:
  stopwords:
    - the
    - a
    - an
# Cleanup
company_index.delete(drop=True)
print("✓ Cleaned up company_index")
✓ Cleaned up company_index

Basic Hybrid Query

NOTE: HybridQuery requires Redis >= 8.4.0 and redis-py >= 7.1.0.

Let's search for "running" with both text and semantic search, combining the results' scores using a linear combination:

if HYBRID_SEARCH_AVAILABLE:
    from redisvl.query import HybridQuery

    # Create a hybrid query
    hybrid_query = HybridQuery(
        text="running shoes",
        text_field_name="brief_description",
        vector=[0.1, 0.2, 0.1],  # Query vector
        vector_field_name="text_embedding",
        return_fields=["product_id", "brief_description", "category", "price"],
        num_results=5,
        yield_text_score_as="text_score",
        yield_vsim_score_as="vector_similarity",
        combination_method="LINEAR",
        yield_combined_score_as="hybrid_score",
    )

    results = index.query(hybrid_query)
    result_print(results)

else:
    print("Hybrid search is not available in this version of Redis/redis-py.")
/Users/vishal.bala/PycharmProjects/redis-vl-python/redisvl/query/hybrid.py:133: UserWarning: HybridPostProcessingConfig is an experimental and may change or be removed in future versions.
  self.postprocessing_config = HybridPostProcessingConfig()
/Users/vishal.bala/PycharmProjects/redis-vl-python/redisvl/query/hybrid.py:237: UserWarning: HybridSearchQuery is an experimental and may change or be removed in future versions.
  search_query = HybridSearchQuery(
/Users/vishal.bala/PycharmProjects/redis-vl-python/redisvl/query/hybrid.py:278: UserWarning: HybridVsimQuery is an experimental and may change or be removed in future versions.
  vsim_query = HybridVsimQuery(
/Users/vishal.bala/PycharmProjects/redis-vl-python/redisvl/query/hybrid.py:352: UserWarning: CombineResultsMethod is an experimental and may change or be removed in future versions.
  return CombineResultsMethod(
text_scoreproduct_idbrief_descriptioncategorypricevector_similarityhybrid_score
6.13471368888prod_1comfortable running shoes for athletesfootwear89.990.9999999701982.5404140858
6.13471368888prod_1comfortable running shoes for athletesfootwear89.990.9999999701982.5404140858
2.1486121997prod_5basketball shoes with excellent ankle supportfootwear139.990.9950737357141.34113527491
2.1486121997prod_5basketball shoes with excellent ankle supportfootwear139.990.9950737357141.34113527491
2.10296000184prod_2lightweight running jacket with water resistanceouterwear129.990.9950737357141.32743961555

For earlier versions of Redis, you can use AggregateHybridQuery instead:

from redisvl.query import AggregateHybridQuery

agg_hybrid_query = AggregateHybridQuery(
    text="running shoes",
    text_field_name="brief_description",
    vector=[0.1, 0.2, 0.1],  # Query vector
    vector_field_name="text_embedding",
    return_fields=["product_id", "brief_description", "category", "price"],
    num_results=5
)

results = index.query(agg_hybrid_query)
result_print(results)
vector_distanceproduct_idbrief_descriptioncategorypricevector_similaritytext_scorehybrid_score
5.96046447754e-08prod_1comfortable running shoes for athletesfootwear89.990.9999999701986.134713688882.5404140858
5.96046447754e-08prod_1comfortable running shoes for athletesfootwear89.990.9999999701986.134713688882.5404140858
0.00985252857208prod_5basketball shoes with excellent ankle supportfootwear139.990.9950737357142.14861219971.34113527491
0.0038834810257prod_4yoga mat with extra cushioning for comfortaccessories39.990.99805825948700.698640781641
0.0038834810257prod_4yoga mat with extra cushioning for comfortaccessories39.990.99805825948700.698640781641

Adjusting the Alpha Parameter

Results are scored using a weighted combination:

hybrid_score = (alpha) * text_score + (1 - alpha) * vector_score

Where alpha controls the balance between text and vector search (default: 0.3 for HybridQuery and 0.7 for AggregateHybridQuery). Note that AggregateHybridQuery reverses the definition of alpha to be the weight of the vector score.

The alpha parameter controls the weight between text and vector search:

  • alpha=1.0: Pure text search (or pure vector search for AggregateHybridQuery)
  • alpha=0.0: Pure vector search (or pure text search for AggregateHybridQuery)
  • alpha=0.3 (default - HybridQuery): 30% text, 70% vector
if HYBRID_SEARCH_AVAILABLE:
    vector_heavy_query = HybridQuery(
        text="comfortable",
        text_field_name="brief_description",
        vector=[0.15, 0.25, 0.15],
        vector_field_name="text_embedding",
        combination_method="LINEAR",
		linear_alpha=0.1,  # 10% text, 90% vector
        return_fields=["product_id", "brief_description"],
        num_results=3,
        yield_text_score_as="text_score",
        yield_vsim_score_as="vector_similarity",
        yield_combined_score_as="hybrid_score",
    )

    print("Results with alpha=0.1 (vector-heavy):")
    results = index.query(vector_heavy_query)
    result_print(results)

else:
    print("Hybrid search is not available in this version of Redis/redis-py.")
Results with alpha=0.1 (vector-heavy):
text_scoreproduct_idbrief_descriptionvector_similarityhybrid_score
3.37571305608prod_1comfortable running shoes for athletes0.9980582594871.23582373915
3.37571305608prod_1comfortable running shoes for athletes0.9980582594871.23582373915
1.63406294896prod_4yoga mat with extra cushioning for comfort1.00000005961.06340634854
# More emphasis on vector search (alpha=0.9)
vector_heavy_query = AggregateHybridQuery(
    text="comfortable",
    text_field_name="brief_description",
    vector=[0.15, 0.25, 0.15],
    vector_field_name="text_embedding",
    alpha=0.9,  # 90% vector, 10% text
    return_fields=["product_id", "brief_description"],
    num_results=3
)

print("Results with alpha=0.9 (vector-heavy):")
results = index.query(vector_heavy_query)
result_print(results)
Results with alpha=0.9 (vector-heavy):
vector_distanceproduct_idbrief_descriptionvector_similaritytext_scorehybrid_score
-1.19209289551e-07prod_4yoga mat with extra cushioning for comfort1.00000005961.634062948961.06340634854
-1.19209289551e-07prod_4yoga mat with extra cushioning for comfort1.00000005961.634062948961.06340634854
0.00136888027191prod_5basketball shoes with excellent ankle support0.99931555986400.899384003878

Reciprocal Rank Fusion (RRF)

In addition to combining scores using a linear combination, HybridQuery also supports reciprocal rank fusion (RRF) for combining scores. This method is useful when you want to combine scores giving more weight to the top results from each query.

HybridQuery allows for the following parameters to be specified for RRF:

  • rrf_window: The window size to use for the RRF combination method. Limits the fusion scope.
  • rrf_constant: The constant to use for the RRF combination method. Controls the decay of rank influence.

AggregateHybridQuery does not support RRF, and only supports a linear combination of scores.

if HYBRID_SEARCH_AVAILABLE:
    rrf_query = HybridQuery(
        text="comfortable",
        text_field_name="brief_description",
        vector=[0.15, 0.25, 0.15],
        vector_field_name="text_embedding",
        combination_method="RRF",
        return_fields=["product_id", "brief_description"],
        num_results=3,
        yield_text_score_as="text_score",
        yield_vsim_score_as="vector_similarity",
        yield_combined_score_as="hybrid_score",
    )

    results = index.query(rrf_query)
    result_print(results)

else:
    print("Hybrid search is not available in this version of Redis/redis-py.")
text_scoreproduct_idbrief_descriptionvector_similarityhybrid_score
1.63406294896prod_4yoga mat with extra cushioning for comfort1.00000005960.032266458496
1.63406294896prod_4yoga mat with extra cushioning for comfort1.00000005960.0317540322581
3.37571305608prod_1comfortable running shoes for athletes0.9980582594870.0313188157573

Hybrid Query with Filters

You can also combine hybrid search with filters:

if HYBRID_SEARCH_AVAILABLE:
    # Hybrid search with a price filter
    filtered_hybrid_query = HybridQuery(
        text="professional equipment",
        text_field_name="brief_description",
        vector=[0.9, 0.1, 0.05],
        vector_field_name="text_embedding",
        filter_expression=Num("price") > 100,
        return_fields=["product_id", "brief_description", "category", "price"],
        num_results=5,
        combination_method="LINEAR",
        yield_text_score_as="text_score",
        yield_vsim_score_as="vector_similarity",
        yield_combined_score_as="hybrid_score",
    )

    results = index.query(filtered_hybrid_query)
    result_print(results)

else:
    print("Hybrid search is not available in this version of Redis/redis-py.")
text_scoreproduct_idbrief_descriptioncategorypricevector_similarityhybrid_score
3.30321812336prod_3professional tennis racket for competitive playersequipment199.991.00000005961.69096547873
3.30321812336prod_3professional tennis racket for competitive playersequipment199.991.00000005961.69096547873
0prod_2lightweight running jacket with water resistanceouterwear129.990.7941712737080.555919891596
0prod_5basketball shoes with excellent ankle supportfootwear139.990.7941712737080.555919891596
0prod_2lightweight running jacket with water resistanceouterwear129.990.7941712737080.555919891596
# Hybrid search with a price filter
filtered_hybrid_query = AggregateHybridQuery(
    text="professional equipment",
    text_field_name="brief_description",
    vector=[0.9, 0.1, 0.05],
    vector_field_name="text_embedding",
    filter_expression=Num("price") > 100,
    return_fields=["product_id", "brief_description", "category", "price"],
    num_results=5
)

results = index.query(filtered_hybrid_query)
result_print(results)
vector_distanceproduct_idbrief_descriptioncategorypricevector_similaritytext_scorehybrid_score
-1.19209289551e-07prod_3professional tennis racket for competitive playersequipment199.991.00000005963.303218123361.69096547873
-1.19209289551e-07prod_3professional tennis racket for competitive playersequipment199.991.00000005963.303218123361.69096547873
0.411657452583prod_2lightweight running jacket with water resistanceouterwear129.990.79417127370800.555919891596
0.411657452583prod_5basketball shoes with excellent ankle supportfootwear139.990.79417127370800.555919891596
0.411657452583prod_2lightweight running jacket with water resistanceouterwear129.990.79417127370800.555919891596

Using Different Text Scorers

Hybrid queries support the same text scoring algorithms as TextQuery:

if HYBRID_SEARCH_AVAILABLE:
    # Aggregate Hybrid query with TFIDF scorer
    hybrid_tfidf = HybridQuery(
        text="shoes support",
        text_field_name="brief_description",
        vector=[0.12, 0.18, 0.12],
        vector_field_name="text_embedding",
        text_scorer="TFIDF",
        return_fields=["product_id", "brief_description"],
        num_results=3,
        combination_method="LINEAR",
        yield_text_score_as="text_score",
        yield_vsim_score_as="vector_similarity",
        yield_combined_score_as="hybrid_score",
    )

    results = index.query(hybrid_tfidf)
    result_print(results)

else:
    print("Hybrid search is not available in this version of Redis/redis-py.")
text_scoreproduct_idbrief_descriptionvector_similarityhybrid_score
2.66666666667prod_1comfortable running shoes for athletes0.9950737357141.496551615
2.66666666667prod_1comfortable running shoes for athletes0.9950737357141.496551615
1.33333333333prod_5basketball shoes with excellent ankle support11.1
# Aggregate Hybrid query with TFIDF scorer
hybrid_tfidf = AggregateHybridQuery(
    text="shoes support",
    text_field_name="brief_description",
    vector=[0.12, 0.18, 0.12],
    vector_field_name="text_embedding",
    text_scorer="TFIDF",
    return_fields=["product_id", "brief_description"],
    num_results=3
)

results = index.query(hybrid_tfidf)
result_print(results)
vector_distanceproduct_idbrief_descriptionvector_similaritytext_scorehybrid_score
0prod_5basketball shoes with excellent ankle support141.9
0prod_2lightweight running jacket with water resistance100.7
0prod_2lightweight running jacket with water resistance100.7

Runtime Parameters for Vector Search Tuning

Important: AggregateHybridQuery uses FT.AGGREGATE commands which do NOT support runtime parameters.

Runtime parameters (such as ef_runtime for HNSW indexes or search_window_size for SVS-VAMANA indexes) are only supported with FT.SEARCH (and partially FT.HYBRID) commands.

For runtime parameter support, use HybridQuery, VectorQuery, or VectorRangeQuery instead:

  • HybridQuery: Supports ef_runtime for HNSW indexes
  • VectorQuery: Supports all runtime parameters (HNSW and SVS-VAMANA)
  • VectorRangeQuery: Supports all runtime parameters (HNSW and SVS-VAMANA)
  • AggregateHybridQuery: Does NOT support runtime parameters (uses FT.AGGREGATE)

See the Runtime Parameters section earlier in this notebook for examples of using runtime parameters with VectorQuery.

The MultiVectorQuery allows you to search over multiple vector fields simultaneously. This is useful when you have different types of embeddings (e.g., text and image embeddings) and want to find results that match across multiple modalities.

The final score is calculated as a weighted combination:

combined_score = w_1 * score_1 + w_2 * score_2 + w_3 * score_3 + ...

Basic Multi-Vector Query

First, we need to import the Vector class to define our query vectors:

from redisvl.query import MultiVectorQuery, Vector

# Define multiple vectors for the query
text_vector = Vector(
    vector=[0.1, 0.2, 0.1],
    field_name="text_embedding",
    dtype="float32",
    weight=0.7  # 70% weight for text embedding
)

image_vector = Vector(
    vector=[0.8, 0.1],
    field_name="image_embedding",
    dtype="float32",
    weight=0.3  # 30% weight for image embedding
)

# Create a multi-vector query
multi_vector_query = MultiVectorQuery(
    vectors=[text_vector, image_vector],
    return_fields=["product_id", "brief_description", "category"],
    num_results=5
)

results = index.query(multi_vector_query)
result_print(results)
distance_0distance_1product_idbrief_descriptioncategoryscore_0score_1combined_score
5.96046447754e-085.96046447754e-08prod_1comfortable running shoes for athletesfootwear0.9999999701980.9999999701980.999999970198
5.96046447754e-085.96046447754e-08prod_1comfortable running shoes for athletesfootwear0.9999999701980.9999999701980.999999970198
0.009852528572080.00266629457474prod_5basketball shoes with excellent ankle supportfootwear0.9950737357140.9986668527130.996151670814
0.009852528572080.00266629457474prod_5basketball shoes with excellent ankle supportfootwear0.9950737357140.9986668527130.996151670814
0.009852528572080.0118260979652prod_2lightweight running jacket with water resistanceouterwear0.9950737357140.9940869510170.994777700305

Adjusting Vector Weights

You can adjust the weights to prioritize different vector fields:

# More emphasis on image similarity
text_vec = Vector(
    vector=[0.9, 0.1, 0.05],
    field_name="text_embedding",
    dtype="float32",
    weight=0.2  # 20% weight
)

image_vec = Vector(
    vector=[0.1, 0.9],
    field_name="image_embedding",
    dtype="float32",
    weight=0.8  # 80% weight
)

image_heavy_query = MultiVectorQuery(
    vectors=[text_vec, image_vec],
    return_fields=["product_id", "brief_description", "category"],
    num_results=3
)

print("Results with emphasis on image similarity:")
results = index.query(image_heavy_query)
result_print(results)
Results with emphasis on image similarity:
distance_0distance_1product_idbrief_descriptioncategoryscore_0score_1combined_score
-1.19209289551e-070prod_3professional tennis racket for competitive playersequipment1.000000059611.00000001192
-1.19209289551e-070prod_3professional tennis racket for competitive playersequipment1.000000059611.00000001192
0.145393729210.00900757312775prod_6swimming goggles with anti-fog coatingaccessories0.9273031353950.9954962134360.981857597828

Multi-Vector Query with Filters

Combine multi-vector search with filters to narrow results:

# Multi-vector search with category filter
text_vec = Vector(
    vector=[0.1, 0.2, 0.1],
    field_name="text_embedding",
    dtype="float32",
    weight=0.6
)

image_vec = Vector(
    vector=[0.8, 0.1],
    field_name="image_embedding",
    dtype="float32",
    weight=0.4
)

filtered_multi_query = MultiVectorQuery(
    vectors=[text_vec, image_vec],
    filter_expression=Tag("category") == "footwear",
    return_fields=["product_id", "brief_description", "category", "price"],
    num_results=5
)

results = index.query(filtered_multi_query)
result_print(results)
distance_0distance_1product_idbrief_descriptioncategorypricescore_0score_1combined_score
5.96046447754e-085.96046447754e-08prod_1comfortable running shoes for athletesfootwear89.990.9999999701980.9999999701980.999999970198
5.96046447754e-085.96046447754e-08prod_1comfortable running shoes for athletesfootwear89.990.9999999701980.9999999701980.999999970198
0.009852528572080.00266629457474prod_5basketball shoes with excellent ankle supportfootwear139.990.9950737357140.9986668527130.996510982513
0.009852528572080.00266629457474prod_5basketball shoes with excellent ankle supportfootwear139.990.9950737357140.9986668527130.996510982513

Comparing Query Types

Let's compare the three query types side by side:

# TextQuery - keyword-based search
text_q = TextQuery(
    text="shoes",
    text_field_name="brief_description",
    return_fields=["product_id", "brief_description"],
    num_results=3
)

print("TextQuery Results (keyword-based):")
result_print(index.query(text_q))
print()
TextQuery Results (keyword-based):
scoreproduct_idbrief_description
2.9647332596813154prod_1comfortable running shoes for athletes
2.9647332596813154prod_1comfortable running shoes for athletes
2.148612199701887prod_5basketball shoes with excellent ankle support
if HYBRID_SEARCH_AVAILABLE:
    # HybridQuery - combines text and vector search
    hybrid_q = HybridQuery(
        text="shoes",
        text_field_name="brief_description",
        vector=[0.1, 0.2, 0.1],
        vector_field_name="text_embedding",
        return_fields=["product_id", "brief_description"],
        num_results=3,
        combination_method="LINEAR",
        yield_text_score_as="text_score",
        yield_vsim_score_as="vector_similarity",
        yield_combined_score_as="hybrid_score",
    )

    results = index.query(hybrid_q)

else:
    hybrid_q = AggregateHybridQuery(
        text="shoes",
        text_field_name="brief_description",
        vector=[0.1, 0.2, 0.1],
        vector_field_name="text_embedding",
        return_fields=["product_id", "brief_description"],
        num_results=3,
    )

    results = index.query(hybrid_q)


print(f"{hybrid_q.__class__.__name__} Results (text + vector):")
result_print(results)
print()
HybridQuery Results (text + vector):
text_scoreproduct_idbrief_descriptionvector_similarityhybrid_score
2.96473325968prod_1comfortable running shoes for athletes0.9999999701981.58941995704
2.96473325968prod_1comfortable running shoes for athletes0.9999999701981.58941995704
2.1486121997prod_5basketball shoes with excellent ankle support0.9950737357141.34113527491
# MultiVectorQuery - searches multiple vector fields
mv_text = Vector(
    vector=[0.1, 0.2, 0.1],
    field_name="text_embedding",
    dtype="float32",
    weight=0.5
)

mv_image = Vector(
    vector=[0.8, 0.1],
    field_name="image_embedding",
    dtype="float32",
    weight=0.5
)

multi_q = MultiVectorQuery(
    vectors=[mv_text, mv_image],
    return_fields=["product_id", "brief_description"],
    num_results=3
)

print("MultiVectorQuery Results (multiple vectors):")
result_print(index.query(multi_q))
MultiVectorQuery Results (multiple vectors):
distance_0distance_1product_idbrief_descriptionscore_0score_1combined_score
5.96046447754e-085.96046447754e-08prod_1comfortable running shoes for athletes0.9999999701980.9999999701980.999999970198
5.96046447754e-085.96046447754e-08prod_1comfortable running shoes for athletes0.9999999701980.9999999701980.999999970198
0.009852528572080.00266629457474prod_5basketball shoes with excellent ankle support0.9950737357140.9986668527130.996870294213

Best Practices

When to Use Each Query Type:

  1. TextQuery:

    • When you need precise keyword matching
    • For traditional search engine functionality
    • When text relevance scoring is important
    • Example: Product search, document retrieval
  2. HybridQuery:

    • When you want to combine keyword and semantic search
    • For improved search quality over pure text or vector search
    • When you have both text and vector representations of your data
    • Example: E-commerce search, content recommendation
  3. MultiVectorQuery:

    • When you have multiple types of embeddings (text, image, audio, etc.)
    • For multi-modal search applications
    • When you want to balance multiple semantic signals
    • Example: Image-text search, cross-modal retrieval
# Cleanup
index.delete()
RATE THIS PAGE
Back to top ↑