Edit

Azure AI Search

Azure AI Search grounds Agent Framework agents with content from a search index. In Python, AzureAISearchContextProvider supports semantic and agentic retrieval. In .NET, connect an Azure AI Search client to TextSearchProvider.

This integration uses the RAG pattern: it retrieves relevant external content before model invocation without treating that content as conversational memory.

Connect Azure AI Search to TextSearchProvider

Create a SearchClient, map search hits to TextSearchProvider.TextSearchResult, and attach the provider through AIContextProviders.

// Load .env file if present (for local development)
Env.TraversePath().Load();

string projectEndpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
    ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-4o";

string searchEndpoint = Environment.GetEnvironmentVariable("AZURE_SEARCH_ENDPOINT")
    ?? throw new InvalidOperationException("AZURE_SEARCH_ENDPOINT is not set.");
string searchIndexName = Environment.GetEnvironmentVariable("AZURE_SEARCH_INDEX_NAME")
    ?? throw new InvalidOperationException("AZURE_SEARCH_INDEX_NAME is not set.");

// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
// Use a chained credential. Try a temporary dev token first (for local Docker debugging),
// then fall back to DefaultAzureCredential (for local dev via dotnet run / managed identity in
// production). The dev credential is scope aware so a single instance serves both Foundry and
// Azure AI Search clients (each Azure SDK client requests a token for its own audience).
TokenCredential credential = new ChainedTokenCredential(
    new DevTemporaryTokenCredential(),
    new DefaultAzureCredential());

// Connect to the pre-provisioned search index. The caller is expected to have created the
// index and populated it with documents matching the schema (id / content / sourceName /
// sourceLink) before running this sample. See README.md for an example provisioning script.
var searchClient = new SearchClient(new Uri(searchEndpoint), searchIndexName, credential);

TextSearchProviderOptions textSearchOptions = new()
{
    SearchTime = TextSearchProviderOptions.TextSearchBehavior.BeforeAIInvoke,
    RecentMessageMemoryLimit = 6,
};

AIAgent agent = new AIProjectClient(new Uri(projectEndpoint), credential)
    .AsAIAgent(new ChatClientAgentOptions
    {
        Name = Environment.GetEnvironmentVariable("AGENT_NAME") ?? "hosted-azure-search-rag",
        ChatOptions = new ChatOptions
        {
            ModelId = deploymentName,
            Instructions = "You are a helpful support specialist for Contoso Outdoors. " +
                           "Answer questions using the provided context and cite the source document when available.",
        },
        AIContextProviders = [new TextSearchProvider(CreateSearchAdapter(searchClient), textSearchOptions)]
    });
// ── Search adapter ───────────────────────────────────────────────────────────
// Wraps a SearchClient as the delegate TextSearchProvider expects. Keyword/full-text only;
// no embeddings. Returns the top results and projects them into TextSearchResult entries
// the provider will inject into the model context.

static Func<string, CancellationToken, Task<IEnumerable<TextSearchProvider.TextSearchResult>>>
    CreateSearchAdapter(SearchClient client, int top = 3) =>
    async (query, cancellationToken) =>
    {
        var options = new SearchOptions { Size = top };
        Response<SearchResults<SearchDocument>> response =
            await client.SearchAsync<SearchDocument>(query, options, cancellationToken).ConfigureAwait(false);

        var results = new List<TextSearchProvider.TextSearchResult>();
        await foreach (SearchResult<SearchDocument> hit in response.Value.GetResultsAsync().WithCancellation(cancellationToken).ConfigureAwait(false))
        {
            results.Add(new TextSearchProvider.TextSearchResult
            {
                SourceName = hit.Document.TryGetValue("sourceName", out var name) ? name?.ToString() ?? string.Empty : string.Empty,
                SourceLink = hit.Document.TryGetValue("sourceLink", out var link) ? link?.ToString() ?? string.Empty : string.Empty,
                Text = hit.Document.TryGetValue("content", out var content) ? content?.ToString() ?? string.Empty : string.Empty,
                RawRepresentation = hit
            });
        }

        return results;
    };

The sample hosts the resulting agent in Foundry, but the search adapter works with a regular ChatClientAgent.

Install the packages

pip install agent-framework-azure-ai-search agent-framework-foundry --pre

Use semantic retrieval

Semantic mode performs search against an existing index and can combine keyword and vector retrieval.

credential = AzureCliCredential()

# Get configuration from environment
search_endpoint = os.environ["AZURE_SEARCH_ENDPOINT"]
search_key = os.environ.get("AZURE_SEARCH_API_KEY")
index_name = os.environ["AZURE_SEARCH_INDEX_NAME"]
project_endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
model_deployment = os.environ.get("FOUNDRY_MODEL", "gpt-4o")
openai_endpoint = os.environ.get("AZURE_OPENAI_ENDPOINT")
embedding_deployment = os.environ.get("AZURE_OPENAI_EMBEDDING_MODEL")

embedding_client = None
if openai_endpoint and embedding_deployment:
    embedding_client = OpenAIEmbeddingClient(
        azure_endpoint=openai_endpoint,
        model=embedding_deployment,
        credential=credential,
    )

