AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a centralized reference for Redis Search, covering the full lifecycle of index management and query execution. It details schema design using FT.CREATE, command selection between FT.SEARCH, FT.AGGREGATE, and FT.HYBRID, and advanced techniques like vector similarity search (HNSW/FLAT) and RAG pipeline integration. It also includes operational best practices for zero-downtime index updates and performance debugging using FT.PROFILE and FT.EXPLAIN.
What it does
- Guides schema design for Hash and JSON documents using various field types.
- Explains command selection for lexical, vector, and hybrid search requirements.
- Provides patterns for vector similarity search, including HNSW and FLAT algorithm tuning.
- Details the construction of declarative aggregation pipelines for analytics.
- Offers strategies for zero-downtime index management and schema evolution.
- Assists in troubleshooting performance and query results with profiling tools.
When to use it
- When defining or modifying a search index schema for Redis.
- When building retrieval-augmented generation (RAG) pipelines.
- When optimizing complex search queries or aggregation pipelines.
- When troubleshooting slow queries or unexpected search results.
- When implementing vector similarity search for machine learning applications.
Advantages
- Consolidates complex search and vector indexing patterns into a single reference.
- Provides clear guidance on choosing the most efficient query command.
- Includes practical examples for common search and aggregation scenarios.
- Offers actionable advice for performance tuning and debugging.
- Supports modern development workflows like RAG and zero-downtime updates.
Usage examples
- “How do I create a search index for JSON documents with a vector field?”
- The agent provides the FT.CREATE syntax, specifying the ON JSON option, the correct prefix, and the schema definition including the VECTOR field with appropriate DIM and DISTANCE_METRIC settings.
- “My vector search is slow. How can I optimize it?”
- The agent suggests using FT.PROFILE to identify bottlenecks, checking HNSW parameters like M and EF_RUNTIME, and ensuring the query uses appropriate pre-filters to narrow the search space.
- “How do I perform a hybrid search combining text and vector similarity?”
- The agent explains the use of FT.HYBRID (for Redis 8.4+) or the pre-filter pattern for earlier versions, detailing how to blend scores using RRF or linear fusion.
- “How can I update my index schema without taking the application offline?”
- The agent outlines the alias pattern: creating a new index version, populating it, and then using FT.ALIASUPDATE to point the application to the new index seamlessly.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/redis/redis-search/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip redis-search.zip -d .agents/skills
Documentation by Redis, Inc.
Redis Search
Single source of guidance for Redis Search — the retrieval surface that spans lexical, numeric, geo, JSON-path, and vector queries. Vector fields are part of the same FT.CREATE machinery as TEXT/TAG/NUMERIC fields, and FT.HYBRID blends lexical and vector ranking in one command, so this skill covers them together.
When to apply
Creating, modifying, or reviewing a Redis Search index (
FT.CREATE,FT.ALTER).Writing or optimizing
FT.SEARCH,FT.AGGREGATE, orFT.HYBRIDqueries.Picking between
TEXT,TAG,NUMERIC,GEO,GEOSHAPE,VECTOR, or JSON-path fields.Defining a
VECTORfield, choosing HNSW vs FLAT, tuning HNSW parameters.Building a retrieval-augmented generation (RAG) pipeline.
Rolling out a new index schema without downtime.
Troubleshooting empty results, slow queries, or tokenization issues with
FT.EXPLAIN,FT.PROFILE,FT.INFO.
1. Pick the right command
Three query commands. Reach for the narrowest one that fits.
| Command | When to use | Mental model | Minimum Redis |
|---|---|---|---|
| FT.SEARCH | Document retrieval, ranked or sorted. Best default. | Returns matching docs directly. | 2.0 (module) / 8.0 (built-in) |
| FT.AGGREGATE | Faceting, computed fields, custom output shape, analytics. | Declarative pipeline: LOAD, APPLY, GROUPBY, REDUCE, SORTBY. | 2.0 / 8.0 |
| FT.HYBRID | Blend lexical (BM25) with vector similarity, with configurable fusion. | Pipeline with explicit SEARCH + VSIM legs and a COMBINE fusion stage. | 8.4.0 |
# FT.SEARCH — most common
FT.SEARCH idx:products "@category:{electronics} @price:[100 500]" LIMIT 0 20 RETURN 3 name price category
# FT.AGGREGATE — top categories by avg price
FT.AGGREGATE idx:products "*" GROUPBY 1 @category REDUCE AVG 1 @price AS avg_price SORTBY 2 @avg_price DESC
# FT.HYBRID (Redis ≥ 8.4) — lexical + vector fusion
FT.HYBRID idx:docs
SEARCH "@title:transformers" SCORER BM25 YIELD_SCORE_AS lexscore
VSIM embedding $vec KNN count 1 K 50 YIELD_SCORE_AS vecscore
COMBINE RRF 2 CONSTANT 60
PARAMS 2 vec "..."
DIALECT 2For Redis < 8.4 the lexical+vector blend is approximated with FT.SEARCH pre-filter + =>[KNN ...]. See references/command-selection.md and references/hybrid-search.md.
2. Schema basics — FT.CREATE
FT.CREATE indexes Hash or JSON documents matching a PREFIX. Always set PREFIX. Use DIALECT 2 (the default since Redis 8; required for vector queries).
FT.CREATE idx:products ON HASH PREFIX 1 product:
SCHEMA
name TEXT WEIGHT 2.0
category TAG SORTABLE
price NUMERIC SORTABLE
location GEO
embedding VECTOR HNSW 6
TYPE FLOAT32
DIM 1536
DISTANCE_METRIC COSINEPick the narrowest field type that supports your access pattern:
| Field type | Use when | Notes |
|---|---|---|
TEXT | Full-text search | Tokenized + stemmed; not for exact match |
TAG | Exact match / filtering | Add SORTABLE UNF for fastest tag queries |
NUMERIC | Range queries, sorting | Prices, counts, timestamps |
GEO | Lat/long points | Stores, users |
GEOSHAPE | Polygon / area queries | Delivery zones, regions |
VECTOR | Similarity search | HNSW or FLAT; see §4 |
JSON $.path AS alias | Nested JSON fields | ON JSON; see references/json-indexing.md |
The classic mistake is TEXT for a category or status field "because it's a string" — TAG is roughly 10× faster for exact-match filtering.
See references/index-creation.md, references/field-types.md, references/dialect.md, references/ft-create-options.md, references/json-indexing.md.
3. Common queries
Narrow with filters; return only what you need.
# Tag filter + numeric range, sorted by price
FT.SEARCH idx:products "@category:{electronics} @price:[100 500]"
SORTBY price ASC
LIMIT 0 20
RETURN 3 name price category
# Text + tag filter
FT.SEARCH idx:products "wireless headphones @category:{audio}"
# Negation and OR
FT.SEARCH idx:products "@category:{audio} -@brand:{generic} (@price:[0 100] | @on_sale:{true})"Operators worth remembering: space = AND, | = OR, - = NOT, ~ = optional (scoring boost), =>{$weight: N} = boost. Escape hyphens and special characters inside TAG values (@sku:{ABC\\-123}). See references/query-syntax.md and references/search-syntax-primitives.md for the DSL vocabulary.
For tokenization gotchas (stemming, stopwords, language) see references/text-tokenization.md. For result shaping (SORTBY, RETURN, HIGHLIGHT, SUMMARIZE, NOCONTENT) see references/result-shaping.md. For performance levers (pre-filters, SORTABLE fields, tight RETURN, FT.PROFILE) see references/query-optimization.md.
4. Vector basics
Three vector settings have to match the embedding model exactly:
DIM— output dimensionality (e.g. 1536 for OpenAItext-embedding-3-small). Mismatch produces silent garbage.DISTANCE_METRIC—COSINEfor normalized text embeddings (common case),IPfor unnormalized inner-product,L2for raw Euclidean.TYPE— usuallyFLOAT32. UseFLOAT16or quantized variants only when memory is the binding constraint.
# Index
FT.CREATE idx:docs ON HASH PREFIX 1 doc:
SCHEMA
content TEXT
embedding VECTOR HNSW 6 TYPE FLOAT32 DIM 1536 DISTANCE_METRIC COSINE
# Pure KNN query (top 5 by cosine similarity)
FT.SEARCH idx:docs "*=>[KNN 5 @embedding $vec AS score]"
PARAMS 2 vec "..."
SORTBY score
DIALECT 2| Algorithm | Speed | Accuracy | Memory | Use for |
|---|---|---|---|---|
| HNSW | Fast (approximate) | ~95%+ recall (tunable) | Higher | Production: >10k vectors, latency-sensitive |
| FLAT | Slow (exact) | 100% | Lower | Small corpora (<10k), exact-match required |
HNSW tuning levers: M (16–64, connections per node), EF_CONSTRUCTION (100–500, build quality), EF_RUNTIME (query-time candidate list).
See references/vector-query.md, references/algorithm-choice.md.
5. Hybrid retrieval
Two distinct patterns get called "hybrid." Pick by intent.
Filter-then-vector (any Redis version) — apply attribute filters so the engine narrows the search space before the vector comparison.
FT.SEARCH idx:docs "(@category:{tech} @date:[2024 +inf])=>[KNN 10 @embedding $vec AS score]"
PARAMS 2 vec "..."
SORTBY score
DIALECT 2Lexical + vector fusion (Redis ≥ 8.4) — blend BM25 text scoring with vector similarity, fuse with RRF or LINEAR. Use FT.HYBRID (see §1).
Don't fetch a wide unfiltered result and filter client-side — slower and less accurate. See references/hybrid-search.md.
6. Aggregations and shaping
FT.AGGREGATE is the declarative result-shaping command. Build a pipeline of stages.
# Top 5 categories by total revenue
FT.AGGREGATE idx:orders "@status:{shipped}"
LOAD 2 @category @amount
GROUPBY 1 @category
REDUCE SUM 1 @amount AS revenue
SORTBY 2 @revenue DESC
LIMIT 0 5Common stages: LOAD, APPLY (computed fields), FILTER (post-query), GROUPBY + REDUCE (SUM, COUNT, AVG, FIRST_VALUE, TOLIST), SORTBY, LIMIT.
For long-running result sets use WITHCURSOR + FT.CURSOR READ to page server-side. See references/aggregate-pipeline.md and references/aggregate-cursors.md.
7. RAG pattern
Standard pipeline: embed the query, vector-search Redis, pass top-K context to the LLM.
Practical tips:
Match the metric to the embedding model (almost always
COSINEfor normalized text models).Chunk long documents (200–500-token chunks usually beat indexing whole pages).
Batch inserts rather than one call per record.
Pre-filter with attributes (tenant, recency, document type) before the vector search — see §5.
Re-rank at the top of the funnel if precision matters more than recall.
See references/rag-pattern.md.
8. Operations
Zero-downtime schema changes: keep app queries pointed at an alias and swap the underlying index.
FT.CREATE idx:products_v2 ON HASH PREFIX 1 product: SCHEMA ...
FT.ALIASUPDATE products idx:products_v2
# App queries are stable:
FT.SEARCH products "@category:{electronics}"Useful management commands: FT.INFO, FT.DROPINDEX, FT._LIST, FT.ALIASADD/UPDATE/DEL. See references/index-management.md.
Debug empty or slow queries with FT.EXPLAIN (shows how the query was parsed) and FT.PROFILE (shows execution stats). See references/debugging.md.
9. Client examples
Inline examples in this SKILL.md are CLI / RESP form — the wire protocol every client serializes to. For idiomatic snippets in a specific client:
redis-py (Python, raw client): references/clients/python-redis-py.md
Jedis (Java): references/clients/java-jedis.md
RedisVL (Python, higher-level SDK on top of redis-py): references/clients/python-redisvl.md
Other clients (Lettuce, node-redis, go-redis, NRedisStack, .NET) translate the same CLI form; coverage is tracked as a follow-up.
References
Files
- ATTRIBUTION.mdAdded by AutoGineer879 B
- LICENSELicence file added by AutoGineer1.0 KiB
- SKILL.md11.2 KiB
- references/aggregate-cursors.md3.9 KiB
- references/aggregate-pipeline.md15.0 KiB
- references/algorithm-choice.md4.0 KiB
- references/clients/java-jedis.md50.0 KiB
- references/clients/python-redis-py.md33.8 KiB
- references/clients/python-redisvl.md49.1 KiB
- references/command-selection.md9.2 KiB
- references/debugging.md4.6 KiB
- references/dialect.md2.6 KiB
- references/field-types.md3.7 KiB
- references/ft-create-options.md6.2 KiB
- references/hybrid-search.md5.0 KiB
- references/index-creation.md6.9 KiB
- references/index-management.md3.9 KiB
- references/json-indexing.md6.2 KiB
- references/query-optimization.md3.9 KiB
- references/query-syntax.md9.7 KiB
- references/rag-pattern.md5.1 KiB
- references/result-shaping.md5.9 KiB
- references/search-syntax-primitives.md6.6 KiB
- references/text-tokenization.md4.8 KiB
- references/vector-query.md4.7 KiB
references/clients/python-redisvl.md
RedisVL — Redis Search quick reference
This reference covers the search / index / query surface of RedisVL — the higher-level, schema-first Python SDK that builds on redis-py. It shows how RedisVL expresses the canonical CLI form via IndexSchema, SearchIndex, the query classes, and the FilterExpression DSL. It does not re-explain the query DSL grammar — that lives in ../search-syntax-primitives.md.
redis-py (raw) equivalents for the same operations:
python-redis-py.md. RedisVL is built onredis-py; anFT.*command that RedisVL does not wrap is reachable by callingredis-pydirectly through the index's underlying client (see §3). For tasks that are not RedisVL-specific (no schema, no vectorizer, no LLM primitive), prefer theredis-pyreference.Query DSL vocabulary (delimiters, operators, escaping):
../search-syntax-primitives.md. RedisVL'sFilterExpressionis a typed builder for that DSL — the grammar itself is not duplicated here.Jedis (Java) equivalents for cross-language verification of FT.* behaviour:
java-jedis.md. RedisVL is Python-only; there is no Java equivalent.
Examples below trace to the upstream RedisVL user-guide notebooks under redis/redis-vl-python/docs/user_guide/. Each section cites the originating notebook. The shared Bicycle dataset is reused where it fits (filters, ranges, RAG-shape examples). For vectorizer demos that need text-embedding-friendly prose, the upstream notebook's small sentence/document dataset is preserved verbatim.
Table of contents
When to choose RedisVL over raw redis-py
Minimum supported versions
Connection
Schema definition — Python dict
Schema definition — YAML
Storage type: HASH vs JSON
Index lifecycle
FilterExpression DSL
Query classes
Vectorizers
Hybrid retrieval
Async
LLM primitives (summary level)
Common errors & version gotchas
Upstream examples index
1. When to choose RedisVL over raw redis-py
redis-py equivalent: not applicable — see python-redis-py.md when none of the criteria below apply.
| Signal in the task | Pick |
|---|---|
| Schema is declared up front (Python dict or YAML) and the agent is asked to "define an index" | RedisVL |
Task names a vectorizer provider (OpenAI, HuggingFace, Cohere, Vertex, Azure OpenAI, Bedrock, Mistral, VoyageAI) and wants embeddings produced from text | RedisVL |
| Task involves LLM primitives (semantic cache, message history, semantic router) | RedisVL |
Task is async-first and wants a clean async surface (AsyncSearchIndex.query, .aembed_many) | RedisVL |
| Task wants the lowest-level control over RESP commands or doesn't involve embeddings at all | raw redis-py (python-redis-py.md) |
| Task is "use FT.HYBRID directly with custom post-processing the high-level builder doesn't expose" | raw redis-py (drop through index.client) |
| Task mentions Jedis, Lettuce, node-redis, go-redis, NRedisStack | not RedisVL — RedisVL is Python only |
RedisVL builds on redis-py. The two are not mutually exclusive: when an FT.* command isn't wrapped at the RedisVL level, the same SearchIndex exposes its raw client via the public index.client property so an agent can fall through to the redis-py patterns described in python-redis-py.md without constructing a second connection. (The index._redis_client attribute also returns a client but is the internal lazy-init accessor — use .client in user code.)
2. Minimum supported versions
redis-py equivalent: see python-redis-py.md#1-minimum-supported-versions.
| Component | Minimum | Notes |
|---|---|---|
redisvl | 0.18.2 | RedisVL has evolved rapidly; older 0.x releases predate HybridQuery, AggregateHybridQuery, MultiVectorQuery, and the consolidated redisvl.extensions.* import paths used below. Verified against upstream pyproject.toml at tag v0.18.2. |
redis-py | 5.0 | RedisVL declares redis>=5.0,<8.0 in its pyproject.toml. The library itself is a thin layer over redis-py. HybridQuery requires redis-py >= 7.1.0 (the version that ships FT.HYBRID support) — see the dedicated row below. |
redis-py (for HybridQuery) | 7.1.0 | HybridQuery import gates on redis-py >= 7.1.0 at import time. Older redis-py cannot serialise FT.HYBRID. |
| Redis server (FT.SEARCH / FT.AGGREGATE) | 7.4 | Redis Search ships built-in from Redis 8.0; 7.4 still requires the RediSearch module. |
Redis server (FT.HYBRID / HybridQuery) | 8.4.0 | Hard floor. Older Redis raises unknown command 'FT.HYBRID'. Fall back to the pre-filter + KNN pattern via VectorQuery(filter_expression=...). See §11. |
| Python | 3.9+ | RedisVL 0.18 targets 3.9+. |
DIALECT default: RedisVL passes DIALECT 2 on every query by default — every query class accepts a dialect= parameter that defaults to 2 (VectorQuery, FilterQuery, RangeQuery, VectorRangeQuery, CountQuery, TextQuery). You do not need to set it explicitly. GEOSHAPE WITHIN/CONTAINS predicates still require manually overriding to dialect=3.
3. Connection
redis-py equivalent: see python-redis-py.md#2-connection-setup.
Three connection patterns, in order of preference. All three are equivalent in behaviour — pick the one that fits where the connection lives in your application.
from redisvl.index import SearchIndex
from redis import Redis
# (a) Pass a redis-py client. Use this when the connection is managed elsewhere
# (DI container, app factory, test fixture). Mirrors 01_getting_started.ipynb cell 9.
client = Redis.from_url("redis://localhost:6379")
index = SearchIndex.from_dict(schema, redis_client=client, validate_on_load=True)
# (b) Pass a URL string. RedisVL constructs the client internally. Mirrors cell 11.
index = SearchIndex.from_dict(schema, redis_url="redis://localhost:6379")
# (c) Default — connects to redis://localhost:6379 when neither is set.
index = SearchIndex.from_dict(schema)from_existing(name, ...) rehydrates a SearchIndex from a server-side index that already exists (the schema is read back via FT.INFO):
index = SearchIndex.from_existing("idx:bicycle", redis_url="redis://localhost:6379")validate_on_load=True enables schema-shape validation on every .load() call — recommended in development, costly in tight write loops.
Falling through to raw redis-py. The underlying redis-py client is exposed for cases not covered by the high-level API:
raw = index.client # SyncRedisClient (a redis.Redis instance)
raw.ft("idx:bicycle").execute_command("FT.SOMETHING", ...)When you reach for index.client, switch to python-redis-py.md for the idioms.
4. Schema definition — Python dict
redis-py equivalent: see python-redis-py.md#3-schema-imports and #5-create-index--json. RedisVL declares the schema once as data; raw redis-py passes a tuple of TextField / TagField / etc. constructors plus an IndexDefinition.
The canonical schema shape — mirrors 01_getting_started.ipynb cell 3:
schema = {
"index": {
"name": "user_simple",
"prefix": "user_simple_docs",
# "storage_type": "hash" (default) or "json" — see §6
},
"fields": [
{"name": "user", "type": "tag"},
{"name": "credit_score", "type": "tag"},
{"name": "job", "type": "text"},
{"name": "age", "type": "numeric"},
{
"name": "user_embedding",
"type": "vector",
"attrs": {
"dims": 3,
"distance_metric": "cosine",
"algorithm": "flat",
"datatype": "float32",
},
},
],
}The dict goes to SearchIndex.from_dict(schema) — see §3 for the connection variants.
Field types: tag, text, numeric, geo, vector. Each maps to the corresponding FT.CREATE schema clause.
Vector field algorithms: "flat" for exact / small-scale (≤ ~1M vectors), "hnsw" for ANN, "svs-vamana" for SVS Vamana (advanced; Intel-optimised; mention as optional — see 09_svs_vamana.ipynb).
The schema above uses 3-dim toy embeddings from the upstream
01_getting_started.ipynbnotebook for brevity; the HNSW attrs block below shows the same schema shape with production-shape 1536 dims.dimsmust match whatever vectorizer or precomputed embedding produces the field's bytes — see §10.
HNSW attributes (added under attrs):
{
"name": "embedding",
"type": "vector",
"attrs": {
"dims": 1536,
"distance_metric": "cosine",
"algorithm": "hnsw",
"datatype": "float32",
"m": 16, # max bidirectional links per node
"ef_construction": 200, # build-time exploration factor
"ef_runtime": 10, # default query-time exploration factor
},
},JSON storage with nested paths. Set storage_type: "json" at the index level and use JSONPath-style nested field names (RedisVL emits the path-and-alias for you):
schema = {
"index": {"name": "bike_index", "prefix": "bike:", "storage_type": "json"},
"fields": [
{"name": "name", "type": "text"},
{"name": "metadata.brand", "type": "tag", "path": "$.metadata.brand"},
{"name": "metadata.price", "type": "numeric", "path": "$.metadata.price"},
# ...
],
}The name is the query alias (@metadata.brand); the path is the underlying JSONPath. For top-level JSON fields the path defaults to $.<name>.
5. Schema definition — YAML
redis-py equivalent: not applicable. Raw redis-py has no YAML schema concept; you compose fields in code.
YAML is the recommended schema format for production — the file is checked into source control alongside the application and reused across sync/async/CLI consumers. Canonical shape — mirrors redis/redis-vl-python upstream docs/user_guide/schema.yaml:
version: '0.1.0'
index:
name: vectorizers
prefix: doc
storage_type: hash
fields:
- name: sentence
type: text
- name: embedding
type: vector
attrs:
dims: 768
algorithm: flat
distance_metric: cosineLoad with from_yaml:
from redisvl.index import SearchIndex
index = SearchIndex.from_yaml(
"schemas/bicycle.yaml",
redis_url="redis://localhost:6379",
validate_on_load=True,
)Field shape under fields: mirrors the dict form in §4 — same type, attrs, path. The top-level version: field is the schema-format version, not the data version; upstream docs/user_guide/schema.yaml at tag v0.18.2 pins it to '0.1.0'. RedisVL validates this format-version string at parse time — keep it '0.1.0' for RedisVL 0.18.x and bump only when a future RedisVL release introduces a new schema-format major.
6. Storage type: HASH vs JSON
redis-py equivalent: see python-redis-py.md#4-create-index--hash and #5-create-index--json.
Mirrors 05_hash_vs_json.ipynb. Set storage_type once in the schema; everything else follows.
# HASH storage — default. Each Redis key holds a flat hash; field names are the schema names verbatim.
hash_schema = {
"index": {"name": "user-hash", "prefix": "user-hash-docs", "storage_type": "hash"},
"fields": [
{"name": "user", "type": "tag"},
{"name": "office_location", "type": "geo"},
{"name": "user_embedding", "type": "vector",
"attrs": {"dims": 3, "distance_metric": "cosine",
"algorithm": "flat", "datatype": "float32"}},
],
}
hindex = SearchIndex.from_dict(hash_schema, redis_url="redis://localhost:6379")
hindex.create(overwrite=True)
hindex.storage_type # -> StorageType.HASH
# JSON storage — same fields, nested JSON document per key. Use JSONPath for nested fields.
json_schema = {
"index": {"name": "user-json", "prefix": "user-json-docs", "storage_type": "json"},
"fields": [
{"name": "user", "type": "tag"},
{"name": "office_location", "type": "geo"},
{"name": "user_embedding", "type": "vector",
"attrs": {"dims": 3, "distance_metric": "cosine",
"algorithm": "flat", "datatype": "float32"}},
],
}
jindex = SearchIndex.from_dict(json_schema, redis_url="redis://localhost:6379")
jindex.create(overwrite=True)Choosing between them:
| Trade-off | HASH | JSON |
|---|---|---|
Nested objects (metadata.brand) | Flatten yourself before load | Native — use JSONPath in path: |
| Partial document update | HSET key field value (RedisVL: .load([{...}]) rewrites) | JSON.SET key $.path value |
| Vector encoding at load time | np.ndarray(...).astype(np.float32).tobytes() | [0.1, 0.2, ...] list (RedisVL converts) |
| Memory footprint per doc | Lower for flat docs | Higher (JSON metadata overhead) |
| Querying nested arrays | Not supported | $.tags[*] projections |
When in doubt for AI/RAG workloads, JSON: it preserves the natural shape of LLM outputs and allows nested metadata without flattening conventions. HASH wins when documents are already flat key-value structures.
7. Index lifecycle
redis-py equivalent: see python-redis-py.md#12-index-management. RedisVL wraps the same FT.CREATE / FT.DROPINDEX / FT.ALTER / FT.ALIAS* commands behind methods on SearchIndex.
# Create the index. overwrite=True drops and recreates; drop=True (with overwrite=True)
# also drops every existing indexed document.
index.create(overwrite=True, drop=False)
# Existence + introspection
index.exists() # -> bool
index.info() # -> dict from FT.INFO
# Load documents. Returns the list of full Redis keys written.
keys = index.load(data) # auto-generates document IDs
keys = index.load(data, id_field="user") # use a field as the document ID
keys = index.load(data, ttl=3600) # set per-document TTL
# Fetch by document ID (the unprefixed ID — RedisVL prepends the index prefix).
record = index.fetch("john")
# Delete by full key (with prefix) or by document ID (RedisVL adds the prefix).
index.drop_keys("user_simple_docs:01ABC...")
index.drop_documents("john")
index.drop_documents(["mary", "joe"])
# Clear: delete every indexed document but keep the index.
n_deleted = index.clear()
# Delete the index. drop=True (default) also deletes all indexed documents (FT.DROPINDEX ... DD);
# drop=False keeps the documents and removes only the index definition.
index.delete(drop=True)index.load(data) is the bulk-write entry point. It accepts a list of dicts; each dict's keys must match the schema's field names (or the JSON path aliases for JSON-storage schemas). For HASH-storage vector fields the dict value must be np.ndarray(...).astype(np.float32).tobytes() (or use a vectorizer, see §10). For JSON-storage vector fields a plain list[float] works.
Updating existing documents. RedisVL 0.18.2 does not expose a dedicated update_load(...) method (the name appears in early proposals; it is not in the shipped API). To update existing indexed documents, call .load(...) again with explicit keys= matching the existing Redis keys, or with the same id_field value:
# Re-load the same document IDs. HASH storage merges field-by-field at the Redis
# level (HSET semantics); JSON storage replaces the document.
keys = index.load(updated_data, id_field="user")
# Or write to explicit keys (must be full prefixed keys).
keys = index.load(updated_data, keys=["user_simple_docs:01ABC", "user_simple_docs:01DEF"])The preprocess= parameter accepts a callable applied to each item before write — useful for normalising shape or computing derived fields. validate_on_load=True (set on the index) re-runs schema validation on every .load() call.
paginate(query, page_size=N) is the recommended way to walk large result sets without holding everything in memory — yields batches of result dicts:
from redisvl.query import FilterQuery
from redisvl.query.filter import FilterExpression
query = FilterQuery(filter_expression=FilterExpression("*"), return_fields=["user", "age", "job"])
for batch in index.paginate(query, page_size=100):
for doc in batch:
process(doc)Mirrors 01_getting_started.ipynb cells 13, 18, 23, 26, 28, 30, 32, 33.
8. FilterExpression DSL
redis-py equivalent: see python-redis-py.md#6-ftsearch-idioms for raw query strings. The full DSL grammar lives in ../search-syntax-primitives.md; this section shows only how RedisVL's typed FilterExpression compiles to that grammar.
The four filter classes mirror the four indexable scalar types — Tag, Text, Num, Geo — plus Timestamp (a Num subclass that accepts datetime objects). All five live in redisvl.query.filter. Operators are overloaded: ==, !=, <, >, <=, >=, % (text wildcard/fuzzy), & (AND), | (OR), ~ (NOT — used via !=).
Tag — mirrors 02_complex_filtering.ipynb cells 8, 10, 12
from redisvl.query.filter import Tag
Tag("credit_score") == "high" # @credit_score:{high}
Tag("credit_score") != "high" # -@credit_score:{high}
Tag("credit_score") == ["high", "medium"] # @credit_score:{high|medium}
Tag("credit_score") == set(["high", "medium"]) # same; set enforces uniqueness
Tag("credit_score") == [] # gracefully -> "*" (no constraint)Num — mirrors cells 17, 18, 19
from redisvl.query.filter import Num
Num("age").between(15, 35) # @age:[15 35]
Num("age") == 14 # @age:[14 14]
Num("age") != 14 # -@age:[14 14]
Num("age") < 18 # @age:[-inf (18]
Num("age") >= 18 # @age:[18 +inf]Text — mirrors cells 25, 26, 27, 28, 29
from redisvl.query.filter import Text
Text("job") == "doctor" # @job:"doctor"
Text("job") != "doctor" # -@job:"doctor"
Text("job") % "doct*" # @job:doct* (wildcard / prefix)
Text("job") % "%%engine%%" # @job:%%engine%% (fuzzy, Levenshtein 2)
Text("job") % "engineer|doctor" # @job:(engineer|doctor)
Text("job") % "" # gracefully -> "*"Geo — mirrors cells 34, 35, 36
from redisvl.query.filter import Geo, GeoRadius
Geo("office_location") == GeoRadius(-122.4194, 37.7749, 10, "km") # @office_location:[-122.4194 37.7749 10 km]
Geo("office_location") != GeoRadius(-122.4194, 37.7749, 10, "km") # -@office_location:[...]Timestamp — mirrors cells 21–23
from redisvl.query.filter import Timestamp
from datetime import datetime
dt = datetime(2025, 3, 16, 13, 45, 39)
Timestamp("last_updated") > dt # @last_updated:[(<epoch> +inf]
Timestamp("last_updated").between(dt1, dt2) # @last_updated:[<epoch1> <epoch2>]Timestamp converts datetime to epoch seconds and emits a NUMERIC range — the underlying field must be declared type: numeric and stored as epoch seconds.
Composition — mirrors cells 38, 40, 42
Boolean operators compose any filter into a single expression:
t = Tag("credit_score") == "high"
lo = Num("age") >= 18
hi = Num("age") <= 100
ts = Timestamp("last_updated") > datetime(2025, 3, 16, 13, 45, 39)
combined = t & lo & hi & ts # AND
either = (Num("age") < 18) | (Num("age") > 93) # OR
# Defensive composition — empty filters fall back to "*", so partial inputs compose cleanly.
def make_filter(age=None, credit=None, job=None):
return (
(Num("age") > age) &
(Tag("credit_score") == credit) &
(Text("job") % job)
)Pass the resulting FilterExpression to any query class via filter_expression=:
from redisvl.query import VectorQuery
v = VectorQuery(
vector=[0.1, 0.1, 0.5],
vector_field_name="user_embedding",
return_fields=["user", "credit_score", "age", "job", "office_location"],
filter_expression=combined,
)Or swap the filter on an existing query without rebuilding it:
v.set_filter(Tag("credit_score") != "high")str(filter_expression) returns the compiled query string — useful for logging or for verifying what RedisVL emits before sending it to Redis.
9. Query classes
redis-py equivalent: see python-redis-py.md#6-ftsearch-idioms (FT.SEARCH) and #7-ftaggregate-idioms (FT.AGGREGATE). RedisVL's query classes route to either depending on shape.
All seven live under redisvl.query. Pass an instance to index.query(...) — RedisVL picks the right underlying command:
| Class | Wraps | Use for |
|---|---|---|
VectorQuery | FT.SEARCH with =>[KNN K @field $vec AS score] | Top-K nearest neighbours, pre-filterable via filter_expression=. |
VectorRangeQuery (also exported as RangeQuery) | FT.SEARCH with =>[VECTOR_RANGE radius $vec] | All hits within a distance threshold of the query vector. |
FilterQuery | FT.SEARCH on a FilterExpression (no vector) | Plain filtered retrieval; pagination via index.paginate(query, page_size=N). |
CountQuery | FT.SEARCH ... LIMIT 0 0 | Count documents matching a filter without fetching them. |
TextQuery | FT.SEARCH with full-text scoring | BM25/TFIDF-scored full-text retrieval over text fields. |
AggregationQuery | FT.AGGREGATE | Group-by + reduce pipelines. Inherits from redis-py's AggregateRequest — fluent API matches. |
HybridQuery | FT.HYBRID (Redis ≥ 8.4.0) | Native blended text + vector ranking. See §11. |
VectorQuery — KNN with optional pre-filter
Mirrors 01_getting_started.ipynb cell 36 + 02_complex_filtering.ipynb cell 8.
from redisvl.query import VectorQuery
from redisvl.query.filter import Tag, Num
# Top-3 nearest, no filter.
query = VectorQuery(
vector=[0.1, 0.1, 0.5],
vector_field_name="user_embedding",
return_fields=["user", "age", "job", "credit_score", "vector_distance"],
num_results=3,
)
results = index.query(query)
# Pre-filtered KNN — filter is applied before the vector scan.
query = VectorQuery(
vector=[0.1, 0.1, 0.5],
vector_field_name="user_embedding",
return_fields=["user", "credit_score", "age", "vector_distance"],
num_results=3,
filter_expression=(Tag("credit_score") == "high") & (Num("age").between(18, 60)),
)
results = index.query(query)results is a list[dict] — each dict carries the returned fields plus id and (when the vector_distance alias is in return_fields) the distance score.
VectorRangeQuery — within a distance threshold
from redisvl.query import VectorRangeQuery
# Backwards-compatible alias — RangeQuery is a thin subclass kept for older code paths.
from redisvl.query import RangeQuery # `class RangeQuery(VectorRangeQuery): pass` in redisvl/query/query.py
range_q = VectorRangeQuery(
vector=[0.1, 0.1, 0.5],
vector_field_name="user_embedding",
return_fields=["user", "vector_distance"],
distance_threshold=0.5, # COSINE distance in [0, 2]
num_results=10, # cap on returned results
)
results = index.query(range_q)Mirrors docs/user_guide/11_advanced_queries.ipynb (distance-threshold pattern; the notebook uses the equivalent filter_expression + VectorQuery shape rather than VectorRangeQuery directly — the class is exercised by tests/integration/test_query.py).
FilterQuery — pure filter retrieval
Mirrors 01_getting_started.ipynb cell 30.
from redisvl.query import FilterQuery
from redisvl.query.filter import Tag
query = FilterQuery(
filter_expression=Tag("credit_score") == "high",
return_fields=["user", "age", "job"],
num_results=50,
)
# Paginate large result sets — recommended over num_results for full scans.
for batch in index.paginate(query, page_size=100):
for doc in batch:
process(doc)CountQuery — count without fetching
from redisvl.query import CountQuery
from redisvl.query.filter import Tag
count = index.query(CountQuery(filter_expression=Tag("brand") == "Nike"))
# Returns the integer match count; emits FT.SEARCH ... LIMIT 0 0 under the hood.Source: redisvl.query.CountQuery (redisvl/query/query.py — class at module scope). Exercised by tests/integration/test_query.py; the user-guide notebooks favour FilterQuery over CountQuery for didactic reasons (showing the fetched docs), but the class is a stable part of the v0.18.2 public API.
TextQuery — full-text scored retrieval
Mirrors 11_advanced_queries.ipynb cells 8, 10, 13.
from redisvl.query import TextQuery
from redisvl.query.filter import Tag, Num
# Plain text search with BM25STD (default scorer).
text_query = TextQuery(
text="running shoes",
text_field_name="brief_description",
return_fields=["product_id", "brief_description", "category", "price"],
num_results=5,
)
# Add a filter expression to scope the search.
filtered = TextQuery(
text="comfortable",
text_field_name="brief_description",
filter_expression=Num("price") < 100,
return_fields=["product_id", "brief_description", "price"],
)
# Multi-field weighting — search across multiple text fields with per-field weights.
weighted = TextQuery(
text="shoes",
text_field_name={"brief_description": 1.0, "full_description": 0.5},
return_fields=["product_id", "brief_description"],
)
# Stopword handling — "english" is the default; pass a custom list or None to disable.
with_stopwords = TextQuery(text="the best shoes", text_field_name="brief_description", stopwords="english")
custom_stop = TextQuery(text="best shoes", text_field_name="brief_description", stopwords=["for", "with"])
no_stopwords = TextQuery(text="the best shoes", text_field_name="brief_description", stopwords=None)Scorers: TFIDF, TFIDF.DOCNORM, BM25STD (default), BM25STD.NORM, BM25STD.TANH, DISMAX, DOCSCORE, HAMMING. See 11_advanced_queries.ipynb cells 10–11.
AggregationQuery — FT.AGGREGATE shape
AggregationQuery lives at redisvl.query.AggregationQuery (redisvl/query/aggregate.py) and subclasses redis-py's AggregateRequest. Its constructor takes a single query_string (the FT.AGGREGATE filter expression) and the inherited fluent API matches the one in python-redis-py.md#7-ftaggregate-idioms — .load(), .apply(), .group_by(), .filter(), .sort_by(), .limit(), .cursor().
from redisvl.query import AggregationQuery
from redis.commands.search import reducers
# Top-3 brands by document count, descending — `*` matches all docs.
agg = (
AggregationQuery("*")
.group_by("@brand", reducers.count().alias("n"))
.sort_by(("@n", "DESC"))
.limit(0, 3)
)
results = index.aggregate(agg)Route through index.aggregate(...) when you want the RedisVL result-shape normalisation; the underlying command is FT.AGGREGATE on the index's name. The reducer factories live in redis.commands.search.reducers (imported from redis-py) — RedisVL does not re-export them. redisvl/query/aggregate.py declares only the subclass plus AggregateHybridQuery, MultiVectorQuery, and Vector; the fluent surface is inherited verbatim. The user-guide notebooks (11_advanced_queries.ipynb) cover the related AggregateHybridQuery (cells 34, 37, 42) but do not include a worked AggregationQuery example — see tests/integration/test_aggregation.py for executable references.
SVS Vamana (advanced / optional)
09_svs_vamana.ipynb covers Intel's SVS Vamana vector algorithm. Treat it as advanced/optional in v1: it requires a Redis build with SVS support (Intel-optimised) and adds tuning parameters orthogonal to the HNSW/FLAT trade-offs. Declare it via "algorithm": "svs-vamana" in the vector field's attrs. Most agents should pick FLAT (≤ ~1M vectors) or HNSW (ANN at scale) and only reach for SVS Vamana when explicitly asked.
10. Vectorizers
redis-py equivalent: not applicable. Raw redis-py has no vectorizer concept — you compute the embedding yourself and pass the raw bytes blob into the query (np.array(...).astype(np.float32).tobytes()). RedisVL's vectorizers wrap that pattern plus the provider-specific HTTP/SDK call.
Canonical example — OpenAI
Mirrors 04_vectorizers.ipynb cells 4–7:
import os
from redisvl.utils.vectorize import OpenAITextVectorizer
oai = OpenAITextVectorizer(
model="text-embedding-ada-002", # or text-embedding-3-small / -3-large
api_config={"api_key": os.environ["OPENAI_API_KEY"]},
)
# Single embedding.
vec = oai.embed("This is a test sentence.")
print(len(vec)) # 1536 for ada-002 / 3-small
# Batch.
sentences = ["That is a happy dog", "That is a happy person", "Today is a sunny day"]
embeddings = oai.embed_many(sentences)
# Async batch — vectorizers expose .aembed_many and .aembed for async use.
embeddings = await oai.aembed_many(sentences)Vectorizers all expose the same surface: .embed(text), .embed_many(texts), .aembed(text), .aembed_many(texts). Pass as_buffer=True to embed_many to get RedisVL's binary buffer format suitable for direct write into HASH-storage vector fields without the np.array(...).tobytes() conversion.
Provider table
All providers live under redisvl.utils.vectorize. Mirrors redisvl/utils/vectorize/__init__.py.
| Class | Provider | Auth | Typical default dim | Upstream notebook cell |
|---|---|---|---|---|
OpenAITextVectorizer | OpenAI | api_config={"api_key": ...} (or OPENAI_API_KEY) | 1536 (ada-002, 3-small); 3072 (3-large) | 04_vectorizers.ipynb 4–7 |
AzureOpenAITextVectorizer | Azure OpenAI | api_config={"api_key", "api_version", "azure_endpoint"} | Matches deployment | cells 9–11 |
HFTextVectorizer | HuggingFace (local sentence-transformers) | None (local model) | Model-dependent (768 for all-mpnet-base-v2) | cells 13–14 |
VertexAITextVectorizer (VertexAIVectorizer) | Google Vertex AI | api_config={"project_id", "location", "google_application_credentials"} | 768 (text-embedding-005) | cell 16 |
CohereTextVectorizer | Cohere | api_config={"api_key": ...} (or COHERE_API_KEY) | 1024 (embed-english-v3.0) | cells 18+ |
BedrockTextVectorizer (BedrockVectorizer) | AWS Bedrock | AWS credentials via boto3 env / profile | 1024 (amazon.titan-embed-text-v1) | bedrock cells |
VoyageAITextVectorizer (VoyageAIVectorizer) | VoyageAI | api_config={"api_key": ...} | 1024 (voyage-3) | voyage cells |
MistralAITextVectorizer | Mistral | api_config={"api_key": ...} | 1024 (mistral-embed) | — |
CustomTextVectorizer (CustomVectorizer) | Your own callable | n/a — you pass a embed function | Your model | custom-vectorizer section |
RedisVL vectorizers auto-detect the model's dim on first call for known providers — you only need to set dims in the schema when the vectorizer doesn't expose it (e.g., custom). When dims in the schema disagrees with what the vectorizer produces, .load() raises a dimension-mismatch error at write time.
11. Hybrid retrieval
redis-py equivalent: see python-redis-py.md#10-fthybrid. RedisVL exposes the same FT.HYBRID surface through HybridQuery and AggregateHybridQuery.
Version gate: HybridQuery requires Redis ≥ 8.4.0 and redis-py >= 7.1.0. On older Redis or older redis-py, fall back to the pre-filter + KNN pattern via VectorQuery(filter_expression=...) — see below. The upstream 11_advanced_queries.ipynb gates every HybridQuery example on a HYBRID_SEARCH_AVAILABLE flag (cells 32, 36, 39, 41).
Native FT.HYBRID — HybridQuery
Mirrors 11_advanced_queries.ipynb cell 32:
from redisvl.query import HybridQuery
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", # or "RRF" (server default)
linear_alpha=0.3, # 30% text, 70% vector
yield_combined_score_as="hybrid_score",
)
results = index.query(hybrid_query)Combination methods:
combination_method="RRF"— Reciprocal Rank Fusion. Rank-based, robust without weight tuning. Knobs:rrf_window(default 20),rrf_constant(default 60).combination_method="LINEAR"— weighted score blend. Knob:linear_alpha(text weight;1 - alphais the implicit vector weight).
Scorers (text leg): same options as TextQuery — BM25STD (default), TFIDF, DISMAX, etc.
Filter expressions apply to both legs:
filtered_hybrid = 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,
combination_method="LINEAR",
yield_text_score_as="text_score",
yield_vsim_score_as="vector_similarity",
yield_combined_score_as="hybrid_score",
)Aggregate-shaped hybrid — AggregateHybridQuery
When you want the FT.AGGREGATE-shape output (rows, group-by stages) instead of the FT.SEARCH-shape:
from redisvl.query import AggregateHybridQuery
agg_hybrid = AggregateHybridQuery(
text="running shoes",
text_field_name="brief_description",
vector=[0.1, 0.2, 0.1],
vector_field_name="text_embedding",
return_fields=["product_id", "brief_description", "category", "price"],
alpha=0.7, # 70% vector, 30% text
num_results=5,
)
results = index.query(agg_hybrid)Fallback for Redis < 8.4.0 — pre-filter + KNN
When FT.HYBRID is unavailable, blend retrieval by applying the text filter as a pre-filter on the vector query:
from redisvl.query import VectorQuery
from redisvl.query.filter import Text, Num
query = VectorQuery(
vector=query_vector,
vector_field_name="text_embedding",
return_fields=["product_id", "brief_description", "price", "vector_distance"],
num_results=10,
# Pre-filter shrinks the candidate set before the KNN scan.
filter_expression=(Text("brief_description") % "running") & (Num("price") < 200),
)
results = index.query(query)This is not the same operation as FT.HYBRID: it filters by text but ranks purely by vector distance. Use it when (a) the Redis version doesn't support FT.HYBRID, or (b) the workload only needs filtered KNN, not blended ranking. The upstream 02_complex_filtering.ipynb cell 8 demonstrates the pattern in its native shape.
12. Async
redis-py equivalent: see python-redis-py.md footer (async out of scope for v1).
AsyncSearchIndex mirrors SearchIndex method-for-method on the search/index/query surface. Async parity is at full coverage for the operations relevant to this reference — create, delete, load, fetch, query, paginate (async generator), aggregate, search, clear, drop_keys, drop_documents, expire_keys, exists, info, listall.
from redisvl.index import AsyncSearchIndex
from redis.asyncio import Redis
client = Redis.from_url("redis://localhost:6379")
index = AsyncSearchIndex.from_dict(schema, redis_client=client)
await index.create(overwrite=True, drop=False)
await index.load(data)
# Same query classes; same construction.
from redisvl.query import VectorQuery
query = VectorQuery(vector=[0.1, 0.1, 0.5], vector_field_name="user_embedding",
return_fields=["user", "age"], num_results=3)
results = await index.query(query)
await index.delete(drop=True)Mirrors 01_getting_started.ipynb cells 41–47.
Divergences from the sync API:
from_existing(...)is async onAsyncSearchIndex—await AsyncSearchIndex.from_existing(name, redis_url=...). Sync isSearchIndex.from_existing(name, redis_url=...).connect(...)andset_client(...)are async.paginateis an async generator:async for batch in index.paginate(query, page_size=N):.No context-manager support equivalent to
SearchIndex.__enter__/__exit__— manage lifetime manually withawait index.disconnect().
Vectorizers expose .aembed(text) and .aembed_many(texts) for the embedding call itself — pair them with AsyncSearchIndex for end-to-end async pipelines (see §10).
13. LLM primitives (summary level)
Full coverage of the LLM-primitive surface is deferred to a future dedicated spec. The summaries below are intentionally narrow — minimal constructor + the upstream notebook to read for depth. None of these primitives are necessary for the core search/index/query surface this reference covers.
SemanticCache — semantic prompt → response cache
Mirrors 03_llmcache.ipynb cell 5.
from redisvl.extensions.cache.llm import SemanticCache
from redisvl.utils.vectorize import HFTextVectorizer
llmcache = SemanticCache(
name="llmcache", # underlying search index name
redis_url="redis://localhost:6379",
distance_threshold=0.1, # cosine distance [0, 2]; lower = stricter
vectorizer=HFTextVectorizer("redis/langcache-embed-v2"),
)
llmcache.store(prompt="What is the capital of France?", response="Paris")
hit = llmcache.check(prompt="capital city of France?") # returns cached response on semantic matchfilterable_fields=[{"name": "user_id", "type": "tag"}] partitions the cache by tenant/user. Defer to 03_llmcache.ipynb for filterable-field semantics, TTLs, and metadata.
LangCache vs SemanticCache: SemanticCache is the in-process Python class shown above. LangCache (13_langcache_semantic_cache.ipynb) is a separate Redis-hosted product — a managed semantic-cache service on Redis Cloud. They share a vector-search shape but live in different scopes. For LangCache coverage, fall through to the redis-semantic-cache skill (a separate skill in this repo) rather than treating it inline as a RedisVL primitive.
MessageHistory — durable chat-history with optional semantic recall
Mirrors 07_message_history.ipynb cells 1, 12.
from redisvl.extensions.message_history import MessageHistory, SemanticMessageHistory
# Plain FIFO chat history.
chat = MessageHistory(name="student tutor")
chat.add_message({"role": "user", "content": "Explain backprop."})
recent = chat.get_recent(top_k=8)
# Vector-recall over the same history — fetches semantically similar past turns.
semantic = SemanticMessageHistory(name="tutor")
semantic.add_messages(recent)
relevant = semantic.get_relevant("How does gradient descent relate to backprop?")Defer to 07_message_history.ipynb for session tagging, role filtering, TTLs.
SemanticRouter — route a query to a labelled bucket via vector match
Mirrors 08_semantic_router.ipynb cells 2, 4.
from redisvl.extensions.router import SemanticRouter, Route
from redisvl.utils.vectorize import HFTextVectorizer
tech = Route(
name="technology",
references=["what are the latest advancements in AI?", "tell me about the newest gadgets"],
metadata={"category": "tech"},
distance_threshold=0.71,
)
router = SemanticRouter(
name="topic-router",
vectorizer=HFTextVectorizer(),
routes=[tech, ...],
redis_url="redis://localhost:6379",
overwrite=True,
)
match = router("what's new in machine learning?") # -> Route name + scoreDefer to 08_semantic_router.ipynb for multi-route disambiguation, from_dict / from_yaml persistence, and threshold tuning.
Other extensions (also summary-only)
EmbeddingsCache (
redisvl.extensions.cache.embeddings.EmbeddingsCache) — cache forembed()calls to avoid recomputation. See10_embeddings_cache.ipynb.Rerankers (
redisvl.utils.rerank) —HFCrossEncoderReranker,CohereReranker,VoyageAIRerankerfor second-stage cross-encoder reranking after initial vector retrieval. See06_rerankers.ipynb.
14. Common errors & version gotchas
redis-py equivalent: see python-redis-py.md#13-common-errors--version-gotchas.
| Symptom | Likely cause | Fix |
|---|---|---|
redis.exceptions.ResponseError: unknown command 'FT.HYBRID' from HybridQuery | Redis server < 8.4.0 or redis-py < 7.1.0. | Upgrade both, or fall back to pre-filter + VectorQuery (§11). |
_IMPORT_ERROR_MESSAGE = "Hybrid queries require Redis >= 8.4.0 and redis-py>=7.1.0" | Importing HybridQuery against an older redis-py. | Upgrade redis-py; gate import on try/except ImportError. |
Vector dimension mismatch on .load() | Vectorizer output dim ≠ schema dims. | Set dims to match the vectorizer's actual output, or pick a model whose dim matches the schema. |
.load() raises ValidationError | validate_on_load=True and a doc has wrong shape (missing field, wrong type). | Either fix the doc or set validate_on_load=False (test/dev only). |
| Vector query returns 0 hits despite obvious matches | HASH-storage vector field was loaded with a list[float] instead of np.array(...).astype(np.float32).tobytes(). | For HASH: convert to bytes. For JSON: lists work natively. |
| GEOSHAPE WITHIN/CONTAINS returns syntax error | RedisVL defaults to DIALECT 2; WITHIN/CONTAINS need DIALECT 3. | Pass dialect=3 on the query class. |
OPENAI_API_KEY AuthenticationError from OpenAITextVectorizer | Missing env var or wrong key passed to api_config. | Set OPENAI_API_KEY in the env, or pass api_config={"api_key": "..."} explicitly. |
AttributeError: 'SearchIndex' object has no attribute 'X' after a RedisVL minor upgrade | RedisVL renamed/moved an API between minors (0.x is pre-1.0). | Pin RedisVL to a known-good version in requirements.txt; check the changelog before upgrading. |
ImportError: cannot import name 'HybridQuery' from 'redisvl.query' | RedisVL < 0.18 (or redis-py < 7.1.0 at install time). | Upgrade RedisVL to ≥ 0.18 and redis-py to ≥ 7.1.0. |
AsyncSearchIndex.from_existing(...) raises "coroutine was never awaited" | The sync API was used. | await AsyncSearchIndex.from_existing(...) — async-side method is a coroutine. |
index.query(query) returns rows where every field is None | The dict's return_fields was empty AND the index is JSON-storage. | Explicitly pass return_fields=[...] — RedisVL doesn't auto-return the full JSON doc. |
index.create() errors Index already exists on re-run | .create() is not idempotent without overwrite=True. | .create(overwrite=True) for dev bootstrap; gate on .exists() for production. |
DIALECT defaults: RedisVL passes dialect=2 on every query class by default. You do not need to set it. Override to dialect=3 for GEOSHAPE WITHIN/CONTAINS. The Redis server default (DIALECT 2 from Redis 8.0+) does not affect RedisVL because RedisVL always sends DIALECT explicitly.
rvl CLI tool: the rvl CLI (rvl index info, rvl stats, rvl index list) is a productivity tool for inspecting indexes from the shell. Deferred from v1 of this reference — agents generating Python code rarely need it. See cli.ipynb upstream.
15. Upstream examples index
redis-py equivalent: see python-redis-py.md#14-upstream-examples-index.
Curated mapping of operation → notebook + cell. Upstream files live at https://github.com/redis/redis-vl-python/blob/main/docs/user_guide/<file>. Source classes live in redis/redis-vl-python/redisvl/....
| Operation | Notebook + cell | Upstream class / method |
|---|---|---|
Schema dict (index, fields, attrs) | 01_getting_started.ipynb cell 3 | IndexSchema.from_dict (redisvl/schema/schema.py) |
SearchIndex construction (client / URL / default) | 01_getting_started.ipynb cells 9, 11 | SearchIndex.__init__, .from_dict, .from_yaml (redisvl/index/index.py) |
index.create(overwrite=True) | cell 13 | SearchIndex.create |
index.load(data) | cell 18 | SearchIndex.load |
index.load(data, id_field="user") + index.fetch("john") | cell 26 | SearchIndex.load, .fetch |
index.key("john") | cell 28 | SearchIndex.key |
FilterQuery + index.paginate(query, page_size=...) | cell 30 | redisvl.query.FilterQuery, SearchIndex.paginate |
index.drop_keys / .drop_documents | cells 32–33 | SearchIndex.drop_keys, .drop_documents |
VectorQuery KNN | cell 36 | redisvl.query.VectorQuery |
VectorRangeQuery / RangeQuery (distance threshold) | not in user-guide notebooks — see tests/integration/test_query.py | redisvl.query.VectorRangeQuery, RangeQuery (redisvl/query/query.py) |
CountQuery (FT.SEARCH ... LIMIT 0 0) | not in user-guide notebooks — see tests/integration/test_query.py | redisvl.query.CountQuery (redisvl/query/query.py) |
AggregationQuery fluent (.group_by / .reduce / .sort_by) | not in user-guide notebooks — see tests/integration/test_aggregation.py | redisvl.query.AggregationQuery (redisvl/query/aggregate.py), inherits AggregateRequest from redis.commands.search.aggregation |
AsyncSearchIndex + async query | cells 41–47 | AsyncSearchIndex.__init__, .query |
Schema mutate: remove_field + add_fields | cell 45 | IndexSchema.remove_field, .add_fields |
Tag(field) == value | 02_complex_filtering.ipynb cell 8 | redisvl.query.filter.Tag |
Tag != / list / set / empty | cells 10, 12, 13, 15 | Tag.__eq__, __ne__ |
Num.between / == / != | cells 17–19 | redisvl.query.filter.Num |
Timestamp > / < / .between | cells 21–23 | redisvl.query.filter.Timestamp |
Text == / != / % (wildcard, fuzzy, `engineer | doctor`) | cells 25–29 |
Geo == GeoRadius(lon, lat, r, units) | cells 34–36 | redisvl.query.filter.Geo, GeoRadius |
Boolean composition &, | | cells 38, 40 | FilterExpression.__and__, __or__ |
OpenAITextVectorizer.embed / .embed_many / .aembed_many | 04_vectorizers.ipynb cells 5–7 | redisvl.utils.vectorize.OpenAITextVectorizer |
AzureOpenAITextVectorizer setup | cells 9–11 | redisvl.utils.vectorize.AzureOpenAITextVectorizer |
HFTextVectorizer (local sentence-transformers) | cells 13–14 | redisvl.utils.vectorize.HFTextVectorizer |
VertexAIVectorizer setup | cell 16 | redisvl.utils.vectorize.VertexAIVectorizer |
HASH-storage schema + .create | 05_hash_vs_json.ipynb cells 5–6 | SearchIndex.from_dict with storage_type: hash |
JSON-storage schema + .create | cells 16–17 | SearchIndex.from_dict with storage_type: json |
| Bike-data + nested JSON metadata | cells 26–27 | (Bicycle-shaped dataset for downstream RAG examples) |
TextQuery BM25 / TFIDF / weighted / stopwords | 11_advanced_queries.ipynb cells 8–20 | redisvl.query.TextQuery |
FilterQuery for STOPWORDS-0 raw query | cell 26 | redisvl.query.FilterQuery |
HybridQuery LINEAR + RRF + filter | cells 32, 36, 39, 41 | redisvl.query.HybridQuery |
AggregateHybridQuery | cells 34, 37 | redisvl.query.AggregateHybridQuery |
SemanticCache constructor + .store / .check | 03_llmcache.ipynb cell 5 | redisvl.extensions.cache.llm.SemanticCache |
SemanticCache with filterable_fields | cells 41, 45 | SemanticCache(filterable_fields=[...]) |
MessageHistory / SemanticMessageHistory | 07_message_history.ipynb cells 1, 12 | redisvl.extensions.message_history.MessageHistory, SemanticMessageHistory |
SemanticRouter + Route | 08_semantic_router.ipynb cells 2, 4 | redisvl.extensions.router.SemanticRouter, Route |
SemanticRouter.from_yaml | cell 21 | SemanticRouter.from_yaml |
Canonical schema.yaml | docs/user_guide/schema.yaml | IndexSchema.from_yaml |
| SVS Vamana algorithm (advanced) | 09_svs_vamana.ipynb | "algorithm": "svs-vamana" in vector field attrs |
EmbeddingsCache | 10_embeddings_cache.ipynb | redisvl.extensions.cache.embeddings.EmbeddingsCache |
rvl CLI (deferred) | cli.ipynb | Out of scope for v1 |
| LangCache (Redis-hosted product) | 13_langcache_semantic_cache.ipynb | Cross-link to the redis-semantic-cache skill |
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs Python, Needs Node.js
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- Redis, Inc.
- Publisher
- Redis (Official)
- Source
- redis/agent-skills · skills/redis-search · commit 74708da
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
- Also distributed in
- redis/agent-skills
AutoGineer is not affiliated with or endorsed by Redis, Inc. or Redis.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download redis-search.zip