Packet To Sniff

How vector search works: similarity, top-k and hybrid search

Vector search finds text by meaning instead of exact words. Learn cosine similarity, top-k retrieval, approximate nearest neighbour indexes, BM25 and hybrid ranking.

By Packet To SniffPublished 3 min read

Helpful background: What are embeddings? Meaning as numbers, explained, What is retrieval-augmented generation (RAG)?.

On this page

In a RAG system, the retrieval step answers one question: which chunks should the model see? Vector search is the most common answer. This article walks through how it works, from a single similarity score to the hybrid ranking used by most serious systems.

Step 1: score every chunk

Once every chunk has an embedding, searching is conceptually simple:

  1. Embed the query with the same model.
  2. Compute the similarity between the query vector and each chunk vector.
  3. Sort and return the best k.
Python
import numpy as np
 
def top_k(query_vec, chunk_vecs, k=5):
    # vectors are assumed normalised, so the dot product is cosine similarity
    scores = chunk_vecs @ query_vec
    best = np.argsort(-scores)[:k]
    return [(int(i), float(scores[i])) for i in best]

This is exact nearest-neighbour search. With a few thousand chunks it takes milliseconds.

Step 2: make it fast at scale

With millions of vectors, comparing against every one is too slow for interactive use. Approximate nearest neighbour (ANN) indexes trade a little accuracy for a lot of speed:

  • HNSW builds a layered graph where each vector links to nearby vectors; search hops through the graph towards the query.
  • IVF clusters vectors and only searches the clusters closest to the query.
  • Quantisation stores compressed vectors to save memory, at some cost in precision.

Vector databases and libraries such as FAISS implement these. The important thing to know: ANN results can occasionally miss a true nearest neighbour, and index settings trade speed against that recall.

Step 3: choose k and a threshold

  • k too small: the right chunk is ranked 6th and never reaches the model.
  • k too large: the prompt fills with loosely related text, which costs tokens and can confuse the answer.
  • No threshold: the system always returns something, even for questions your documents do not cover.

A minimum score threshold lets the system notice "nothing relevant was found" and say so honestly instead of answering from thin evidence. Similarity scores are not calibrated probabilities, so pick the threshold by looking at scores on real questions.

Keyword search still matters: BM25

BM25 is a classic ranking function used by search engines for decades. It scores a document higher when:

  • it contains the query's words,
  • those words are rare across the collection (inverse document frequency), and
  • the document is not padded out by length (length normalisation).

BM25 excels exactly where embeddings are weak: exact identifiers such as CVE-2024-3094, ERR_CONNECTION_RESET, port numbers, function names and acronyms.

Hybrid search and reciprocal rank fusion

Hybrid search runs both searches and merges the results. Raw BM25 scores and cosine scores are on different scales, so instead of adding scores, a popular method merges ranks. Reciprocal rank fusion (RRF), described by Cormack and colleagues in 2009, gives each item:

Output
RRF(d) = sum over each ranked list of  1 / (k + rank_of_d_in_that_list)

with k commonly set to 60. An item ranked first in either list gets a strong score; an item ranked well in both gets the strongest.

Python
def rrf(ranked_lists, k=60):
    scores = {}
    for ranking in ranked_lists:
        for rank, doc_id in enumerate(ranking, start=1):
            scores[doc_id] = scores.get(doc_id, 0.0) + 1.0 / (k + rank)
    return sorted(scores, key=scores.get, reverse=True)
 
print(rrf([["a", "b", "c"], ["c", "a", "d"]]))  # ['a', 'c', 'b', 'd']

Optional step 4: rerank

A reranker is a model that looks at the query and one candidate chunk together and outputs a relevance score. It is slower than vector comparison but more accurate, so a common design is: retrieve the top 20 to 50 cheaply, rerank them, and keep the best 3 to 5 for the prompt.

Summary

Technique Strength Weakness
Exact vector search Simple, accurate Slow for millions of vectors
ANN index (HNSW, IVF) Fast at scale Can miss some true neighbours
BM25 Exact terms, codes, names Misses paraphrases
Hybrid with RRF Best of both Two indexes to maintain
Reranker Highest precision Extra latency and cost

Start simple, measure recall on real questions, and add complexity only when the numbers show you need it. The next article covers how to evaluate a RAG system.

Frequently asked questions

What is top-k in retrieval?

Top-k means returning the k highest-scoring results for a query, for example the five most similar chunks. The value of k trades recall against noise: a larger k is more likely to include the right chunk but also adds irrelevant text to the prompt.

What is hybrid search?

Hybrid search runs both a keyword search, such as BM25, and a vector search, then merges the two ranked lists into one. A common merging method is reciprocal rank fusion, which rewards items that rank highly in either list.

Do I need a vector database?

For thousands or tens of thousands of chunks, an exact search over vectors in memory is usually fast enough. Vector databases add approximate indexes, filtering, persistence and concurrent updates, which matter at larger scale.

Continue learning

Tags

  • How to chunk documents for RAG

    Chunking decides what your RAG system can retrieve. Compare fixed-size, recursive, heading-aware and semantic chunking, and learn how to pick size and overlap.

    Retrieval-augmented generationIntermediate3 min