From 0b843d2b3e52918f6ecc67e56ef4ceb0f6457959 Mon Sep 17 00:00:00 2001 From: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com> Date: Mon, 3 Nov 2025 18:25:16 +0000 Subject: [PATCH] .NET: Add simple rag sample for catalog (#1834) * add simple rag sample for catalog * Update dotnet/samples/Catalog/AgentWithTextSearchRag/AgentWithTextSearchRag.csproj Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- dotnet/agent-framework-dotnet.slnx | 1 + .../AgentWithTextSearchRag.csproj | 21 +++++ .../Catalog/AgentWithTextSearchRag/Program.cs | 82 +++++++++++++++++++ .../Catalog/AgentWithTextSearchRag/README.md | 41 ++++++++++ 4 files changed, 145 insertions(+) create mode 100644 dotnet/samples/Catalog/AgentWithTextSearchRag/AgentWithTextSearchRag.csproj create mode 100644 dotnet/samples/Catalog/AgentWithTextSearchRag/Program.cs create mode 100644 dotnet/samples/Catalog/AgentWithTextSearchRag/README.md diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx index cbda6c2809..d342c06be2 100644 --- a/dotnet/agent-framework-dotnet.slnx +++ b/dotnet/agent-framework-dotnet.slnx @@ -137,6 +137,7 @@ + diff --git a/dotnet/samples/Catalog/AgentWithTextSearchRag/AgentWithTextSearchRag.csproj b/dotnet/samples/Catalog/AgentWithTextSearchRag/AgentWithTextSearchRag.csproj new file mode 100644 index 0000000000..c6bab8327e --- /dev/null +++ b/dotnet/samples/Catalog/AgentWithTextSearchRag/AgentWithTextSearchRag.csproj @@ -0,0 +1,21 @@ + + + + Exe + net9.0 + + enable + enable + + + + + + + + + + + + + diff --git a/dotnet/samples/Catalog/AgentWithTextSearchRag/Program.cs b/dotnet/samples/Catalog/AgentWithTextSearchRag/Program.cs new file mode 100644 index 0000000000..931ce50014 --- /dev/null +++ b/dotnet/samples/Catalog/AgentWithTextSearchRag/Program.cs @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft. All rights reserved. + +// This sample shows how to use TextSearchProvider to add retrieval augmented generation (RAG) +// capabilities to an AI agent. The provider runs a search against an external knowledge base +// before each model invocation and injects the results into the model context. + +using Azure.AI.OpenAI; +using Azure.Identity; +using Microsoft.Agents.AI; +using Microsoft.Agents.AI.Data; +using Microsoft.Extensions.AI; +using OpenAI; + +var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); +var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; + +TextSearchProviderOptions textSearchOptions = new() +{ + // Run the search prior to every model invocation and keep a short rolling window of conversation context. + SearchTime = TextSearchProviderOptions.TextSearchBehavior.BeforeAIInvoke, + RecentMessageMemoryLimit = 6, +}; + +AIAgent agent = new AzureOpenAIClient( + new Uri(endpoint), + new AzureCliCredential()) + .GetChatClient(deploymentName) + .CreateAIAgent(new ChatClientAgentOptions + { + Instructions = "You are a helpful support specialist for Contoso Outdoors. Answer questions using the provided context and cite the source document when available.", + AIContextProviderFactory = _ => new TextSearchProvider(MockSearchAsync, textSearchOptions) + }); + +AgentThread thread = agent.GetNewThread(); + +Console.WriteLine(">> Asking about returns\n"); +Console.WriteLine(await agent.RunAsync("Hi! I need help understanding the return policy.", thread)); + +Console.WriteLine("\n>> Asking about shipping\n"); +Console.WriteLine(await agent.RunAsync("How long does standard shipping usually take?", thread)); + +Console.WriteLine("\n>> Asking about product care\n"); +Console.WriteLine(await agent.RunAsync("What is the best way to maintain the TrailRunner tent fabric?", thread)); + +static Task> MockSearchAsync(string query, CancellationToken cancellationToken) +{ + // The mock search inspects the user's question and returns pre-defined snippets + // that resemble documents stored in an external knowledge source. + List results = new(); + + if (query.Contains("return", StringComparison.OrdinalIgnoreCase) || query.Contains("refund", StringComparison.OrdinalIgnoreCase)) + { + results.Add(new() + { + Name = "Contoso Outdoors Return Policy", + Link = "https://contoso.com/policies/returns", + Value = "Customers may return any item within 30 days of delivery. Items should be unused and include original packaging. Refunds are issued to the original payment method within 5 business days of inspection." + }); + } + + if (query.Contains("shipping", StringComparison.OrdinalIgnoreCase)) + { + results.Add(new() + { + Name = "Contoso Outdoors Shipping Guide", + Link = "https://contoso.com/help/shipping", + Value = "Standard shipping is free on orders over $50 and typically arrives in 3-5 business days within the continental United States. Expedited options are available at checkout." + }); + } + + if (query.Contains("tent", StringComparison.OrdinalIgnoreCase) || query.Contains("fabric", StringComparison.OrdinalIgnoreCase)) + { + results.Add(new() + { + Name = "TrailRunner Tent Care Instructions", + Link = "https://contoso.com/manuals/trailrunner-tent", + Value = "Clean the tent fabric with lukewarm water and a non-detergent soap. Allow it to air dry completely before storage and avoid prolonged UV exposure to extend the lifespan of the waterproof coating." + }); + } + + return Task.FromResult>(results); +} diff --git a/dotnet/samples/Catalog/AgentWithTextSearchRag/README.md b/dotnet/samples/Catalog/AgentWithTextSearchRag/README.md new file mode 100644 index 0000000000..614597bed9 --- /dev/null +++ b/dotnet/samples/Catalog/AgentWithTextSearchRag/README.md @@ -0,0 +1,41 @@ +# What this sample demonstrates + +This sample demonstrates how to use TextSearchProvider to add retrieval augmented generation (RAG) capabilities to an AI agent. The provider runs a search against an external knowledge base before each model invocation and injects the results into the model context. + +Key features: +- Configuring TextSearchProvider with custom search behavior +- Running searches before AI invocations to provide relevant context +- Managing conversation memory with a rolling window approach +- Citing source documents in AI responses + +## Prerequisites + +Before running this sample, ensure you have: + +1. An Azure OpenAI endpoint configured +2. A deployment of a chat model (e.g., gpt-4o-mini) +3. Azure CLI installed and authenticated + +## Environment Variables + +Set the following environment variables: + +```powershell +# Replace with your Azure OpenAI endpoint +$env:AZURE_OPENAI_ENDPOINT="https://your-openai-resource.openai.azure.com/" + +# Optional, defaults to gpt-4o-mini +$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini" +``` + +## How It Works + +The sample uses a mock search function that demonstrates the RAG pattern: + +1. When the user asks a question, the TextSearchProvider intercepts it +2. The search function looks for relevant documents based on the query +3. Retrieved documents are injected into the model's context +4. The AI responds using both its training and the provided context +5. The agent can cite specific source documents in its answers + +The mock search function returns pre-defined snippets for demonstration purposes. In a production scenario, you would replace this with actual searches against your knowledge base (e.g., Azure AI Search, vector database, etc.).