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.
Keyword search
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.
Vector search
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.
Hybrid search
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.
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.
Agentic search
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.
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.
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.
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:
| Method | Best at | Watch out for | Typical use |
|---|---|---|---|
| Keyword (BM25) | brands, exact terms, raw speed | no sense of meaning | autocomplete, "did you mean", sub-100ms lookups |
| Vector | meaning, typos, loose phrasing | exact codes, no explanation | fallback when keyword finds nothing |
| Learned sparse | semantic recall at index speed | model and storage overhead | keyword systems that need more recall |
| Hybrid + rerank | overall relevance | a little slower | the main results page |
| Agentic | multi-part requests | seconds, not milliseconds | a "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:
| Engine | Keyword | Vector | Hybrid |
|---|---|---|---|
| Elasticsearch / OpenSearch | BM25 (Lucene) | kNN via HNSW | RRF, plus ELSER learned sparse |
| Azure AI Search | BM25 | HNSW | RRF and a semantic reranker |
| Postgres + pgvector | full-text (tsvector) | HNSW or IVFFlat | combine in SQL |
| Vector databases (Pinecone, Weaviate, Qdrant) | limited | primary feature | varies 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.