Approximate kNN query examples
This page collects query patterns specific to approximate kNN: requiring a minimum similarity, combining approximate kNN with other retrieval methods, searching several vector fields at once, and aggregating over the nearest neighbors.
Except where a text embedding model is required, the examples run against the image-index mapping and sample data from Approximate kNN search. That index stores an image-vector field with l2_norm similarity, a title text field, and a file-type keyword field.
The examples use whichever of the three kNN forms suits the pattern: the top-level knn option, the knn query, or the knn retriever. Refer to Approximate kNN search methods to choose between them for your own searches. For query vector examples shared by exact and approximate kNN, refer to kNN search examples. For filtered approximate kNN search, refer to Filter approximate kNN results. For vectors stored in nested fields, refer to Nested approximate kNN search.
Approximate kNN always tries to return k nearest neighbors, even when none of them are close. Combined with a filter, this means you can filter away every relevant document and still receive k hits, drawn from whatever distant vectors remain.
Use the similarity parameter to set a threshold that a vector must meet to be considered a match. The knn search flow with this parameter is:
- Apply any user-provided
filterqueries. - Explore the vector space to gather
kcandidates. - Discard candidates that don't meet the
similaritythreshold.
Because the threshold is applied last, a search can return fewer than k hits, which is the point of the parameter.
What the threshold means depends on the similarity configured on the field:
l2_norm: a maximum distance. Matches are the vectors that fall within adims-dimensional hypersphere of radiussimilarity, centered onquery_vector. Lower values are stricter.cosine,dot_product, andmax_inner_product: a minimum similarity. Higher values are stricter.
similarity is the true similarity value before it is transformed into _score and before any boosts are applied.
To derive a threshold from a _score you've already observed, invert the score. For float and bfloat16 vectors:
l2_norm:sqrt((1 / _score) - 1)cosine:(2 * _score) - 1dot_product:(2 * _score) - 1max_inner_product:_score < 1:1 - (1 / _score)_score >= 1:_score - 1
byte and bit vectors use different score formulas, so these inversions don't apply to them.
The following query searches for the given query_vector, restricts results to PNG files, and requires that matches fall within an l2_norm distance of 36:
POST image-index/_search
{
"knn": {
"field": "image-vector",
"query_vector": [1, 5, -20],
"k": 5,
"num_candidates": 50,
"similarity": 36,
"filter": {
"term": {
"file-type": "png"
}
}
},
"fields": ["title"],
"_source": false
}
In this data set, the only document with file-type = png has the vector [42, 8, -15]. Its l2_norm distance from [1, 5, -20] is 41.412, which is farther than the threshold of 36 allows. The filter leaves one candidate and the threshold rejects it, so this search returns no hits.
Combine approximate kNN with other retrieval methods when you want one ranked result list that reflects both how similar documents are to your query vector and how well they match specific words or phrases. For example, you might find images that look similar to a reference photo while also matching a title keyword like "mountain lake".
The difficulty is that BM25 scores and vector similarity scores live on unrelated scales, and those scales shift with the query. Combining them by rank, or by normalizing them first, is more robust than adding raw scores together.
Reciprocal rank fusion (RRF) merges result sets by the rank a document holds in each one, ignoring the raw scores entirely. This is the recommended starting point for hybrid search, because it needs no score tuning.
Pass a standard retriever for the keyword query and a knn retriever for the vector search to an rrf retriever:
POST image-index/_search
{
"retriever": {
"rrf": {
"retrievers": [
{
"standard": {
"query": {
"match": {
"title": "mountain lake"
}
}
}
},
{
"knn": {
"field": "image-vector",
"query_vector": [54, 10, -2],
"k": 50,
"num_candidates": 100
}
}
],
"rank_window_size": 50
}
},
"size": 10
}
- How many results to pull from each retriever before merging. Raising it improves relevance at the cost of performance. It must be at least as large as
size, and defaults to10. Set theknnretriever'skto at least this value, or the vector result set won't fill the window.
When you do want explicit control over how much each signal contributes, use a linear retriever. It normalizes each retriever's scores, then combines them as a weighted sum. Normalizing first is what makes the weights meaningful, because it puts both result sets on the same 0-to-1 scale before the weights apply.
POST image-index/_search
{
"retriever": {
"linear": {
"retrievers": [
{
"retriever": {
"standard": {
"query": {
"match": {
"title": "mountain lake"
}
}
}
},
"weight": 0.9,
"normalizer": "minmax"
},
{
"retriever": {
"knn": {
"field": "image-vector",
"query_vector": [54, 10, -2],
"k": 50,
"num_candidates": 100
}
},
"weight": 0.1,
"normalizer": "minmax"
}
],
"rank_window_size": 50
}
},
"size": 10
}
- The multiplier applied to this retriever's normalized scores. Defaults to
1.0. - How to normalize this retriever's scores before weighting.
minmaxrescales each result set to a range of 0 to 1.
You can also perform hybrid retrieval without retrievers, by combining the knn option with a standard query in the same request. This is the most direct form, but it adds the two raw scores together, so you have to tune the boosts by hand for your data and query mix.
POST image-index/_search
{
"query": {
"match": {
"title": {
"query": "mountain lake",
"boost": 0.9
}
}
},
"knn": {
"field": "image-vector",
"query_vector": [54, 10, -2],
"k": 5,
"num_candidates": 50,
"boost": 0.1
},
"size": 10
}
This search finds the global top k = 5 vector matches, combines them with the matches from the match query, and returns the 10 top-scoring results. The knn and query matches are combined through a disjunction, as if you took a boolean OR between them. The top k vector results represent the global nearest neighbors across all index shards.
The score of each result is the sum of the knn and query scores, and the boost values weight each score in that sum. In the preceding example, the scores are calculated as follows:
score = 0.9 * match_score + 0.1 * knn_score
For more on hybrid search, including approaches that use semantic_text fields and ES|QL, refer to Hybrid search.
Search multiple vector fields with approximate kNN when your documents store more than one vector representation and you want to rank results by similarity across all of them in a single request. For example, you might search an image embedding and a title embedding together to surface documents that are both visually and semantically relevant.
These examples add a second vector field, title-vector, to the image-index mapping created in Approximate kNN search:
PUT image-index/_mapping
{
"properties": {
"title-vector": {
"type": "dense_vector",
"similarity": "l2_norm"
}
}
}
Pass an array to the knn option to search both fields, optionally alongside a query:
POST image-index/_search
{
"query": {
"match": {
"title": {
"query": "mountain lake",
"boost": 0.9
}
}
},
"knn": [ {
"field": "image-vector",
"query_vector": [54, 10, -2],
"k": 5,
"num_candidates": 50,
"boost": 0.1
},
{
"field": "title-vector",
"query_vector": [1, 20, -52, 23, 10],
"k": 10,
"num_candidates": 100,
"boost": 0.5
}],
"size": 10
}
This search retrieves the global top k = 5 neighbors for image-vector and the global top k = 10 for title-vector. These vector result sets are combined with the matches from the match query, and the top 10 overall documents are returned. Multiple knn clauses and the query clause are combined via a disjunction (boolean OR). The top k vector results represent the global nearest neighbors across all index shards.
With the boosts configured above, a document is scored as:
score = 0.9 * match_score + 0.1 * knn_score_image-vector + 0.5 * knn_score_title-vector
As with hybrid retrieval, you can instead pass one knn retriever per field to an rrf or linear retriever, which spares you from balancing raw scores across fields. Refer to Approximate kNN query examples > Use approximate kNN in hybrid search.
You can use aggregations with the knn option, but the buckets cover a different document set than you might expect. Elasticsearch computes aggregations over the documents that match the search, and for approximate kNN that means the top k nearest documents rather than everything in the index. If the request also includes a query, aggregations cover the combined set of knn and query matches.
The following request buckets the five nearest images by file type:
POST image-index/_search
{
"knn": {
"field": "image-vector",
"query_vector": [-5, 9, -12],
"k": 5,
"num_candidates": 50
},
"aggs": {
"file-types": {
"terms": {
"field": "file-type"
}
}
}
}
The file-types buckets describe the five nearest neighbors only, so treat the counts as a summary of the result set rather than of the index. To aggregate across all documents that match a filter, run a separate request without a knn clause.
Use these resources to explore related search approaches and API details:
- Tune approximate kNN search: Production guidance for vector memory, node sizing, indexing, filesystem cache, and on-disk rescoring.
- Profile kNN search: Inspect query timing and vector operation counts to diagnose slow kNN searches.
dense_vectorfield type: API reference for vector field mapping, includingindex,similarity,index_options, and quantization parameters.knnquery: API reference for theknnquery, including parameters,query_vector_builderoptions, and usage withdense_vectorandsemantic_textfields.