Ways to Search

3 June 2026 · Search

Overview

Text search comes in a few families. Keyword matching ranks documents by the words they share with the query. Vector search ranks by meaning, using embeddings. Hybrid search runs both and merges the results. Agentic search puts a language model in front of a search tool and lets it plan several queries to answer one request.

Each family makes a different trade between speed, precision, and how well it handles vague or natural-language queries. This post covers how each one works, what it is good at, and where it breaks.

The running example is a product catalogue: a food and drinks store with around 305 items, each carrying a name, description, flavour notes, food pairings and occasion tags. The methods apply to any text corpus.

The four families
Keyword (BM25)
Matches the exact words you typed. Fast, predictable, no model involved.
Vector
Embeds text as numbers and matches on meaning, not words.
Hybrid + rerank
Runs keyword and vector together, fuses them, then reranks the top results.
Agentic
A language model plans and runs several searches through one tool.

BM25 and the inverted index

Keyword search looks for the query words inside the document text. The ranking function behind it is BM25, the most widely used of the Okapi "Best Matching" functions developed through the 1970s and 80s. It is the default in Lucene-based engines like Elasticsearch and OpenSearch, and in Azure AI Search.

The data structure underneath is the inverted index. Instead of scanning every document on every query, the engine keeps a list per term: "pinot" points to documents 7, 12 and 88, "noir" points to 7 and 88. Searching "pinot noir" becomes a lookup and an intersection of two short lists. Text search has worked this way since the 1970s.

Before indexing, text passes through an analyser that lowercases it, drops stop words like "the" and "and", and reduces words to a root form (stemming), so "baking", "baked" and "bakes" collapse to "bake". The query gets the same treatment, which is why "Baked Brie" can match a label that reads "baking brie".

BM25 scoring rests on three ideas:

  • Term frequency with saturation. A term that appears five times scores higher than one that appears once, but with diminishing returns. The tenth occurrence adds little.
  • Inverse document frequency. Rare terms count for more. A match on "bottle" means little when every item is a bottle; a match on a brand name means a lot.
  • Length normalisation. Shorter fields win ties, since a one-line label about pinot is more focused than a long paragraph that mentions it once.

It returns in well under a millisecond, and it is exact. Search "Whittakers Dark" and you get Whittakers Dark. The limit is vocabulary. Search "cosy red for a cold night" when the wine is described as "full-bodied shiraz with rich tannins", and the two share no terms, so BM25 has nothing to match. It only knows the words you typed.

Embeddings and approximate nearest neighbours

Vector search drops the dependence on shared words. It converts the query and every document into a fixed-length list of numbers, an embedding, positioned so that similar meanings sit close together and unrelated text sits far apart.

A model like `text-embedding-3-small` maps any text to 1536 dimensions. "Bubbly to celebrate a promotion" and "Prosecco for a celebration" land close together despite sharing almost no words. "Prosecco" and "paint roller" land far apart. The model learns this from training on large numbers of related and unrelated text pairs, pulling related ones together and pushing unrelated ones apart, until the geometry of the output reflects meaning.

Matching is then a distance calculation, usually cosine similarity: vectors pointing the same way score near 1, perpendicular ones near 0. Comparing the query against every document (exact nearest neighbour) is accurate but scales linearly, so production engines use approximate nearest neighbour search. The common index is HNSW (Hierarchical Navigable Small World), a layered graph that takes large hops near the top and small steps near the bottom, finding close matches in a few milliseconds. Elasticsearch, OpenSearch, pgvector and the dedicated vector databases all use HNSW or a close variant.

The payoff is semantic recall. Ask for "smoky cocktail to sip after work" and you get peated whiskies and mezcal even though "smoky" never appears, because the model places "peated" and "campfire" nearby. Subword tokenisation also makes it tolerant of typos. The costs: it can drift on exact lookups, where one product code sits near every other product code, and the ranking is hard to explain, since the reason lives in 1536 numbers.

A middle option is learned sparse retrieval, such as Elastic's ELSER or SPLADE. A model expands the text into weighted terms, including related words that never appeared, then stores them in a normal inverted index. You get some semantic recall while keeping keyword-style speed and explainability.

Rank fusion and a reranking model

