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://mp-staging.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/aggregate-pipeline.md
Build FT.AGGREGATE Pipelines in the Correct Stage Order
FT.AGGREGATE runs stages in the order you write them, like a Unix pipeline. The canonical order is LOAD → APPLY → FILTER → GROUPBY/REDUCE → APPLY → SORTBY → LIMIT. Swapping stages doesn't error — it silently changes what your query computes. For paginating large aggregates, see aggregate-cursors.md.
Correct: Canonical pipeline against the Bicycle dataset — load needed fields, project a derived field, filter, group, sort, limit.
# Average price per brand for mountain bicycles, top 5 brands
FT.AGGREGATE idx:bicycle "@type:{mountain}"
LOAD 3 @brand @price @condition
APPLY "@price * 0.9" AS sale_price
FILTER "@condition == 'new'"
GROUPBY 1 @brand
REDUCE COUNT 0 AS bike_count
REDUCE AVG 1 @price AS avg_price
REDUCE AVG 1 @sale_price AS avg_sale_price
SORTBY 2 @avg_price DESC
LIMIT 0 5
DIALECT 2Stages, in order
| Stage | Purpose | Notes |
|---|---|---|
LOAD n @f1 @f2 ... | Hydrate fields from the source doc into the pipeline. | Only loaded fields are visible to later stages. LOAD * pulls everything (expensive). |
APPLY <expr> AS alias | Project a computed field. | Operates row-by-row before grouping. |
FILTER <expr> | Drop rows that fail a predicate. | Filters pipeline rows, not the underlying index. Index-level filters belong in the query string. |
GROUPBY n @f1 ... REDUCE <fn> ... | Collapse rows that share group keys. | Reducers: COUNT, COUNT_DISTINCT, SUM, AVG, MIN, MAX, STDDEV, QUANTILE, TOLIST, FIRST_VALUE, RANDOM_SAMPLE. |
APPLY (post-group) | Compute derived fields over reducer output. | E.g. APPLY "@bike_count / @brand_count" AS share. |
SORTBY n @f1 ASC ... | Order the result. | The n is the count of (field, direction) tokens. |
LIMIT offset num | Slice the result. | For result sets > 1000 rows, use WITHCURSOR (see aggregate-cursors.md). |
Common errors per stage — see "Counting tokens, not fields" and "FILTER and LOAD discipline" below for nargs miscount on
LOAD/GROUPBY/SORTBY/REDUCE COUNT, missing@on pipeline field references, missingASC/DESConSORTBY, and FILTER-before-LOAD errors.
Common reducers — quick reference
REDUCE COUNT 0 AS n # count rows in group
REDUCE COUNT_DISTINCT 1 @user_id AS uniq # distinct values of @user_id
REDUCE SUM 1 @price AS total
REDUCE AVG 1 @price AS mean
REDUCE MIN 1 @price AS lo
REDUCE MAX 1 @price AS hi
REDUCE QUANTILE 2 @price 0.95 AS p95
REDUCE TOLIST 1 @model AS models # collect into a list
REDUCE FIRST_VALUE 1 @model BY @price DESC AS top_modelCounting tokens, not fields
The most frequent class of FT.AGGREGATE parse errors is treating <nargs> as "number of semantic fields" when Redis counts tokens that follow. Same root cause, four shapes.
Every pipeline field reference starts with @. The @ is part of the field token in LOAD, GROUPBY, SORTBY, APPLY, and FILTER — not just in the query string. Inside expressions like @field >= 5 or substr(@date, 0, 4), the @ is still required.
# Bad: missing @ on pipeline field references
GROUPBY 1 category # "Unknown property 'category'. Did you mean '@category'?"
APPLY substr(date, 0, 4) AS year # "Unknown symbol 'date'"
FILTER "date >= '2022-01'" # "Unknown symbol 'date'"
# Good
GROUPBY 1 @category
APPLY substr(@date, 0, 4) AS year
FILTER "@date >= '2022-01'"REDUCE always follows a GROUPBY. For a whole-result aggregate, use GROUPBY 0.
# Bad: REDUCE without a preceding GROUPBY — "Unknown argument 'REDUCE' at position 1"
FT.AGGREGATE idx:bicycle "@type:{mountain}" REDUCE AVG 1 @price AS avg_price DIALECT 2
# Good: single-row aggregate over all matched docs
FT.AGGREGATE idx:bicycle "@type:{mountain}"
GROUPBY 0
REDUCE AVG 1 @price AS avg_price
DIALECT 2REDUCE COUNT 0 — the 0 is mandatory even though COUNT takes no args. <nargs> is the count of arguments to the reducer, regardless of whether the reducer "really" needs any.
# Bad: "Bad arguments for COUNT: could not convert ..."
GROUPBY 0 REDUCE COUNT AS count
# Good
GROUPBY 0 REDUCE COUNT 0 AS countLOAD <n> also counts tokens, not fields. path AS alias = 3 tokens. Same counting rule as RETURN. An aliased JSONPath ($.path AS alias) needs nargs=3; an unaliased load needs nargs=1.
# Bad: nargs=1 but the LOAD has 3 tokens — "Unknown argument 'AS' at position 4"
LOAD 1 $.beers[?(@.abv >= 0.07)] AS match
LOAD 1 $.brewery_id AS brewery_id
# Good: 1 aliased path = 3 tokens
LOAD 3 $.beers[?(@.abv >= 0.07)] AS match
# Good: 2 unaliased + 1 aliased = 2 + 3 = 5 tokens
LOAD 5 @date @subject $.event.ts AS tsSORTBY <n> counts tokens, not fields. Each sort entry is @field ASC|DESC = 2 tokens. Always supply ASC or DESC.
# Bad: SORTBY 1 @count DESC — Redis consumes 1 token, then "Unknown argument 'DESC'"
SORTBY 1 @count DESC
# Bad: SORTBY 2 @brand DESC @price ASC — said 2, gave 4, trailing tokens become unknown args
SORTBY 2 @brand DESC @price ASC
# Good: one sort entry = 2 tokens
SORTBY 2 @count DESC
# Good: two sort entries = 4 tokens
SORTBY 4 @count DESC @brand ASCA single pipeline can contain at most one SORTBY step ("Multiple SORTBY steps are not allowed"). For a secondary sort, extend the same token list rather than writing two SORTBY clauses.
For top-N, prefer SORTBY … MAX N over a trailing LIMIT 0 N. MAX lives inside SORTBY, is more efficient, and does not compose with LIMIT — pick one.
# Good (preferred): in-sort limit
SORTBY 2 @count DESC MAX 5
# Equivalent but less efficient
SORTBY 2 @count DESC
LIMIT 0 5
# Bad: combining MAX and LIMIT — redundant; pick one
SORTBY 2 @count DESC MAX 5 LIMIT 0 5FILTER and LOAD discipline
Pipeline FILTER operates on pipeline rows, which only contain attributes that were declared SORTABLE in the schema (auto-projected) or explicitly LOADed. Plain TEXT/TAG fields are not auto-projected.
Prefer query-string filters over pipeline FILTER whenever possible. The query string runs against the index; pipeline FILTER runs after candidates are materialized.
# Bad: FILTER on a TEXT field that wasn't loaded — "Unknown symbol 'date'"
FT.AGGREGATE idx:observations "@code:\"heart rate\""
GROUPBY 0 REDUCE COUNT 0 AS count
FILTER "@date >= '2023-01-01'"
DIALECT 2
# Good: express the date filter in the query string (it hits the index directly)
FT.AGGREGATE idx:observations "@date:2023* @code:\"heart rate\""
GROUPBY 0 REDUCE COUNT 0 AS count
DIALECT 2
# Also valid: LOAD the field first, then FILTER on it in the pipeline
FT.AGGREGATE idx:observations "@code:\"heart rate\""
LOAD 1 @date
FILTER "@date >= '2023-01-01'"
GROUPBY 0 REDUCE COUNT 0 AS count
DIALECT 2Downstream stages must reference the exact alias that was LOADed. Loading a JSONPath without AS projects it under the path string, not its basename.
# Bad: loaded $.subject, then referenced @timestamp
# → "Property '@timestamp' not loaded nor in schema"
LOAD 1 $.subject
SORTBY 2 @timestamp DESC
# Good: load every alias you'll reference; rename with AS when needed
LOAD 2 @date @subject
SORTBY 2 @date DESC
# Good: explicit AS for a nested JSONPath
LOAD 2 $.subject AS subject $.event.ts AS ts
SORTBY 2 @ts DESCNested JSON array predicates. For "how many parents have at least one child matching X," use a JSONPath predicate + exists:
# How many breweries have at least one beer with ABV >= 0.07?
FT.AGGREGATE idx:breweries "*"
LOAD 1 $.beers[?(@.abv >= 0.07)] AS match
FILTER "exists(@match)"
GROUPBY 0 REDUCE COUNT 0 AS qualifying_breweries
DIALECT 2Reducer by intent
Match the reducer to the shape of the question.
# "how many" → GROUPBY 0 REDUCE COUNT 0 AS n
# "how many distinct X" → GROUPBY 1 @x GROUPBY 0 REDUCE COUNT 0 AS n
# (or single-step: REDUCE COUNT_DISTINCT 1 @x AS n)
# "list distinct X" → GROUPBY 0 REDUCE TOLIST 1 @x AS list
# "sum / total" → REDUCE SUM 1 @field AS total
# "average per Y" → GROUPBY 1 @y REDUCE AVG 1 @field AS avg
# "top row by stat" → REDUCE FIRST_VALUE 4 @other_field BY @stat DESC AS top
# "p95 / quantile" → REDUCE QUANTILE 2 @field 0.95 AS p95
# "min / max within group" → REDUCE MIN 1 @field / REDUCE MAX 1 @fieldMulti-step pipeline patterns
These compose multiple GROUPBY + REDUCE steps. The pipeline is sequential: each stage's output becomes the next stage's input, so the order of stages matters and the field names must thread through.
Top-N per group (per-group count, then global sort + cap). "Top N most frequent X per Y" — group by both first, then re-group by Y and pick the top X.
# Q: "Top brand per state by bike count"
FT.AGGREGATE idx:bicycle "*"
GROUPBY 2 @state @brand
REDUCE COUNT 0 AS cnt
GROUPBY 1 @state
REDUCE FIRST_VALUE 4 @brand BY @cnt DESC AS top_brand
REDUCE MAX 1 @cnt AS top_cnt
SORTBY 2 @top_cnt DESC
DIALECT 2Distinct values with their counts (single group + count). Two-column return: the value and how many docs have it.
# Q: "How many bikes per category"
FT.AGGREGATE idx:bicycle "*"
GROUPBY 1 @category
REDUCE COUNT 0 AS n
SORTBY 2 @n DESC
DIALECT 2Count distinct (two-stage) — when you need how many unique values, not a per-value list. The first GROUPBY collapses duplicates; the second counts the resulting rows.
# Q: "How many distinct brands sell mountain bikes?"
FT.AGGREGATE idx:bicycle "@type:{mountain}"
GROUPBY 1 @brand
GROUPBY 0 REDUCE COUNT 0 AS distinct_brand_count
DIALECT 2Bucket-by-derived-field (APPLY before GROUPBY). When the bucket isn't a stored field — extract it with APPLY, then group on the alias.
# Q: "Bites per year"
FT.AGGREGATE idx:bites "*"
LOAD 1 @DateOfBite
APPLY year(@DateOfBite) AS year
GROUPBY 1 @year REDUCE COUNT 0 AS n
SORTBY 2 @year ASC
DIALECT 2Filter-after-derive (year extraction + filter). When the question is "in YEAR X" but the field is a timestamp — APPLY year(@ts) AS year then FILTER "@year == X". Don't try to express this in the query string; the query DSL has no year().
# Q: "How many bites in 2016 by breed"
FT.AGGREGATE idx:bites "@Breed:rottweiler"
LOAD 1 @DateOfBite
APPLY year(@DateOfBite) AS year
FILTER "@year == 2016"
GROUPBY 0 REDUCE COUNT 0 AS n
DIALECT 2Multi-stage rule: every stage produces a flat row of (name, value) pairs. Downstream stages only see names that earlier stages emitted — either a LOADed field, a GROUPBY key, a REDUCE … AS alias, or an APPLY … AS alias. Reference fields via their current alias, not the source path.
APPLY functions
APPLY accepts a fixed allowlist of math, string, and time functions. round, now(), and date() do not exist — invoking them returns Unknown function name.
| Category | Functions |
|---|---|
| Math | ceil, floor, abs, log, exp, sqrt, pow, mod |
| String | substr, format, upper, lower, matched_terms, contains, startswith, strlen |
| Time | parse_time, day, month, year, monthofyear, dayofweek, dayofmonth, dayofyear, hour, minute, timefmt |
| Geo | geodistance |
No round — emulate via floor / ceil.
# Round @weight to 2 decimal places
APPLY floor(@weight * 100) / 100 AS weight_roundedsubstr(s, start, length) — 0-indexed start, length is the number of characters to take (not the end position). ISO dates YYYY-MM-DD:
APPLY substr(@date, 0, 4) AS year # YYYY
APPLY substr(@date, 5, 2) AS month # MM
APPLY substr(@date, 8, 2) AS day # DD
APPLY substr(@date, 0, 7) AS yyyy_mm # YYYY-MMcontains for TEXT substring filtering — the query-string form -@field:value does not negate substrings on TEXT. Use contains in a pipeline FILTER:
# "Cities NOT containing 'ile'"
FT.AGGREGATE idx:cities "*"
LOAD 1 @city
FILTER "!contains(@city, 'ile')"
DIALECT 2Incorrect: Filtering after grouping when you meant to filter the source rows; mismatched n count on GROUPBY/SORTBY; loading every field "just in case."
# Bad: FILTER after GROUPBY filters group rows, not source rows.
# Intent was "only new bikes," but here you keep all groups and trim brand rows by mean price.
FT.AGGREGATE idx:bicycle "*"
GROUPBY 1 @brand REDUCE AVG 1 @price AS avg_price
FILTER "@condition == 'new'" # @condition no longer exists post-group!
DIALECT 2
# Bad: GROUPBY count mismatched — RESP parse error or surprising grouping
FT.AGGREGATE idx:bicycle "*"
GROUPBY 2 @brand # said 2 fields but only listed 1
REDUCE COUNT 0 AS n
DIALECT 2
# Bad: LOAD * inflates the pipeline payload on every doc
FT.AGGREGATE idx:bicycle "*" LOAD * GROUPBY 1 @brand REDUCE COUNT 0 AS n DIALECT 2Client mirrors
# redis-py — STEP_START aggregate_pipeline
# Mirrors doctests/query_agg.py
from redis import Redis
from redis.commands.search.aggregation import AggregateRequest
from redis.commands.search.reducers import count, avg, sort_by
r = Redis()
req = (
AggregateRequest("@type:{mountain}")
.load("@brand", "@price", "@condition")
.apply(sale_price="@price * 0.9")
.filter("@condition == 'new'")
.group_by("@brand", count().alias("bike_count"), avg("@price").alias("avg_price"))
.sort_by(("@avg_price", "DESC"))
.limit(0, 5)
.dialect(2)
)
results = r.ft("idx:bicycle").aggregate(req)
# STEP_END// Jedis — STEP_START aggregate_pipeline
// Mirrors QueryAggExample.java
import redis.clients.jedis.UnifiedJedis;
import redis.clients.jedis.search.aggr.AggregationBuilder;
import redis.clients.jedis.search.aggr.Reducers;
import redis.clients.jedis.search.aggr.SortedField;
try (UnifiedJedis jedis = new UnifiedJedis("redis://localhost:6379")) {
AggregationBuilder agg = new AggregationBuilder("@type:{mountain}")
.load("@brand", "@price", "@condition")
.apply("@price * 0.9", "sale_price")
.filter("@condition == 'new'")
.groupBy("@brand",
Reducers.count().as("bike_count"),
Reducers.avg("@price").as("avg_price"))
.sortBy(SortedField.desc("@avg_price"))
.limit(0, 5)
.dialect(2);
jedis.ftAggregate("idx:bicycle", agg);
}
// STEP_ENDUpstream sources
redis-py:
doctests/query_agg.pyJedis:
QueryAggExample.javaReference: FT.AGGREGATE, Aggregations
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