Azure AI Search combines a Lucene-style inverted index and an HNSW vector index behind one API. Below the feature list and SDK examples, this page adds the ingestion and query architecture, guidance on when to use it instead of other vector stores, and the configuration mistakes that most often hurt RAG quality.
Azure AI Search (formerly Azure Cognitive Search) is Microsoft's managed search service and the default retrieval layer behind Azure OpenAI's "On Your Data" and AI Foundry RAG workflows. It supports full-text (BM25), vector, semantic reranking, and hybrid search in a single index, with built-in skillsets that call other Azure AI services (OCR, language, vision) during ingestion.
Edm.String, Collection(Edm.Single) for vectors, etc.) and attributes (searchable, filterable, sortable, facetable, retrievable, key).
from azure.search.documents.indexes import SearchIndexClient
from azure.search.documents.indexes.models import (
SearchIndex, SearchField, SearchFieldDataType, SimpleField, SearchableField,
VectorSearch, HnswAlgorithmConfiguration, VectorSearchProfile,
SemanticConfiguration, SemanticField, SemanticPrioritizedFields, SemanticSearch,
)
from azure.core.credentials import AzureKeyCredential
client = SearchIndexClient("https://myco-search.search.windows.net", AzureKeyCredential("<key>"))
index = SearchIndex(
name="hr-policies",
fields=[
SimpleField(name="id", type=SearchFieldDataType.String, key=True),
SearchableField(name="title", type=SearchFieldDataType.String),
SearchableField(name="content", type=SearchFieldDataType.String, analyzer_name="en.microsoft"),
SearchField(
name="content_vector",
type=SearchFieldDataType.Collection(SearchFieldDataType.Single),
searchable=True,
vector_search_dimensions=1536,
vector_search_profile_name="hnsw-default",
),
],
vector_search=VectorSearch(
algorithms=[HnswAlgorithmConfiguration(name="hnsw")],
profiles=[VectorSearchProfile(name="hnsw-default", algorithm_configuration_name="hnsw")],
),
semantic_search=SemanticSearch(configurations=[
SemanticConfiguration(
name="default",
prioritized_fields=SemanticPrioritizedFields(
title_field=SemanticField(field_name="title"),
content_fields=[SemanticField(field_name="content")],
),
)
]),
)
client.create_or_update_index(index)
from azure.search.documents import SearchClient
from azure.search.documents.models import VectorizedQuery
from openai import AzureOpenAI
aoai = AzureOpenAI(azure_endpoint="...", api_key="...", api_version="2024-10-21")
def embed(text: str) -> list[float]:
return aoai.embeddings.create(model="embedding-3-large-prod", input=[text]).data[0].embedding
search = SearchClient("https://myco-search.search.windows.net", "hr-policies", AzureKeyCredential("<key>"))
results = search.search(
search_text="parental leave for new parents", # BM25 side
vector_queries=[VectorizedQuery( # vector side
vector=embed("parental leave for new parents"),
k_nearest_neighbors=50,
fields="content_vector",
)],
query_type="semantic", # turns on semantic reranker
semantic_configuration_name="default",
top=5,
select=["id", "title", "content"],
)
for r in results:
print(f"{r['@search.reranker_score']:.3f} {r['title']}")
Point an indexer at a data source, attach a skillset that chunks + embeds, and Azure AI Search handles ingestion end-to-end.
{
"name": "hr-skillset",
"skills": [
{
"@odata.type": "#Microsoft.Skills.Text.SplitSkill",
"textSplitMode": "pages",
"maximumPageLength": 2000,
"pageOverlapLength": 300,
"inputs": [{"name": "text", "source": "/document/content"}],
"outputs": [{"name": "textItems", "targetName": "chunks"}]
},
{
"@odata.type": "#Microsoft.Skills.Text.AzureOpenAIEmbeddingSkill",
"resourceUri": "https://myco.openai.azure.com",
"deploymentId": "embedding-3-large-prod",
"modelName": "text-embedding-3-large",
"context": "/document/chunks/*",
"inputs": [{"name": "text", "source": "/document/chunks/*"}],
"outputs": [{"name": "embedding", "targetName": "chunk_vector"}]
}
]
}
user_groups = ["hr-admin", "all-employees"]
filter_expr = " or ".join(f"acl_groups/any(g: g eq '{g}')" for g in user_groups)
results = search.search(
search_text="termination policy",
filter=filter_expr,
top=10,
)
There are two ways to get data in. The push model has your code call the index API with documents you have already chunked and embedded; it gives complete control and works with any source. The pull model uses an indexer, which connects to a supported data source on a schedule, tracks changes, cracks documents (PDF, Office, HTML), runs a skillset, and writes the enriched output to the index through field mappings. Integrated vectorization adds a text-split skill and an embedding skill so chunking and embedding happen inside the service, and attaches a vectorizer to the index so queries can be embedded server-side too.
A hybrid request runs the keyword query against the inverted index with BM25 and the vector query against HNSW in parallel. Reciprocal rank fusion combines the two ranked lists using rank positions only, which avoids normalising incompatible BM25 and cosine scores. The optional semantic ranker then rescores the top 50 fused results with a cross-encoder and can return extractive captions and answers. Filters on filterable fields (tenant, ACL groups, dates) are applied in the same request; for vectors you choose pre-filtering or post-filtering.
| Option | Best when | Trade-off |
|---|---|---|
| Azure AI Search | Azure-centric stack, need hybrid + semantic ranking, ACL filters, managed indexers | Priced by provisioned search units; less control over ANN internals |
| pgvector / PostgreSQL | Data already relational, moderate scale | Build keyword and hybrid ranking yourself |
| Elasticsearch / OpenSearch | Existing Elastic estate, heavy log or keyword workloads | Self-manage or separate managed offering |
| Dedicated vector DB | Very large vector-only workloads | Weaker full-text search |