Vector & semantic search
|
This section documents the current Elasticsearch 9.x line (with 8.19 as the final 8.x release) as published at the Elasticsearch documentation, which is the reference these pages are written and verified against. No specific patch version is pinned. Some capabilities (Kibana-only UIs, the ML/NLP model-management workflow, cross-cluster replication, and parts of the paid / serverless-only surface) are linked, not documented in depth. This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production, as Elasticsearch iterates quickly. This section’s bibliography lists the reference material consulted while preparing these pages. |
Elasticsearch retrieves by vector similarity as well as by BM25 term matching. A dense_vector
field holds an embedding and is searched with kNN; a sparse_vector field holds term-weight pairs
produced by a model such as ELSER; a semantic_text field hides the embedding step entirely.
Retrievers then compose these with ordinary
full-text queries into one hybrid request. For
where vector search fits among the alternatives see
Choosing the Right Database (vector-database
section); for the same idea in a document database see
Couchbase Vector Search.
dense_vector and kNN
The dense_vector field
A dense_vector field stores a float array of fixed length. Set dims to the model’s output size
(inferred from the first indexed vector if omitted), similarity to the distance function used for
scoring (cosine is the default and normalises for you; dot_product needs unit-length vectors;
l2_norm is Euclidean), and leave index at its default true so the values go into an HNSW graph
for approximate search. index_options tunes that graph: type (int8_hnsw is the default and
quantises to one byte per dimension, hnsw keeps full float32, int4_hnsw and bbq_hnsw compress
further), m (neighbours per node, default 16) and ef_construction (build-time candidate list,
default 100).
PUT /products
{
"mappings": {
"properties": {
"name": { "type": "text" },
"name_embedding": {
"type": "dense_vector",
"dims": 384,
"similarity": "cosine",
"index": true,
"index_options": {
"type": "int8_hnsw",
"m": 16,
"ef_construction": 100
}
}
}
}
}
// https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html
PUT /products/_doc/1
{
"name": "wireless noise-cancelling headphones",
"name_embedding": [0.12, -0.98, 0.33, "...384 floats..."]
}
Setting index: false stores the vector but builds no graph — it can then only be scored by the
brute-force script_score shown below. See
dense_vector field type
for every index_options parameter and the quantization trade-offs.
Approximate kNN: the top-level knn option
The knn option on _search runs approximate nearest-neighbour search over the HNSW graph. field
and query_vector say what to compare; k is how many neighbours to return; num_candidates (>=
k, capped at 10000) is how many the graph keeps per shard while searching — raising it improves
recall at the cost of latency. Hits are scored by similarity and merged into the normal
hits.hits[].
GET /products/_search
{
"knn": {
"field": "name_embedding",
"query_vector": [0.11, -0.95, 0.30, "...384 floats..."],
"k": 10,
"num_candidates": 100
}
}
// https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html
To search from text instead of a pre-computed vector, replace query_vector with
query_vector_builder and let an inference endpoint embed the query at search time:
GET /products/_search
{
"knn": {
"field": "name_embedding",
"query_vector_builder": {
"text_embedding": {
"model_id": "my-text-embedding-model",
"model_text": "headphones that block background noise"
}
},
"k": 10,
"num_candidates": 100
}
}
knn may also be given as an array of clauses to search several vector fields at once. Full
parameter list and tuning guidance:
k-nearest neighbor (kNN) search.
Exact kNN with script_score
When the candidate set is already small (a tight filter, or a field mapped with index: false), a
script_score query over every matching document gives exact results with no graph. The vector
functions (cosineSimilarity, dotProduct, l1norm, l2norm) take the query vector as a script
parameter; add a constant so the final _score stays non-negative.
GET /products/_search
{
"query": {
"script_score": {
"query": { "term": { "category": "audio" } },
"script": {
"source": "cosineSimilarity(params.qv, 'name_embedding') + 1.0",
"params": {
"qv": [0.11, -0.95, 0.30, "...384 floats..."]
}
}
}
}
}
// Brute force: cost scales with the number of docs matching the inner query.
// https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#exact-knn
Filtered kNN
A filter inside the knn option restricts which documents can be returned while the graph is
traversed (pre-filtering), so k results come back even when the filter is highly selective — unlike a post-filter that would thin an already-small neighbour list. The filter is a standard
term-level query.
GET /products/_search
{
"knn": {
"field": "name_embedding",
"query_vector": [0.11, -0.95, 0.30, "...384 floats..."],
"k": 10,
"num_candidates": 100,
"filter": {
"bool": {
"must": [ { "term": { "in_stock": true } } ],
"filter": [ { "range": { "price": { "lte": 300 } } } ]
}
}
}
}
// https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#knn-search-filter-example
Sparse vectors, ELSER and semantic_text
sparse_vector and ELSER
ELSER (Elastic Learned Sparse EncodeR) is a built-in model that turns text into a set of weighted
term tokens rather than a dense embedding. Those pairs are stored in a sparse_vector field, and
querying is lexical-style term expansion, so no dims or similarity is involved. The usual path
is an inference endpoint plus an
ingest pipeline (or semantic_text, below) that
fills the field on write.
// 1. Create an inference endpoint backed by ELSER (downloads/starts the model).
PUT /_inference/sparse_embedding/my-elser
{
"service": "elasticsearch",
"service_settings": {
"num_allocations": 1,
"num_threads": 1,
"model_id": ".elser_model_2"
}
}
// https://www.elastic.co/guide/en/elasticsearch/reference/current/infer-service-elasticsearch.html
PUT /articles
{
"mappings": {
"properties": {
"body": { "type": "text" },
"body_tokens": { "type": "sparse_vector" }
}
}
}
// https://www.elastic.co/guide/en/elasticsearch/reference/current/sparse-vector.html
At search time the sparse_vector query points at the same endpoint, which expands the query string
into weighted tokens and scores documents by their overlap:
GET /articles/_search
{
"query": {
"sparse_vector": {
"field": "body_tokens",
"inference_id": "my-elser",
"query": "how do I renew an expired certificate"
}
}
}
// https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-sparse-vector-query.html
Background and deployment sizing for the model: ELSER.
semantic_text and the semantic query
A semantic_text field wires the model in at mapping time: you name an inference_id once, and
every value indexed into the field is sent to that endpoint, chunked into passages that fit the
model’s input limit, embedded, and stored — dense or sparse depending on the endpoint’s task type.
No ingest pipeline, no manual vector field.
PUT /docs
{
"mappings": {
"properties": {
"content": {
"type": "semantic_text",
"inference_id": "my-elser",
"chunking_settings": {
"strategy": "sentence",
"max_chunk_size": 250,
"sentence_overlap": 1
}
}
}
}
}
// inference_id defaults to the built-in ".elser-2-elasticsearch" endpoint if omitted.
// https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-text.html
PUT /docs/_doc/1
{ "content": "Long multi-paragraph text. Elasticsearch splits it into chunks and embeds each one." }
The semantic query takes only the field and the natural-language string; Elasticsearch embeds the
query with the same endpoint and matches it against the stored chunks, returning the best passages
in inner_hits:
GET /docs/_search
{
"query": {
"semantic": {
"field": "content",
"query": "how is long text broken up before embedding"
}
}
}
// https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-semantic-query.html
The end-to-end walk-through is
Semantic search with semantic_text;
the conceptual overview of all three approaches (dense, ELSER, semantic_text) is
Semantic search.
Retrievers and hybrid search
A retriever is a node in a tree that produces a ranked document set, replacing the flat
query/knn top-level keys. Composing retrievers is how lexical and vector results are combined
and re-ranked in a single call.
| Retriever | What it returns |
|---|---|
|
The hits of any Query DSL |
|
Approximate kNN hits, same parameters as the top-level |
|
A fused ranking of its child retrievers via Reciprocal Rank Fusion |
|
Its child’s hits re-ordered by a cross-encoder / rerank inference endpoint |
rrf: Reciprocal Rank Fusion for hybrid results
rrf merges its retrievers by rank position, not by score, so a BM25 score and a cosine
similarity never have to be normalised onto one scale. Each document scores sum(1 / (rank_constant
+ rank)) across the lists it appears in; rank_constant (default 60) damps the contribution of low
ranks and rank_window_size (default 10) is how deep into each child list the fusion looks.
GET /products/_search
{
"retriever": {
"rrf": {
"retrievers": [
{
"standard": {
"query": { "match": { "name": "noise cancelling headphones" } }
}
},
{
"knn": {
"field": "name_embedding",
"query_vector": [0.11, -0.95, 0.30, "...384 floats..."],
"k": 20,
"num_candidates": 100
}
}
],
"rank_constant": 60,
"rank_window_size": 20
}
}
}
// https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html
text_similarity_reranker
Wrap any retriever to have a rerank model re-score the top rank_window_size hits against the query
text — typically as the outer stage over an rrf node.
GET /docs/_search
{
"retriever": {
"text_similarity_reranker": {
"retriever": {
"standard": { "query": { "semantic": { "field": "content", "query": "certificate renewal steps" } } }
},
"field": "content",
"inference_id": "my-rerank-endpoint",
"inference_text": "certificate renewal steps",
"rank_window_size": 50
}
}
}
// https://www.elastic.co/guide/en/elasticsearch/reference/current/retrievers-overview.html
The retriever catalogue, nesting rules and which _search features (pagination, aggregations,
highlighting) work with each are in
Retrievers.
Retrievers, aggregations and result shaping across the rest of the search surface are covered in
Search extras and
Search API & pagination.