Keyword and vector fail in opposite directions. Keyword misses meaning; vector misses exact brands and codes. Hybrid search runs both and merges the results in one request.

The merge usually uses Reciprocal Rank Fusion (RRF). Each document gets a score of `1 / (k + rank)` from each list, summed across lists, where `k` is a smoothing constant (Azure AI Search and Elasticsearch default to 60). Because RRF reads ranks, not raw scores, it fuses two engines that score on different scales without any normalisation.

How a hybrid query flows
Query
Keyword + Vector run in parallel
Fuse by rank (RRF)
Rerank top 50
Results

After fusion, a reranker can reorder the top candidates. Azure AI Search uses a semantic ranker; the same role is filled by cross-encoder models elsewhere. The distinction matters: a vector search uses a bi-encoder that embeds the query and document separately, while a cross-encoder reads the query and a candidate together, which is more accurate but too slow to run over a whole index. So it only scores the top 50 from fusion, and returns a fresh relevance score plus a short caption explaining each match.

In practice hybrid plus reranking is the strongest general default. Vector covers vocabulary the index never saw; keyword covers exact brands; the reranker cleans up the order.

A model that plans multiple searches

A single hybrid query handles one intent well. It struggles with a request that contains several:

"Dinner for six. Grilled lamb main, two starters, a good red, one alcohol-free option, a chocolate dessert, around 250 dollars."

That is five searches in one sentence. A single query has to pick one intent and lose the rest. Agentic search gives a language model one tool, a `search_catalog` function, and lets it decompose the request, run several searches, read the results, and assemble an answer.

One brief, many sub-queries
"a good red"
search: full-bodied red wine for grilled lamb
"two starters"
search: cheese and charcuterie starters
"alcohol-free option"
search: non-alcoholic drink for dinner
"chocolate dessert"
search: chocolate dessert or dark chocolate
"around 250 dollars"
tracked as a running total, not a search

The model runs a loop: read the brief, plan a sub-query, call the tool, read what came back, then either search again or move on. It repeats until every part of the request is covered, then writes up a grouped recommendation with a running price.

The agent loop
Read brief
Plan sub-query
Call search tool
Read results
More needed? back to plan
Assemble answer

One property does most of the work for trustworthiness: grounding. The model never sees the raw index. It only sees what the search tool returns, so it cannot recommend a product that does not exist. Every item in the answer had to come back from a real search.

Grounding keeps the model honest
Without grounding
The model writes product names from memory and can invent items that are not in stock.
With grounding
Every recommended item came from a tool result, so the answer can only contain real products.

The cost is latency and price. Several searches plus model reasoning take seconds, not milliseconds, and each run spends tokens. Agentic search fits a "help me plan" flow, not an autocomplete box.

Which one to use

The right method per query type, and per engine

There is no single winner. Each method owns a different job:

MethodBest atWatch out forTypical use
Keyword (BM25)brands, exact terms, raw speedno sense of meaningautocomplete, "did you mean", sub-100ms lookups
Vectormeaning, typos, loose phrasingexact codes, no explanationfallback when keyword finds nothing
Learned sparsesemantic recall at index speedmodel and storage overheadkeyword systems that need more recall
Hybrid + rerankoverall relevancea little slowerthe main results page
Agenticmulti-part requestsseconds, not millisecondsa "help me plan" feature

The same methods are available across most modern engines, so the choice is usually about your existing stack rather than the algorithm:

EngineKeywordVectorHybrid
Elasticsearch / OpenSearchBM25 (Lucene)kNN via HNSWRRF, plus ELSER learned sparse
Azure AI SearchBM25HNSWRRF and a semantic reranker
Postgres + pgvectorfull-text (tsvector)HNSW or IVFFlatcombine in SQL
Vector databases (Pinecone, Weaviate, Qdrant)limitedprimary featurevaries by product

A reasonable default for most catalogues: hybrid with a reranker on the main results page, BM25 for autocomplete and exact lookups, and an agentic flow behind an explicit "plan this for me" action. Vector or learned sparse fills the recall gaps underneath.

The wider shift is what a search box becomes once it models intent. Each agent sub-query is a structured statement of what a user actually wanted, which turns search logs into a usable signal for whoever decides what to stock.

A reference implementation of all four methods is on GitHub.

Written with help of

Comments (0)

280

loading...