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/query-syntax.md
Master Redis Search Query Syntax
The Redis Search query DSL composes operators (AND, OR, NOT, optional), field-scoped predicates (@field:value), and delimiter-specific value forms (TAG {}, NUMERIC [], TEXT phrase ""). Most "empty result" bugs come from picking the wrong delimiter or forgetting to escape special characters in TAG values.
Before writing the query expression, anchor terminology in search-syntax-primitives.md (Query Term, Field Identifier, Delimiters, Operators).
Correct: Operator and delimiter reference, against the canonical Bicycle dataset.
# Field scoping — TEXT (free-text, tokenized + stemmed)
FT.SEARCH idx:bicycle "@description:wireless" DIALECT 2
# TAG — exact match with { }; pipe = OR
FT.SEARCH idx:bicycle "@condition:{new|refurbished}" DIALECT 2
# NUMERIC range — inclusive [], exclusive ( prefix, +inf/-inf supported
FT.SEARCH idx:bicycle "@price:[100 500]" DIALECT 2
FT.SEARCH idx:bicycle "@price:[(100 (500]" DIALECT 2
FT.SEARCH idx:bicycle "@price:[-inf 200]" DIALECT 2
# TEXT phrase — quotes for exact ordering
FT.SEARCH idx:bicycle "\"mountain bicycle\"" DIALECT 2
# TEXT prefix / suffix / infix wildcards
FT.SEARCH idx:bicycle "@model:bik*" DIALECT 2
FT.SEARCH idx:bicycle "@model:*ike*" DIALECT 2
# Fuzzy match — %term% (1 edit), %%term%% (2 edits), %%%term%%% (3 edits)
FT.SEARCH idx:bicycle "@model:%bicycle%" DIALECT 2
# Boolean — implicit AND (space), | OR, - NOT, ~ optional, () grouping
FT.SEARCH idx:bicycle "@type:{mountain} -@condition:{used}" DIALECT 2
FT.SEARCH idx:bicycle "(@type:{mountain}|@type:{road}) @price:[-inf 500]" DIALECT 2
# GEO — point + radius
FT.SEARCH idx:bicycle "@store_location:[-122.4 37.7 50 km]" DIALECT 2
# GEOSHAPE — WITHIN polygon (DIALECT 3+, but FT.CREATE marks the field)
FT.SEARCH idx:zones "@boundary:[WITHIN $poly]" PARAMS 2 poly "POLYGON((...))" DIALECT 3TAG escaping rules
These are the single biggest source of empty-result bugs. TAG values are not tokenized; hyphens, dots, commas, @, :, and spaces inside a tag must be escaped with a leading backslash, and the whole value lives inside {}.
# TAG with hyphen — must escape
FT.SEARCH idx:bicycle "@brand:{Giant\\-Cycles}" DIALECT 2
# TAG with dot — must escape
FT.SEARCH idx:bicycle "@email:{user\\@example\\.com}" DIALECT 2
# TAG with space — escape the space (or use double-quotes inside the braces)
FT.SEARCH idx:bicycle "@brand:{Trek\\ Bicycles}" DIALECT 2
# TAG with embedded colons (FHIR-style urn:uuid:...) — escape every : and -
FT.SEARCH idx:obs "@subject:{urn\\:uuid\\:fa70e7dd\\-03aa\\-6885\\-ca29\\-c65c38dab633}" DIALECT 2TAG comparisons are case-sensitive AND require exact-value match (no substring). Mirror the casing of values exactly as they appear in the schema's top_values or sample documents — Redis Search does not auto-fold TAG case and does not substring-match TAG values.
@Breed:{Pit}will NOT match an indexed@Breed:{pitbull}. If you seepitbullin the sample data, query exactly@Breed:{pitbull}(or@Breed:pitbullfor TEXT fields). Do not infer a more-specific or more-generic variant.For multi-word breeds / categories, look at the actual schema value:
Pit Bullandpitbullare different values and will not match each other.
TAG IDs (UUIDs, FHIR urn:uuid:…, hyphenated codes) MUST be escaped. Every :, -, ., and space inside {...} needs a leading backslash. UUIDs almost always contain hyphens — forgetting to escape returns zero results or a syntax error.
# Bad: unescaped hyphens in a UUID — "Syntax error at offset 13 near ..."
FT.SEARCH explanationofbenefits "@id:{96771ad4-d132-3aa6-3a79-36a03ded158e}"
# Good: every hyphen escaped
FT.SEARCH explanationofbenefits "@id:{96771ad4\\-d132\\-3aa6\\-3a79\\-36a03ded158e}"
# Good: FHIR-style urn:uuid:... — escape every : and -
FT.SEARCH idx:obs "@subject:{urn\\:uuid\\:fa70e7dd\\-03aa\\-6885\\-ca29\\-c65c38dab633}"When the question gives you a literal ID string, the command type is FT.SEARCH (look-up by key), and the value goes inside {...} with every -/:/. escaped.
Multi-word TEXT — phrase vs AND-of-words
Unquoted multi-word values in a @field: clause split on whitespace and AND the terms across the index, not scoped to the field. Use "…" for an exact phrase or (…) for word-AND scoped to the field.
# Bad: unquoted — parses as @reason:sleep AND apnea (apnea is unfielded!)
FT.SEARCH idx:dx "@reason:sleep apnea" DIALECT 2
# Good: exact phrase, words in order, adjacent
FT.SEARCH idx:dx "@reason:\"sleep apnea\"" DIALECT 2
# Good: both words required, any order, no adjacency constraint
FT.SEARCH idx:dx "@reason:(sleep apnea)" DIALECT 2Dates indexed as TEXT
Hyphens are token breaks in TEXT, so an unescaped @date:2022-07 parses as 2022 AND -07. Escape hyphens and use a prefix wildcard for "month of" / "year of" queries; alternation lives inside (...).
# All dates in July 2022 — escape - and use a trailing * for the day
FT.SEARCH idx:events "@date:2022\\-07*" DIALECT 2
# Q1 2022 — alternation of escaped prefixes inside parens
FT.SEARCH idx:events "@date:(2022\\-01*|2022\\-02*|2022\\-03*)" DIALECT 2
# Bad: unescaped hyphen — parses as 2022 AND -07
FT.SEARCH idx:events "@date:2022-07"
# Bad: per-field alternation — becomes a UNION of three different field clauses, not a date OR
FT.SEARCH idx:events "@date:2010 | @date:2011 | @date:2012"The * does NOT distribute across alternation — every alternative carries its own trailing *. This is the single most common date-alternation bug.
# Bad: bare years inside alternation — matches the literal tokens "2018" / "2019", not "any date in 2018/2019"
FT.SEARCH idx:events "@date:(2018|2019)"
# Bad: hyphen-escaped but no wildcard — matches the literal "2022-01" / "2022-02" only
FT.SEARCH idx:events "@date:(2022\\-01|2022\\-02)"
# Good: every alternative gets its own trailing *
FT.SEARCH idx:events "@date:(2018*|2019*)" # any date in 2018 or 2019
FT.SEARCH idx:events "@date:(2022\\-01*|2022\\-02*|2022\\-03*)" # any date in Jan/Feb/Mar 2022The same alternation rule applies inside FT.AGGREGATE query strings — every TEXT field where you want a prefix-match disjunction, not just dates.
Incorrect: Using () for TAG values, {} for TEXT, forgetting to escape hyphens, or mixing delimiters.
# Bad: () around a TAG value — parses as a TEXT clause, returns nothing
FT.SEARCH idx:bicycle "@condition:(new)"
# Bad: unescaped hyphen in a TAG — RQE treats the dash as NOT
FT.SEARCH idx:bicycle "@brand:{Giant-Cycles}" # returns 0 results
# Bad: NUMERIC values inside {} — silently empty
FT.SEARCH idx:bicycle "@price:{100 500}"
# Bad: parens unbalanced for numeric range — "Syntax error near +inf"
# The OUTER brackets of a numeric range are always [ ]. To make a bound
# exclusive, prefix the VALUE with ( inside the brackets.
FT.SEARCH idx:bicycle "@abv:(0.08 +inf]"
# Good: exclusive lower, inclusive upper
FT.SEARCH idx:bicycle "@abv:[(0.08 +inf]"
# Good: both bounds exclusive
FT.SEARCH idx:bicycle "@abv:[(0.08 (1.0]"| Delimiter | Use | Example |
|---|---|---|
( ) | TEXT phrase grouping / boolean grouping | (@type:{product} | @type:{post}) |
{ } | TAG exact-match (with | for alternatives) | @condition:{new|refurbished} |
[ ] | NUMERIC range, GEO, GEOSHAPE, VECTOR_RANGE | @price:[100 500], @price:[-inf 200] |
" " | exact phrase match in TEXT | "mountain bicycle" |
Client mirrors
# redis-py — STEP_START query_syntax
# Mirrors doctests/query_ft.py + query_em.py
from redis import Redis
r = Redis()
# TAG with escaped hyphen
r.ft("idx:bicycle").search(r"@brand:{Giant\-Cycles}")
# NUMERIC range
r.ft("idx:bicycle").search("@price:[100 500]")
# Boolean: type mountain OR road, exclude used
r.ft("idx:bicycle").search("(@type:{mountain}|@type:{road}) -@condition:{used}")
# STEP_END// Jedis — STEP_START query_syntax
// Mirrors QueryFtExample.java + QueryEmExample.java
import redis.clients.jedis.UnifiedJedis;
import redis.clients.jedis.search.Query;
try (UnifiedJedis jedis = new UnifiedJedis("redis://localhost:6379")) {
// TAG with escaped hyphen — note Java requires double-escaping the backslash
jedis.ftSearch("idx:bicycle", new Query("@brand:{Giant\\-Cycles}"));
jedis.ftSearch("idx:bicycle", new Query("@price:[100 500]"));
jedis.ftSearch("idx:bicycle",
new Query("(@type:{mountain}|@type:{road}) -@condition:{used}"));
}
// STEP_ENDUpstream sources
redis-py:
doctests/query_ft.py,query_em.py,query_geo.py,query_range.pyJedis:
QueryFtExample.java,QueryEmExample.java,QueryGeoExample.javaReference: Query Syntax, Escaping
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