From 8a3cab38ee33b935a376834cdb12fcb7b9867339 Mon Sep 17 00:00:00 2001
From: westey <164392973+westey-m@users.noreply.github.com>
Date: Wed, 29 Oct 2025 14:34:00 +0000
Subject: [PATCH] .NET: Add a sample that shows how to do RAG with the
TextSearchProvider (#1757)
* Add a sample that shows how to do RAG with the TextSearchProvider
* Update file encoding.
---
dotnet/agent-framework-dotnet.slnx | 1 +
.../Agent_Step18_TextSearchRag.csproj | 21 +++++
.../Agent_Step18_TextSearchRag/Program.cs | 82 +++++++++++++++++++
.../samples/GettingStarted/Agents/README.md | 1 +
4 files changed, 105 insertions(+)
create mode 100644 dotnet/samples/GettingStarted/Agents/Agent_Step18_TextSearchRag/Agent_Step18_TextSearchRag.csproj
create mode 100644 dotnet/samples/GettingStarted/Agents/Agent_Step18_TextSearchRag/Program.cs
diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx
index 6ede4d8e79..cf1e367293 100644
--- a/dotnet/agent-framework-dotnet.slnx
+++ b/dotnet/agent-framework-dotnet.slnx
@@ -58,6 +58,7 @@
+
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step18_TextSearchRag/Agent_Step18_TextSearchRag.csproj b/dotnet/samples/GettingStarted/Agents/Agent_Step18_TextSearchRag/Agent_Step18_TextSearchRag.csproj
new file mode 100644
index 0000000000..8298cfe6e8
--- /dev/null
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step18_TextSearchRag/Agent_Step18_TextSearchRag.csproj
@@ -0,0 +1,21 @@
+
+
+
+ Exe
+ net9.0
+
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step18_TextSearchRag/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step18_TextSearchRag/Program.cs
new file mode 100644
index 0000000000..f06ba68298
--- /dev/null
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step18_TextSearchRag/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/GettingStarted/Agents/README.md b/dotnet/samples/GettingStarted/Agents/README.md
index c306c8066a..09ebf5e0db 100644
--- a/dotnet/samples/GettingStarted/Agents/README.md
+++ b/dotnet/samples/GettingStarted/Agents/README.md
@@ -43,6 +43,7 @@ Before you begin, ensure you have the following prerequisites:
|[Using plugins with an agent](./Agent_Step15_Plugins/)|This sample demonstrates how to use plugins with an agent|
|[Reducing chat history size](./Agent_Step16_ChatReduction/)|This sample demonstrates how to reduce the chat history to constrain its size, where chat history is maintained locally|
|[Background responses](./Agent_Step17_BackgroundResponses/)|This sample demonstrates how to use background responses for long-running operations with polling and resumption support|
+|[Adding RAG with text search](./Agent_Step18_TextSearchRag/)|This sample demonstrates how to enrich agent responses with retrieval augmented generation using the text search provider|
## Running the samples from the console