Hybrid search RAG .NET is a first-stage retrieval pattern for questions that need both exact words and related meaning. A developer might ask for a policy number, an error code, or a product name that must match precisely. They might instead paraphrase a concept found in a document. In Azure AI Search, one hybrid request can run lexical and vector retrieval in parallel, then merge their independently ranked candidates with reciprocal rank fusion (RRF). The important idea is not that one signal replaces the other. Each signal produces candidates for a different reason.
That separation prevents a common mental-model error. BM25 does not become a vector score. A vector similarity score does not become a keyword score. Azure AI Search ranks each result list with its own retrieval method, then applies RRF to produce the response ordering. The fused list is the output of candidate retrieval, ready for a RAG application to inspect and ground an answer in retrieved text.
Hybrid search RAG .NET starts with two candidate lists
Hybrid retrieval needs an index with both searchable text fields and a vector field representing the same content at a useful retrieval granularity. The text field gives lexical search an inverted index to query. The vector field gives dense search a numeric representation to query. Azure AI Search documents hybrid search as a single request containing full-text and vector queries, executed in parallel and merged into one result set with reciprocal rank fusion.
The shared index is important, but the candidate lists should remain conceptually separate:
- Lexical retrieval asks which documents contain terms that the query makes important.
- Dense retrieval asks which documents are nearest to the query embedding in the configured vector space.
- RRF asks which documents have strong positions across the ranked lists.
This is a retrieval composition, not an invitation to collapse every relevance decision into one score. In particular, no useful conclusion follows from comparing a raw lexical score directly to a raw vector score. They arise from different ranking functions and have different ranges. Azure AI Search fuses ranks rather than treating those raw values as interchangeable.
For a wider view of the ingestion, retrieval, and evidence boundaries around this step, see my RAG with Semantic Kernel guide. That framework-specific walkthrough is complementary: this article stays focused on how a direct Azure AI Search request composes its first-stage candidates.
Lexical candidate retrieval preserves the words that matter
Lexical retrieval starts with text, not embeddings. In the Azure AI Search model, a full-text query searches fields marked searchable and ranks matching documents with BM25. This is useful when a query carries tokens whose exact form carries meaning: a ticket number, a named API, an exception type, a version, a legal term, or a rare internal phrase.
Consider a question such as, “What changed in ERR-AUTH-4017?” A dense retriever can associate nearby explanations with authentication, but it cannot promise the exact token will be represented as strongly as a lexical match. Conversely, a text query for “connection pool exhaustion” can return material that repeats those words while missing a chunk that describes “running out of database connections.” These are different candidate-retrieval failure modes, not proof that either method is generally better.
In hybrid search RAG .NET, the lexical side gives the request a way to preserve explicit query evidence. It also uses the normal text-index features that apply to the search request. The Azure AI Search vector overview explains that text and vector fields can coexist in one index, and that filters operate on filterable text or numeric fields rather than on the vector itself.
That distinction matters for RAG metadata. A request can constrain eligible chunks with filterable text or numeric fields, such as document status, content type, or a server-derived access scope, while its text query still generates lexical candidates from searchable fields. The filter is an eligibility boundary. BM25 is the lexical ranking signal. Keeping those responsibilities distinct makes the request easier to reason about.
If you need a framework-specific introduction to an indexed semantic-search application before adding the lexical path, my Semantic Kernel semantic search article provides that earlier context. It is not a substitute for understanding the two independently ranked lists in a hybrid request.
Dense candidate retrieval covers paraphrase and conceptual similarity
Dense retrieval begins by embedding the query with a model compatible with the vectors stored in the target field. It then searches for the nearest vectors. The vector field's dimensions must match the embedding model output used for the content placed in that field.
The dense path is useful when a question and a relevant chunk express the same idea with different vocabulary. A user can ask, “How do I reclaim connections after a timeout?” while the source describes “disposing a failed database session.” There might be no exact phrase overlap. If the query and chunk embeddings place that meaning near each other, vector retrieval can add the chunk to its candidate list.
Dense retrieval does not read a document in the same way a person does. It compares the query vector with indexed vectors under the vector-search configuration. Its output is still a ranked candidate list, not an answer and not evidence that every returned chunk supports a future generated claim. A RAG application should return or retain the chunk text, source identity, and metadata it needs for citation and review.
This is also why query and document embeddings must be treated as one compatibility contract. The content that was indexed and the query being searched need the expected vector shape and embedding-space relationship. For a closer look at embedding generation as an application concern, see the existing text embeddings with Semantic Kernel article. Its API surface is framework-specific; the compatibility principle applies before the Azure AI Search request.
Reciprocal rank fusion turns ranked candidates into one response
RRF is the merge step in Azure AI Search hybrid search. It receives multiple already ranked result lists, assigns contributions by position, sums them, and sorts the combined result. A chunk that appears near the top of both lists receives contributions from both. A chunk that appears only in one list can still be returned if its position is strong enough.
The standard form is:
RRF(document) = Σ 1 / (rank + k)
Here, rank is the document's position in one candidate list and k is the RRF constant. Azure AI Search documents that its RRF constant is separate from the vector query's nearest-neighbor count. The two values solve different problems: the vector count controls how many dense candidates enter the merge, while the RRF constant shapes how rank positions contribute to the fused order.
That formula also explains why RRF is a practical bridge between lexical and dense retrieval. It uses positions, not a direct arithmetic combination of BM25 and vector-similarity scores. A document can benefit from either retrieval route without pretending that the underlying scores share a common scale.
In hybrid search RAG .NET, the fused score orders the service response. It is not a statement that the first chunk is sufficient to answer a question, that all candidates are equally useful context, or that the retrieved text may be treated as trusted instructions. Fusion finishes the combination of first-stage candidates. Grounding, source attribution, and any later application policy remain separate responsibilities.
Hybrid search RAG .NET sends lexical and dense candidates in one stable request
The following example uses the active Azure AI Search .NET SDK line, Azure.Search.Documents 12. It combines a text query with a VectorizedQuery in one SearchClient.SearchAsync<T> call. No second request is needed to manually join the two result sets, and there is no second-stage ranking configuration in this example.
using Azure;
using Azure.Search.Documents;
using Azure.Search.Documents.Models;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
public sealed class RagChunk
{
public string Id { get; set; } = string.Empty;
public string Content { get; set; } = string.Empty;
public string SourceUri { get; set; } = string.Empty;
public string TenantId { get; set; } = string.Empty;
}
public static class HybridSearch
{
public static async Task<IReadOnlyList<RagChunk>> FindCandidatesAsync(
SearchClient client,
string question,
ReadOnlyMemory<float> queryVector,
CancellationToken cancellationToken)
{
var vectorQuery = new VectorizedQuery(queryVector)
{
KNearestNeighborsCount = 20,
Fields = { "contentVector" }
};
var options = new SearchOptions
{
Filter = "tenantId eq 'contoso'"
};
options.Select.Add(nameof(RagChunk.Id));
options.Select.Add(nameof(RagChunk.Content));
options.Select.Add(nameof(RagChunk.SourceUri));
options.VectorSearch = new VectorSearchOptions
{
Queries = { vectorQuery }
};
Response<SearchResults<RagChunk>> response =
await client.SearchAsync<RagChunk>(
question,
options,
cancellationToken);
var chunks = new List<RagChunk>();
await foreach (SearchResult<RagChunk> result
in response.Value.GetResultsAsync())
{
chunks.Add(result.Document);
}
return chunks;
}
}
The question parameter is the lexical input. The queryVector parameter is the dense input. Their presence in the same call is what makes this a hybrid query. The example's filter is deliberately ordinary Azure AI Search syntax so the retrieval mechanics remain visible. In an application, construct access filters from authenticated server-side policy and ensure the selected fields include the source metadata required by your answer path.
The service performs the parallel candidate retrieval and RRF merge. Your code consumes one ordered response. That is different from issuing a lexical query, issuing a vector query, and trying to compare or combine their scores in application code.
Optional Azure semantic ranking is a separate post-RRF stage, not part of RRF itself; when enabled, Azure reports its result in @search.rerankerScore. This example deliberately omits that configuration.
For a framework-specific view of vector-store abstractions and connectors, read Semantic Kernel Vector Store in C#. The direct SDK request above is intentionally narrower: it illustrates Azure AI Search's documented, vendor-specific hybrid behavior without turning this page into a store-selection guide.
Use a small RRF model to understand the merge, not to replace it
Azure AI Search owns the RRF implementation for a hybrid request. You should not reimplement it merely to reconstruct the service response. A small standalone model can still clarify why ranks are the input to fusion and why a document appearing in both lists gains two contributions.
using System.Collections.Generic;
using System.Linq;
public static class ReciprocalRankFusion
{
public static IReadOnlyList<string> Fuse(
IEnumerable<IReadOnlyList<string>> rankedLists,
int rankConstant = 60)
{
return rankedLists
.SelectMany(list => list.Select(
(documentId, index) => new
{
DocumentId = documentId,
Contribution = 1d / (index + 1 + rankConstant)
}))
.GroupBy(item => item.DocumentId)
.OrderByDescending(group => group.Sum(item => item.Contribution))
.Select(group => group.Key)
.ToArray();
}
}
If lexical retrieval ranks A, B, C and dense retrieval ranks B, D, A, B and A receive contributions from both lists. Their relative result depends on their positions, while C and D receive only one contribution each. The method uses one-based ranks because a list index starts at zero but retrieval positions conventionally start at one.
The rankConstant is exposed here solely to make the mathematical form explicit. It is not a tuning instruction for the Azure service. Azure AI Search documents the service's RRF constant and applies its own merge behavior to the request. The example is a teaching aid for rank fusion, not a replacement for the managed operation.
Keep the RAG boundary after the fused list
The fused response is a candidate set. A RAG answer path still has work to do: preserve source information, choose the bounded context it will provide to the model, and attach citations to material actually used as evidence. A good retrieval result is not the same thing as a correct generated answer.
That boundary is especially useful for diagnosis. If an answer lacks the relevant source, inspect the lexical query, the query embedding, vector-field compatibility, metadata eligibility, and the fused candidate positions. If the relevant source is present but the answer does not reflect it, the failure belongs after candidate retrieval. Separating these questions avoids treating every bad answer as a generic model problem.
The Azure AI Search API-version guidance identifies Azure.Search.Documents 12 as the active .NET SDK and says the former 2023-07-01-preview REST API is no longer supported. Keep an implementation on the current stable surface documented for the service, and keep the conceptual distinction intact: lexical candidates, dense candidates, then rank fusion.
The durable lesson for hybrid search RAG .NET is modest but important. Exact terms and conceptual similarity can each contribute useful candidates. Azure AI Search runs the two paths together and uses RRF to create one ordered response. To see how selected chunks can become an answer path, use the existing document Q&A application with RAG and Semantic Kernel as framework-specific follow-on reading. Understanding the retrieval division makes it easier to investigate behavior without inventing a contest between BM25 and vectors.
Frequently asked questions
What is hybrid search for RAG in .NET?
It is a retrieval request that sends a text query and a vector query to an index containing searchable text and vector fields. In Azure AI Search, the service runs the candidate searches in parallel and merges their ranked results with RRF.
Does BM25 search the vector field?
No. BM25 is the lexical ranking path for searchable text fields, while the vector query targets a vector field. The two paths can address content from the same indexed document, but they use different fields and ranking methods.
Does vector retrieval require exact keyword matches?
No. Vector retrieval ranks vectors by their configured similarity relationship, so it can retrieve conceptually related text when wording differs. It does not remove the value of lexical retrieval for identifiers and other exact terms.
Does reciprocal rank fusion add BM25 and vector scores together?
No. RRF combines contributions based on a document's rank positions in the separately ranked lists. This avoids assuming that raw scores from different ranking algorithms use the same scale.
Can filters be used with a hybrid Azure AI Search request?
Yes. Azure AI Search documents that a vector query can include a filter expression over filterable text or numeric fields. Treat the filter as an eligibility rule and retain the distinction between filtering, lexical ranking, dense ranking, and fusion.
Is the fused result list already a RAG answer?
No. It is an ordered candidate list. A RAG application still needs to select evidence, construct bounded context, generate a response, and preserve citations to the retrieved sources it relies on.
A stable mental model for hybrid retrieval
Hybrid retrieval is easier to debug when each operation has one job. Lexical retrieval preserves exact query language. Dense retrieval contributes conceptual matches. RRF merges their ranked candidate lists into the Azure AI Search response. Keep that sequence visible in code and in telemetry, then treat the resulting chunks as evidence candidates rather than a completed answer.

