This guide teaches an agent how to perform high-quality relevance search on content indices using ES|QL.
- Scope
- Quick Strategy Rules
- Standard Workflow
- Multi-Stage Retrieval Pattern
- Retrieval Strategies
- Semantic Intent Without Semantic Field
- Reranking Stage
- Phrase Search
- Multi-Index Search
- Weak Result Recovery
- Mandatory Rules
- Final Decision Process
This guidance is designed for:
- document search
- knowledge bases
- articles
- documentation
- product content
It is not intended for logs or observability datasets.
Agents should always follow a multi-stage retrieval pattern:
retrieve → (optional fuse) → rerank
Start with inexpensive retrieval and apply expensive ranking only to a limited candidate set.
| Situation | Strategy |
|---|---|
semantic_text field exists |
semantic MATCH |
only text fields exist |
lexical MATCH |
dense_vector field exists |
KNN retrieval |
| lexical + semantic fields exist | hybrid retrieval |
| semantic intent but no semantic field | lexical → RERANK |
| exact wording required | MATCH_PHRASE |
| multiple indices | branch search using FORK + FUSE |
Use the narrowest index pattern that satisfies the request.
// Single index
FROM knowledge-base
// Multiple indices
FROM docs-*,articles-*
Before writing queries, identify searchable fields.
Preferred fields for content search:
title
name
subject
summary
body
content
description
text
| Field type | Purpose |
|---|---|
semantic_text |
semantic retrieval |
text |
lexical retrieval |
dense_vector |
vector retrieval |
keyword |
filtering only |
Never use keyword fields for natural language search.
titlesummarybody/contentdescriptiontext
Short fields provide precision. Long fields provide recall.
Use the Quick Strategy Rules table to select the right approach based on the available field types. Then follow the matching retrieval pattern below.
Always follow this structure:
- retrieve candidate documents
- optionally combine retrieval strategies
- rerank candidates
Typical candidate size: 50–200 documents. Use smaller sets when fields are strong. Use larger sets when recall is important.
Use when only text fields exist.
FROM my-index METADATA _score
| WHERE MATCH(title, ?query) OR MATCH(body, ?query)
| SORT _score DESC
| LIMIT 100
Guidelines:
- one field per
MATCH— the first argument is a single mapped field (title,content), not a quoted pseudo-field like"title,content"and not two identifiers before the query (MATCH(title, body, "q")is invalid); useMATCH(a, ?query) OR MATCH(b, ?query)orQSTR/KQLfor multi-field search - a single
MATCHon the main content field is often sufficient - add additional fields (title, summary) only when recall is poor or the user explicitly asks for broader search
- title fields provide precision, body fields provide recall
- prefer
MATCHoverLIKEorRLIKE
Use when a semantic_text field exists. MATCH is required for semantic_text fields — it automatically performs
vector-based semantic search. No separate function is needed.
FROM my-index METADATA _score
| WHERE MATCH(semantic_body, ?query)
| SORT _score DESC
| LIMIT 100
Prefer the semantic field representing the main document body.
Version:
KNNis 9.2+ (preview).TEXT_EMBEDDINGis 9.3+. Verify cluster version viaesql-version-history.mdbefore using these functions. For clusters below 9.2, use semantic retrieval withsemantic_text+MATCHinstead.
Use when embeddings are stored as dense_vector. KNN can also target semantic_text fields.
FROM my-index METADATA _score
| WHERE KNN(content_embedding, TEXT_EMBEDDING(?query, "embedding_endpoint"))
| SORT _score DESC
| LIMIT 100
Rules:
- the query embedding model must match the document embeddings
- always retrieve a bounded candidate set
- avoid embedding operations across the full index
Version:
FORKis 8.19/9.1+ (preview).FUSEis 9.2+ (preview). On clusters below 9.2, use lexical retrieval followed byRERANKas a fallback. On clusters below 8.19/9.1, use a single-branchMATCHwithRERANK.
Use when both lexical and semantic fields exist.
FROM my-index METADATA _id, _index, _score
| FORK
(
WHERE MATCH(title, ?query) OR MATCH(body, ?query)
| SORT _score DESC
| LIMIT 100
)
(
WHERE MATCH(semantic_body, ?query)
| SORT _score DESC
| LIMIT 100
)
| FUSE
| SORT _score DESC
| LIMIT 100
Hybrid retrieval improves both recall and precision.
Pipeline:
retrieve lexically
retrieve semantically
fuse
rerank
If semantic search is requested but the index lacks semantic_text:
- retrieve candidates lexically
- rerank results semantically
FROM my-index METADATA _score
| WHERE MATCH(title, ?query) OR MATCH(body, ?query) OR MATCH(summary, ?query)
| SORT _score DESC
| LIMIT 100
Never stop at "semantic search unavailable" without attempting lexical retrieval.
Version:
RERANKis 9.2+ (preview). On clusters below 9.2, skip the reranking stage and rely on initial retrieval scoring. For clusters below 9.2, sorting by_score DESCafterMATCHprovides BM25 or vector-based ordering.
Always rerank a bounded candidate set.
FROM my-index METADATA _score
| WHERE MATCH(body, ?query)
| SORT _score DESC
| LIMIT 100
| RERANK body ON ?query
| SORT _score DESC
| LIMIT 20
Use when:
- lexical retrieval produced good candidates
- semantic ranking improves ordering
Version:
V_COSINEand other vector similarity functions are 9.3+ (preview). Verify cluster version viaesql-version-history.mdbefore using these functions.
Use when document embeddings exist.
FROM my-index METADATA _score
| WHERE MATCH(body, ?query)
| SORT _score DESC
| LIMIT 100
| EVAL q = TEXT_EMBEDDING(?query, "embedding_endpoint")
| EVAL semantic_score = V_COSINE(content_embedding, q)
| SORT semantic_score DESC
| LIMIT 20
Rules:
- compute the query embedding once
- compare only against candidate documents
- avoid vector scans across entire indices
Use when exact wording matters.
FROM my-index METADATA _score
| WHERE MATCH_PHRASE(body, ?phrase)
| SORT _score DESC
| LIMIT 20
Typical cases:
- product names
- quoted text
- error messages
- legal phrases
When querying multiple indices:
- inspect mappings
- confirm compatible fields
- branch queries when schemas differ
- fuse results
- rerank candidates
_indexis not implicit: You may use_indexin queries (for exampleWHERE _index LIKE "docs-%") only if theFROMclause requests it viaMETADATA _index(typicallyMETADATA _id, _index, _scoreforFORK/FUSE). If_indexis omitted fromMETADATA, the column does not exist in the pipeline — the query fails with an unknown-field error. Prefer the compatible-schemas pattern when you do not need per-index branching.
FROM docs-*,articles-* METADATA _score
| WHERE MATCH(title, ?query) OR MATCH(body, ?query)
| SORT _score DESC
| LIMIT 20
FROM docs-*,support-* METADATA _id, _index, _score
| FORK
(
WHERE _index LIKE "docs-%"
| WHERE MATCH(body, ?query)
| SORT _score DESC
| LIMIT 100
)
(
WHERE _index LIKE "support-%"
| WHERE MATCH(content, ?query)
| SORT _score DESC
| LIMIT 100
)
| FUSE
| SORT _score DESC
| LIMIT 20
Always keep the pattern simple:
retrieve per index family → fuse → rerank
If results are weak:
- search additional content fields
- increase candidate size
- apply semantic reranking
- switch to hybrid retrieval
- inspect mappings for stronger fields
- split multi-index search by index family
Avoid jumping directly to expensive ranking.
Always:
- inspect mappings first
- retrieve candidates before reranking
- limit candidate sets to ~50–200
- use
_scorefor ranking - prefer content fields over metadata
- follow retrieve → fuse → rerank
Never:
- search natural language in
keywordfields - run embedding operations across entire indices
- skip the retrieval stage
- use
LIKEfor relevance search - dump full mappings unless necessary
Use the examples from Retrieval Strategies as starting templates:
- Lexical — see Lexical Retrieval
- Semantic — see Semantic Retrieval
- Hybrid — see Hybrid Retrieval, then add a Reranking Stage
Use the hybrid pattern when both lexical and semantic fields exist.
Follow this order:
- inspect mappings
- choose best retrieval field family
- retrieve candidates
- fuse if necessary
- rerank
- return results
Mental model:
retrieve → fuse → rerank