Vector and hybrid search

Elasticsearch · Advanced · 14 min

Semantic search and retrieval for AI applications rely on vector similarity, often blended with classic text relevance.

Vector search finds documents whose embedding vectors are close to the query's vector, so "cheap flights" can match "low-cost airfare" without shared words. Embeddings come from a model, either computed by your app or by Elasticsearch through an inference endpoint.

Map a field as dense_vector with dims and similarity (cosine, dot_product, l2_norm, max_inner_product). Indexed vectors use an HNSW graph for approximate nearest-neighbour search, quantized by default to save memory (int8, or BBQ binary quantization for higher dimensions in recent versions).

Query with the knn option or query: k is how many neighbours to return, num_candidates how many to examine per shard; more candidates means better recall but slower queries. You can add a filter inside knn so filtering happens during the graph search rather than after.

The semantic_text field type automates the pipeline: it chunks text, generates embeddings through an inference endpoint at index time, and is queried with a simple semantic or match query.

Hybrid search combines lexical BM25 and vector results. Because their scores live on different scales, use a retriever with reciprocal rank fusion (rrf), which merges by rank position. Hybrid usually beats either method alone: lexical catches exact names and codes, vectors catch meaning.

Vectors need memory: keep the HNSW graph and quantized vectors in RAM for fast queries.

Example

PUT /docs
{ "mappings": { "properties": {
  "text":      { "type": "text" },
  "embedding": { "type": "dense_vector", "dims": 3, "similarity": "cosine" } } } }

PUT /docs/_doc/1?refresh=true
{ "text": "low-cost airfare deals", "embedding": [0.9, 0.1, 0.2] }

GET /docs/_search
{
  "retriever": { "rrf": { "retrievers": [
    { "standard": { "query": { "match": { "text": "cheap flights" } } } },
    { "knn": { "field": "embedding", "query_vector": [0.88, 0.15, 0.2],
               "k": 10, "num_candidates": 100 } }
  ] } }
}
# doc 1 is returned via the kNN retriever even though no words match
# (real embeddings have hundreds of dimensions)

Remember

  • dense_vector + HNSW gives approximate kNN search
  • k results, num_candidates per shard trades recall for speed
  • semantic_text automates chunking and embedding
  • Combine lexical and vector results with RRF

Watch out for

  • Comparing raw BM25 and vector scores directly; use rank fusion instead.

Check yourself

  1. Raising num_candidates in a kNN query mostly affects what?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html

Builds on

Unlocks

Nothing builds on this yet.