# Create Azure AI Search context provider with semantic mode (recommended, fast)
print("Using SEMANTIC mode (hybrid search + semantic ranking, fast)\n")
search_provider = AzureAISearchContextProvider(
    source_id="search_provider",
    endpoint=search_endpoint,
    index_name=index_name,
    api_key=search_key,  # Use api_key for API key auth, or credential for managed identity
    credential=credential if not search_key else None,
    mode="semantic",  # Default mode
    top_k=3,  # Retrieve top 3 most relevant documents
    embedding_function=embedding_client,  # Provide embedding function for hybrid search
    vector_field_name="DescriptionVector"
    if embedding_client
    else None,  # Set vector field for hybrid search if using embeddings
)

# Create agent with search context provider
async with (
    search_provider,
    Agent(
        client=FoundryChatClient(
            project_endpoint=project_endpoint,
            model=model_deployment,
            credential=credential,
        ),
        name="SearchAgent",
        instructions=(
            "You are a helpful assistant. Use the provided context from the "
            "knowledge base to answer questions accurately."
        ),
        context_providers=[search_provider],
    ) as agent,
):
    print("=== Azure AI Agent with Search Context (Semantic Mode) ===\n")

    for user_input in USER_INPUTS:
        print(f"User: {user_input}")
        print("Agent: ", end="", flush=True)

        # Stream response
        async for chunk in agent.run(user_input, stream=True):
            if chunk.text:
                print(chunk.text, end="", flush=True)

        print("\n")

Use agentic retrieval

Agentic mode uses an Azure AI Search Knowledge Base for query planning and multi-hop retrieval.

# Agentic mode requires exactly ONE of: knowledge_base_name OR index_name
# Option 1: Use existing Knowledge Base (recommended)
knowledge_base_name = os.environ.get("AZURE_SEARCH_KNOWLEDGE_BASE_NAME")
# Option 2: Auto-create KB from index (requires azure_openai_resource_url)
index_name = os.environ.get("AZURE_SEARCH_INDEX_NAME")
azure_openai_resource_url = os.environ.get("AZURE_OPENAI_RESOURCE_URL")

# Create Azure AI Search context provider with agentic mode (recommended for accuracy)
print("Using AGENTIC mode (Knowledge Bases with query planning, recommended)\n")
print("This mode is slightly slower but provides more accurate results.\n")

# Configure based on whether using existing KB or auto-creating from index
if knowledge_base_name:
    # Use existing Knowledge Base - simplest approach
    search_provider = AzureAISearchContextProvider(
        source_id="search_provider",
        endpoint=search_endpoint,
        api_key=search_key,
        credential=AzureCliCredential() if not search_key else None,
        mode="agentic",
        knowledge_base_name=knowledge_base_name,
        # Optional: Configure retrieval behavior. "answer_synthesis" output mode and
        # "medium"/"low" reasoning effort require the preview build of azure-search-documents
        # (`pip install --pre azure-search-documents`); the provider auto-detects the build.
        knowledge_base_output_mode="extractive_data",  # or "answer_synthesis" (preview build only)
        retrieval_reasoning_effort="minimal",  # or "medium", "low" (preview build only)
    )
else:
    # Auto-create Knowledge Base from index
    if not index_name:
        raise ValueError("Set AZURE_SEARCH_KNOWLEDGE_BASE_NAME or AZURE_SEARCH_INDEX_NAME")
    if not azure_openai_resource_url:
        raise ValueError("AZURE_OPENAI_RESOURCE_URL required when using index_name")
    search_provider = AzureAISearchContextProvider(
        source_id="search_provider",
        endpoint=search_endpoint,
        index_name=index_name,
        api_key=search_key,
        credential=AzureCliCredential() if not search_key else None,
        mode="agentic",
        azure_openai_resource_url=azure_openai_resource_url,
        model=model_deployment,
        # Optional: Configure retrieval behavior. "answer_synthesis" output mode and
        # "medium"/"low" reasoning effort require the preview build of azure-search-documents
        # (`pip install --pre azure-search-documents`); the provider auto-detects the build.
        knowledge_base_output_mode="extractive_data",  # or "answer_synthesis" (preview build only)
        retrieval_reasoning_effort="minimal",  # or "medium", "low" (preview build only)
        top_k=3,
    )

# Create agent with search context provider
async with (
    search_provider,
    Agent(
        client=FoundryChatClient(
            project_endpoint=project_endpoint,
            model=model_deployment,
            credential=AzureCliCredential(),
        ),
        name="SearchAgent",
        instructions=(
            "You are a helpful assistant with advanced reasoning capabilities. "
            "Use the provided context from the knowledge base to answer complex "
            "questions that may require synthesizing information from multiple sources."
        ),
        context_providers=[search_provider],
    ) as agent,
):
    print("=== Azure AI Agent with Search Context (Agentic Mode) ===\n")

    for user_input in USER_INPUTS:
        print(f"User: {user_input}")
        print("Agent: ", end="", flush=True)

        # Stream response
        async for chunk in agent.run(user_input, stream=True):
            if chunk.text:
                print(chunk.text, end="", flush=True)
            for content in chunk.contents:
                if content.annotations:
                    print(f"\n[Sources: {content.annotations}]", end="", flush=True)

        print("\n")

Some agentic output and reasoning options require the preview azure-search-documents package.

Note

Azure AI Search doesn't currently have a dedicated Agent Framework Go integration. Implement retrieval as a custom tool or context provider, or see the Agent Framework Go repository for the latest status.

Production considerations

  • Prefer Microsoft Entra authentication or managed identity over search keys.
  • Apply tenant-aware filters and index isolation.
  • Treat retrieved content as untrusted input and mitigate indirect prompt injection.
  • Preserve source metadata when the agent should cite documents.

Next steps