Guides · 4 min read · October 26, 2026
Hybrid search in Azure AI Search: keyword and vector in one query
People search in two ways. Sometimes they type an exact name: a tool, a service, an error code. Sometimes they describe a problem in their own words. Keyword search is good at the first and vector search is good at the second. Hybrid search runs both for the same query and merges the results. In Azure AI Search, it's one query.
The results below are real captures from a RAG system built on a corpus of 100 workplace documents, run on live Azure in October 2026.
Two kinds of search
Keyword search finds documents that contain your words. Azure AI Search ranks them with BM25, which rewards rare words that appear often in a document. It's precise on names, codes, and jargon. It's blind to meaning: "checkout broke" doesn't match a postmortem that says "checkout outage".
Vector search compares meaning. An embedding model turns each passage, and each query, into a vector, a list of numbers. Passages whose vectors sit close to the query's vector come first. It finds paraphrases well. It's weaker on exact identifiers, because a tool name like deployctl carries little meaning for the model.
In a RAG system, users send both kinds of query. So the retrieval step needs both.
How the merge works: Reciprocal Rank Fusion
You can't just add the two scores. BM25 scores and vector similarities sit on different scales, like one judge scoring out of 100 and another out of 1.
Reciprocal Rank Fusion (RRF) ignores the scores and uses only positions. Each list gives every result 1 / (60 + rank) points, and the points are added up. A result near the top of either list rises. A result near the top of both rises the most. It needs no tuning, and it's hard for one list to dominate.
That's also why hybrid scores look odd. They come back around 0.03, because they're sums of small fractions. Read them as an ordering, never as a percentage, and never compare them with scores from another search mode.
The index
The index needs a text field for the keyword side and a vector field for the vector side. In Python, with the azure-search-documents library:
fields = [
SimpleField(name="id", type=SearchFieldDataType.String, key=True),
SearchableField(name="text", type=SearchFieldDataType.String), # keyword side
SearchableField(name="title", type=SearchFieldDataType.String),
SimpleField(name="file", type=SearchFieldDataType.String, filterable=True),
SearchField( # vector side
name="vector",
type=SearchFieldDataType.Collection(SearchFieldDataType.Single),
searchable=True,
vector_search_dimensions=1536, # must match the embedding model
vector_search_profile_name="vprofile",
),
]
vector_search = VectorSearch(
algorithms=[HnswAlgorithmConfiguration(name="hnsw")],
profiles=[VectorSearchProfile(name="vprofile", algorithm_configuration_name="hnsw")],
)
1536 matches text-embedding-3-small. Use a different embedding model, and this number changes with it. HNSW is the approximate nearest-neighbor index that keeps vector search fast at scale.
The query
Passing both search_text and vector_queries is the whole hybrid feature. The service runs both rankings and merges them with RRF before it answers:
from azure.search.documents import SearchClient
from azure.search.documents.models import VectorizedQuery
client = SearchClient(endpoint, index_name, credential)
results = client.search(
search_text=query, # keyword side
vector_queries=[
VectorizedQuery(vector=embed(query), k_nearest_neighbors=50, fields="vector")
], # vector side
select=["text", "title", "file"],
top=5,
)
embed(query) is your call to the same embedding model you used for the passages. k_nearest_neighbors=50 gives the vector side a deep list to merge, even though you only keep the top 5.
Real results
An exact name, where keyword search is strong:
query: deployctl rollback checkout-api
0.0333 runbook-checkout-api.md — Runbook: checkout-api
0.0318 runbook-catalog-service.md — Runbook: catalog-service
0.0317 runbook-auth-service.md — Runbook: auth-service
The tool name and service name put the right runbook on top.
A paraphrase, where vector search is strong:
query: checkout broke
0.0333 runbook-checkout-api.md — Runbook: checkout-api
0.0320 postmortem-checkout-latency.md — Postmortem: checkout latency (2026-07-14)
0.0268 service-catalog.csv — Table service-catalog.csv — columns: service, team, tie
On the same corpus, vector search alone put three runbooks on top and missed the checkout postmortem entirely. Hybrid puts it second. The merge isn't just a tie-breaker. It finds results that either side alone loses.
A scanned paper memo, read earlier with OCR:
query: when do old badges stop working
0.0333 scanned-facilities-memo.pdf — FACILITIES MEMO X badge reader replacement
0.0308 policy-ai-usage.html — AI Tool Usage
0.0303 postmortem-stale-catalog.md — Postmortem: stale catalog (2026-01-25)
When hybrid isn't best
Hybrid is the safe default, not a guarantee. On a test set made almost entirely of paraphrased questions, pure vector search beat hybrid. The answer isn't a rule of thumb. It's a golden set: real questions, each with the document that should answer it, scored after every change.
When the right document shows up in the top results but too low, add a re-ranker. Azure AI Search's semantic ranker reads the query with each top result and re-orders them. It's often the biggest single quality gain in a RAG system. It adds a few hundred milliseconds per query, and it needs a paid tier.
What it costs
Azure AI Search bills by the hour, by tier. The free tier is small: one per subscription, about 50 MB, and three indexes. The Basic tier was about $0.11 an hour in October 2026, whether you query it or not. For learning, create it when you need it and delete it at the end of the session. An index is disposable, as long as your chunks and vectors are stored somewhere else, such as your database.