{"id":58593,"date":"2025-10-23T10:00:00","date_gmt":"2025-10-23T17:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/dotnet\/?p=58593"},"modified":"2025-10-23T08:39:31","modified_gmt":"2025-10-23T15:39:31","slug":"upgrading-to-microsoft-agent-framework-in-your-dotnet-ai-chat-app","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/upgrading-to-microsoft-agent-framework-in-your-dotnet-ai-chat-app\/","title":{"rendered":"Upgrading to Microsoft Agent Framework in Your .NET AI Chat App"},"content":{"rendered":"<p>The AI App Templates let you spin up a working chat application in minutes, complete with AI integration, custom data ingestion, and all the pieces you need to get started. It&#8217;s a cool and solid foundation.<\/p>\n<p>But here&#8217;s the thing: what if you want to go beyond basic chat? What if you want to build AI agents that can actually <em>reason<\/em>, make decisions, use tools, and orchestrate complex workflows? That&#8217;s where <strong>Microsoft Agent Framework<\/strong> comes into play.<\/p>\n<p>In this post, I&#8217;m going to show you how I took a standard AI chat app\u2014generated using the .NET AI templates\u2014and enhanced it with Microsoft Agent Framework. Let&#8217;s start!<\/p>\n<h2>What is Microsoft Agent Framework?<\/h2>\n<p><a href=\"https:\/\/aka.ms\/agent-framework\">Microsoft Agent Framework<\/a> is Microsoft&#8217;s preview framework for building AI agents in .NET. Think of it as the next evolution beyond simple chatbots. An AI agent can:<\/p>\n<ul>\n<li><strong>Reason and plan<\/strong> through multi-step workflows<\/li>\n<li><strong>Use tools and functions<\/strong> to interact with your APIs, databases, and services<\/li>\n<li><strong>Maintain context<\/strong> across entire conversations<\/li>\n<li><strong>Make autonomous decisions<\/strong> based on instructions and data<\/li>\n<li><strong>Coordinate with other agents<\/strong> in multi-agent scenarios<\/li>\n<\/ul>\n<p>What I really like about it is that it&#8217;s built on patterns we already know and love as .NET developers: dependency injection, middleware, telemetry\u2014all integrated with Microsoft.Extensions.AI. Check out <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/introducing-microsoft-agent-framework-preview\/\">Luis&#8217; great post about AgentFx<\/a> for all the details.<\/p>\n<h2>Prerequisites<\/h2>\n<p>Before we start, you&#8217;ll need:<\/p>\n<ul>\n<li><a href=\"https:\/\/dotnet.microsoft.com\/download\/dotnet\/9.0\">.NET 9 SDK<\/a> installed<\/li>\n<li><a href=\"https:\/\/visualstudio.microsoft.com\/\">Visual Studio<\/a> or <a href=\"https:\/\/code.visualstudio.com\/\">Visual Studio Code<\/a> with C# Dev Kit<\/li>\n<li>An <a href=\"https:\/\/azure.microsoft.com\/free\/\">Azure account<\/a> with access to Azure OpenAI, or use it with <a href=\"https:\/\/github.com\/marketplace?type=models\">GitHub Models<\/a><\/li>\n<li>The .NET AI App Templates installed (we&#8217;ll do this in the next section)<\/li>\n<li>Basic familiarity with .NET, Blazor, and AI concepts<\/li>\n<\/ul>\n<h2>Step 1: Creating the Base AI Chat Application<\/h2>\n<p>Let&#8217;s start by creating a baseline chat app using the official .NET AI templates. First, we need to install the templates:<\/p>\n<pre><code class=\"language-bash\">dotnet new install Microsoft.Extensions.AI.Templates<\/code><\/pre>\n<h3>Creating the Project<\/h3>\n<p>Now let&#8217;s create the app. You can do this through Visual Studio or the CLI:<\/p>\n<p><strong>Using Visual Studio:<\/strong><\/p>\n<ol>\n<li>Open Visual Studio 2022<\/li>\n<li>Select <strong>Create a new project<\/strong><\/li>\n<li>Search for &#8220;AI Chat Web App&#8221;<\/li>\n<li>Configure your project name (e.g., <code>ChatApp20<\/code>) and location<\/li>\n<li>Select <strong>Azure OpenAI<\/strong> as your AI provider<\/li>\n<li>Choose <strong>Local on-disk<\/strong> for the vector store<\/li>\n<li>Choose .NET Aspire for the orchestration<\/li>\n<\/ol>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2025\/10\/01-image-vs-template.png\" alt=\"Visual Studio project dialog with AI Chat Web App template\" \/><\/p>\n<p><strong>Using Visual Studio Code or the .NET CLI:<\/strong><\/p>\n<p>If you prefer VS Code or the command line, check out the <a href=\"https:\/\/learn.microsoft.com\/dotnet\/ai\/quickstarts\/ai-templates?tabs=visual-studio-code%2Cconfigure-visual-studio-code%2Cconfigure-visual-studio-code-aspire&amp;pivots=azure-openai#create-the-net-ai-app\">official documentation<\/a> for step-by-step instructions. The process is similar\u2014you&#8217;ll use <code>dotnet new<\/code> commands to scaffold the project with the same configuration options.<\/p>\n<h3>Understanding the Project Structure<\/h3>\n<p>The template generates a solution with three projects:<\/p>\n<pre><code class=\"language-bash\">ChatApp20\/\r\n\u251c\u2500\u2500 ChatApp20.Web\/              # Blazor Server app with chat UI\r\n\u251c\u2500\u2500 ChatApp20.AppHost\/          # .NET Aspire orchestration\r\n\u2514\u2500\u2500 ChatApp20.ServiceDefaults\/  # Shared service configurations<\/code><\/pre>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2025\/10\/02-image-solution-structure.png\" alt=\"Solution Explorer with project structure\" \/><\/p>\n<p>We&#8217;ll be working mainly in <code>ChatApp20.Web<\/code>, which includes:<\/p>\n<ul>\n<li><strong>Components\/Pages\/Chat\/<\/strong> &#8211; The Blazor chat interface<\/li>\n<li><strong>Services\/<\/strong> &#8211; Data ingestion and semantic search services<\/li>\n<li><strong>Program.cs<\/strong> &#8211; Where all the AI magic gets wired up<\/li>\n<li><strong>wwwroot\/Data\/<\/strong> &#8211; Sample PDF files (survival kit and GPS watch examples)<\/li>\n<\/ul>\n<h3>Initial Program.cs Configuration<\/h3>\n<p>Let&#8217;s look at what the template sets up for us in <code>Program.cs<\/code>. This is where all the AI pieces come together:<\/p>\n<pre><code class=\"language-csharp\">using Microsoft.Extensions.AI;\r\nusing ChatApp20.Web.Components;\r\nusing ChatApp20.Web.Services;\r\nusing ChatApp20.Web.Services.Ingestion;\r\n\r\nvar builder = WebApplication.CreateBuilder(args);\r\nbuilder.AddServiceDefaults();\r\nbuilder.Services.AddRazorComponents().AddInteractiveServerComponents();\r\n\r\n\/\/ Configure Azure OpenAI with chat client and embeddings\r\nvar openai = builder.AddAzureOpenAIClient(\"openai\");\r\nopenai.AddChatClient(\"gpt-4o-mini\")\r\n    .UseFunctionInvocation()\r\n    .UseOpenTelemetry(configure: c =&gt;\r\n        c.EnableSensitiveData = builder.Environment.IsDevelopment());\r\nopenai.AddEmbeddingGenerator(\"text-embedding-3-small\");\r\n\r\n\/\/ Configure vector storage for semantic search\r\nvar vectorStorePath = Path.Combine(AppContext.BaseDirectory, \"vector-store.db\");\r\nvar vectorStoreConnectionString = $\"Data Source={vectorStorePath}\";\r\nbuilder.Services.AddSqliteCollection&lt;string, IngestedChunk&gt;(\"data-chatapp20-chunks\", vectorStoreConnectionString);\r\nbuilder.Services.AddSqliteCollection&lt;string, IngestedDocument&gt;(\"data-chatapp20-documents\", vectorStoreConnectionString);\r\nbuilder.Services.AddScoped&lt;DataIngestor&gt;();\r\nbuilder.Services.AddSingleton&lt;SemanticSearch&gt;();\r\n\r\nvar app = builder.Build();\r\n\r\n\/\/ ... middleware configuration ...\r\n\r\n\/\/ Ingest PDF files on startup\r\nawait DataIngestor.IngestDataAsync(\r\n    app.Services,\r\n    new PDFDirectorySource(Path.Combine(builder.Environment.WebRootPath, \"Data\")));\r\n\r\napp.Run();<\/code><\/pre>\n<h3>The Basic Chat Component<\/h3>\n<p>The initial <code>Chat.razor<\/code> component uses <code>IChatClient<\/code> directly:<\/p>\n<pre><code class=\"language-csharp\">@inject IChatClient ChatClient\r\n@inject SemanticSearch Search\r\n\r\n@code {\r\n    private async Task AddUserMessageAsync(ChatMessage userMessage)\r\n    {\r\n        messages.Add(userMessage);\r\n\r\n        var responseText = new TextContent(\"\");\r\n        currentResponseMessage = new ChatMessage(ChatRole.Assistant, [responseText]);\r\n\r\n        await foreach (var update in ChatClient.GetStreamingResponseAsync(\r\n            messages.Skip(statefulMessageCount), \r\n            chatOptions, \r\n            currentResponseCancellation.Token))\r\n        {\r\n            messages.AddMessages(update, filter: c =&gt; c is not TextContent);\r\n            responseText.Text += update.Text;\r\n            ChatMessageItem.NotifyChanged(currentResponseMessage);\r\n        }\r\n\r\n        messages.Add(currentResponseMessage);\r\n    }\r\n\r\n    [Description(\"Searches for information using a phrase or keyword\")]\r\n    private async Task&lt;IEnumerable&lt;string&gt;&gt; SearchAsync(\r\n        [Description(\"The phrase to search for.\")] string searchPhrase,\r\n        [Description(\"If possible, specify the filename to search.\")] string? filenameFilter = null)\r\n    {\r\n        var results = await Search.SearchAsync(searchPhrase, filenameFilter, maxResults: 5);\r\n        return results.Select(result =&gt;\r\n            $\"&lt;result filename=\\\"{result.DocumentId}\\\" page_number=\\\"{result.PageNumber}\\\"&gt;{result.Text}&lt;\/result&gt;\");\r\n    }\r\n}<\/code><\/pre>\n<p>This works great for getting started! But as your app grows, you&#8217;ll want more flexibility:<\/p>\n<ul>\n<li><strong>Better separation of concerns<\/strong> &#8211; Moving tool functions out of UI components<\/li>\n<li><strong>Easier testing<\/strong> &#8211; Testing agent behavior independently from the UI<\/li>\n<li><strong>More sophisticated patterns<\/strong> &#8211; Support for complex reasoning and multi-step workflows<\/li>\n<li><strong>Agent orchestration<\/strong> &#8211; Coordinating multiple specialized agents<\/li>\n<li><strong>Richer telemetry<\/strong> &#8211; Better observability into how your AI makes decisions<\/li>\n<\/ul>\n<p>That&#8217;s exactly what Microsoft Agent Framework brings to the table. Let&#8217;s see how!<\/p>\n<h2>Step 2: Adding Microsoft Agent Framework<\/h2>\n<p>Now for the fun part\u2014let&#8217;s upgrade this chat app into a proper agent system!<\/p>\n<h3>Installing the Required Packages<\/h3>\n<p>First, we need to add the Microsoft Agent Framework packages to <code>ChatApp20.Web.csproj<\/code>:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2025\/10\/03-image-nuget-packages.png\" alt=\"NuGet packages added to project\" \/><\/p>\n<pre><code class=\"language-xml\">&lt;ItemGroup&gt;\r\n  &lt;!-- Keep existing packages --&gt;\r\n  &lt;PackageReference Include=\"Aspire.Azure.AI.OpenAI\" Version=\"9.5.1-preview.1.25502.11\" \/&gt;\r\n  &lt;PackageReference Include=\"Microsoft.Extensions.AI.OpenAI\" Version=\"9.10.0-preview.1.25513.3\" \/&gt;\r\n  &lt;PackageReference Include=\"Microsoft.Extensions.AI\" Version=\"9.10.0\" \/&gt;\r\n  &lt;PackageReference Include=\"Microsoft.SemanticKernel.Core\" Version=\"1.66.0\" \/&gt;\r\n\r\n  &lt;!-- Add Microsoft Agent Framework packages --&gt;\r\n  &lt;PackageReference Include=\"Microsoft.Agents.AI\" Version=\"1.0.0-preview.251009.1\" \/&gt;\r\n  &lt;PackageReference Include=\"Microsoft.Agents.AI.Abstractions\" Version=\"1.0.0-preview.251009.1\" \/&gt;\r\n  &lt;PackageReference Include=\"Microsoft.Agents.AI.Hosting\" Version=\"1.0.0-preview.251009.1\" \/&gt;\r\n  &lt;PackageReference Include=\"Microsoft.Agents.AI.Hosting.OpenAI\" Version=\"1.0.0-alpha.251009.1\" \/&gt;\r\n  &lt;PackageReference Include=\"Microsoft.Agents.AI.OpenAI\" Version=\"1.0.0-preview.251009.1\" \/&gt;\r\n\r\n  &lt;!-- Keep other existing packages --&gt;\r\n  &lt;PackageReference Include=\"PdfPig\" Version=\"0.1.12-alpha-20251015-255e7\" \/&gt;\r\n  &lt;PackageReference Include=\"System.Linq.Async\" Version=\"7.0.0-preview.1.g24680b5469\" \/&gt;\r\n  &lt;PackageReference Include=\"Microsoft.SemanticKernel.Connectors.SqliteVec\" Version=\"1.66.0-preview\" \/&gt;\r\n&lt;\/ItemGroup&gt;<\/code><\/pre>\n<p>The key Agent Framework packages are:<\/p>\n<ul>\n<li><strong>Microsoft.Agents.AI<\/strong> &#8211; Core agent abstractions and implementations<\/li>\n<li><strong>Microsoft.Agents.AI.Abstractions<\/strong> &#8211; Base interfaces and types<\/li>\n<li><strong>Microsoft.Agents.AI.Hosting<\/strong> &#8211; Dependency injection and hosting extensions<\/li>\n<li><strong>Microsoft.Agents.AI.Hosting.OpenAI<\/strong> &#8211; OpenAI-specific hosting support<\/li>\n<li><strong>Microsoft.Agents.AI.OpenAI<\/strong> &#8211; OpenAI integration for agents<\/li>\n<\/ul>\n<h3>Creating a Dedicated Search Functions Service<\/h3>\n<p>To promote better separation of concerns and testability, create a new <code>SearchFunctions.cs<\/code> service that wraps the semantic search functionality:<\/p>\n<pre><code class=\"language-csharp\">using System.ComponentModel;\r\n\r\nnamespace ChatApp20.Web.Services;\r\n\r\n\/\/\/ &lt;summary&gt;\r\n\/\/\/ Functions exposed to the AI Agent. Wraps SemanticSearch so we can inject dependencies via DI.\r\n\/\/\/ &lt;\/summary&gt;\r\npublic class SearchFunctions\r\n{\r\n    private readonly SemanticSearch _semanticSearch;\r\n\r\n    public SearchFunctions(SemanticSearch semanticSearch)\r\n    {\r\n        _semanticSearch = semanticSearch;\r\n    }\r\n\r\n    [Description(\"Searches for information using a phrase or keyword\")]\r\n    public async Task&lt;IEnumerable&lt;string&gt;&gt; SearchAsync(\r\n        [Description(\"The phrase to search for.\")] string searchPhrase,\r\n        [Description(\"If possible, specify the filename to search that file only. If not provided or empty, the search includes all files.\")] string? filenameFilter = null)\r\n    {\r\n        \/\/ Perform semantic search over ingested chunks\r\n        var results = await _semanticSearch.SearchAsync(searchPhrase, filenameFilter, maxResults: 5);\r\n\r\n        \/\/ Format results as XML for the agent\r\n        return results.Select(result =&gt;\r\n            $\"&lt;result filename=\\\"{result.DocumentId}\\\" page_number=\\\"{result.PageNumber}\\\"&gt;{result.Text}&lt;\/result&gt;\");\r\n    }\r\n}<\/code><\/pre>\n<p><strong>Why this is important:<\/strong><\/p>\n<ul>\n<li>The <code>SearchFunctions<\/code> class is now a dedicated service that can be injected into the agent<\/li>\n<li>It&#8217;s testable in isolation from the UI<\/li>\n<li>The <code>[Description]<\/code> attributes provide metadata that helps the AI understand when and how to use the tool<\/li>\n<li>The agent can invoke this function automatically when it needs to search for information<\/li>\n<\/ul>\n<h3>Registering the AI Agent in Program.cs<\/h3>\n<p>Now, let&#8217;s configure the AI agent in <code>Program.cs<\/code> using the Agent Framework&#8217;s hosting extensions:<\/p>\n<pre><code class=\"language-csharp\">using ChatApp20.Web.Components;\r\nusing ChatApp20.Web.Services;\r\nusing ChatApp20.Web.Services.Ingestion;\r\nusing Microsoft.Agents.AI;\r\nusing Microsoft.Agents.AI.Hosting;\r\nusing Microsoft.Extensions.AI;\r\nusing System.ComponentModel;\r\n\r\nvar builder = WebApplication.CreateBuilder(args);\r\nbuilder.AddServiceDefaults();\r\nbuilder.Services.AddRazorComponents().AddInteractiveServerComponents();\r\n\r\n\/\/ Configure Azure OpenAI\r\nvar openai = builder.AddAzureOpenAIClient(\"openai\");\r\nopenai.AddChatClient(\"gpt-4o-mini\")\r\n    .UseFunctionInvocation()\r\n    .UseOpenTelemetry(configure: c =&gt;\r\n        c.EnableSensitiveData = builder.Environment.IsDevelopment());\r\n\r\n\/\/ Register the AI Agent using the Agent Framework\r\nbuilder.AddAIAgent(\"ChatAgent\", (sp, key) =&gt;\r\n{\r\n    \/\/ Get required services\r\n    var logger = sp.GetRequiredService&lt;ILogger&lt;Program&gt;&gt;();\r\n    logger.LogInformation(\"Configuring AI Agent with key '{Key}' for model '{Model}'\", key, \"gpt-4o-mini\");\r\n\r\n    var searchFunctions = sp.GetRequiredService&lt;SearchFunctions&gt;();\r\n    var chatClient = sp.GetRequiredService&lt;IChatClient&gt;();\r\n\r\n    \/\/ Create and configure the AI agent\r\n    var aiAgent = chatClient.CreateAIAgent(\r\n        name: key,\r\n        instructions: \"You are a useful agent that helps users with short and funny answers.\",\r\n        description: \"An AI agent that helps users with short and funny answers.\",\r\n        tools: [AIFunctionFactory.Create(searchFunctions.SearchAsync)]\r\n        )\r\n    .AsBuilder()\r\n    .UseOpenTelemetry(configure: c =&gt;\r\n        c.EnableSensitiveData = builder.Environment.IsDevelopment())\r\n    .Build();\r\n\r\n    return aiAgent;\r\n});\r\n\r\n\/\/ Configure embeddings and vector storage\r\nopenai.AddEmbeddingGenerator(\"text-embedding-3-small\");\r\n\r\nvar vectorStorePath = Path.Combine(AppContext.BaseDirectory, \"vector-store.db\");\r\nvar vectorStoreConnectionString = $\"Data Source={vectorStorePath}\";\r\nbuilder.Services.AddSqliteCollection&lt;string, IngestedChunk&gt;(\"data-chatapp20-chunks\", vectorStoreConnectionString);\r\nbuilder.Services.AddSqliteCollection&lt;string, IngestedDocument&gt;(\"data-chatapp20-documents\", vectorStoreConnectionString);\r\nbuilder.Services.AddScoped&lt;DataIngestor&gt;();\r\nbuilder.Services.AddSingleton&lt;SemanticSearch&gt;();\r\n\r\n\/\/ Register SearchFunctions for DI injection into the agent\r\nbuilder.Services.AddSingleton&lt;SearchFunctions&gt;();\r\n\r\nvar app = builder.Build();\r\n\r\n\/\/ ... rest of the configuration ...<\/code><\/pre>\n<p><strong>Key points about the agent registration:<\/strong><\/p>\n<ol>\n<li><strong>Keyed Service Registration<\/strong>: The agent is registered with the key <code>\"ChatAgent\"<\/code> using <code>builder.AddAIAgent()<\/code>. This allows you to register multiple agents in the same application.<\/li>\n<li><strong>Agent Configuration<\/strong>: The agent is created with:\n<ul>\n<li>A <strong>name<\/strong> for identification<\/li>\n<li><strong>Instructions<\/strong> (system prompt) that define its personality and behavior<\/li>\n<li>A <strong>description<\/strong> that explains its purpose<\/li>\n<li><strong>Tools<\/strong> that the agent can use (in this case, the <code>SearchAsync<\/code> function)<\/li>\n<\/ul>\n<\/li>\n<li><strong>Tool Binding<\/strong>: The <code>AIFunctionFactory.Create()<\/code> method converts the <code>SearchAsync<\/code> method into a tool that the agent can invoke. The framework automatically handles:\n<ul>\n<li>Parameter validation based on the <code>[Description]<\/code> attributes<\/li>\n<li>JSON serialization\/deserialization<\/li>\n<li>Error handling and retries<\/li>\n<\/ul>\n<\/li>\n<li><strong>Telemetry<\/strong>: The <code>UseOpenTelemetry()<\/code> call ensures that all agent interactions are logged and can be observed through Application Insights or other monitoring tools.<\/li>\n<li><strong>Dependency Injection<\/strong>: The agent factory receives an <code>IServiceProvider<\/code>, allowing it to resolve dependencies like <code>SearchFunctions<\/code> and <code>IChatClient<\/code>.<\/li>\n<\/ol>\n<h3>Updating the Chat Component<\/h3>\n<p>Finally, we need to update <code>Chat.razor<\/code> to use our new AI agent. The changes are pretty straightforward:<\/p>\n<p><strong>Key changes in the code-behind:<\/strong><\/p>\n<ol>\n<li><strong>Inject the IServiceProvider<\/strong> instead of IChatClient:<\/li>\n<\/ol>\n<pre><code class=\"language-csharp\">@inject IServiceProvider ServiceProvider\r\n@using Microsoft.Agents.AI<\/code><\/pre>\n<ol>\n<li><strong>Resolve the agent<\/strong> in <code>OnInitialized()<\/code>:<\/li>\n<\/ol>\n<pre><code class=\"language-csharp\">private AIAgent aiAgent = default!;\r\n\r\nprotected override void OnInitialized()\r\n{\r\n    \/\/ Resolve the keyed AI agent registered as \"ChatAgent\" in Program.cs\r\n    aiAgent = ServiceProvider.GetRequiredKeyedService&lt;AIAgent&gt;(\"ChatAgent\");\r\n    \/\/ ... rest of initialization ...\r\n}<\/code><\/pre>\n<ol>\n<li><strong>Use agent streaming<\/strong> in <code>AddUserMessageAsync()<\/code>:<\/li>\n<\/ol>\n<pre><code class=\"language-csharp\">\/\/ Replace ChatClient.GetStreamingResponseAsync with agent streaming\r\nawait foreach (var update in aiAgent.RunStreamingAsync(\r\n    messages: messages.Skip(statefulMessageCount),\r\n    cancellationToken: currentResponseCancellation.Token))\r\n{\r\n    var responseUpdate = update.AsChatResponseUpdate();\r\n    messages.AddMessages(responseUpdate, filter: c =&gt; c is not TextContent);\r\n    responseText.Text += update.Text;\r\n    chatOptions.ConversationId = responseUpdate.ConversationId;\r\n    ChatMessageItem.NotifyChanged(currentResponseMessage);\r\n}<\/code><\/pre>\n<p>That&#8217;s it! The agent handles everything else\u2014tool invocation, reasoning, and response generation.<\/p>\n<h2>Step 3: Running and Testing the Enhanced Application<\/h2>\n<h3>Running with .NET Aspire<\/h3>\n<p>One of the best parts about using the AI templates is that everything runs through .NET Aspire. This gives you:<\/p>\n<ul>\n<li><strong>Service discovery<\/strong> between components<\/li>\n<li><strong>Unified logging<\/strong> and telemetry in the Aspire dashboard<\/li>\n<li><strong>Health checks<\/strong> for all services<\/li>\n<li><strong>Easy configuration<\/strong> for all your secrets and settings<\/li>\n<\/ul>\n<p>Run the app. The Aspire dashboard opens automatically in your browser<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2025\/10\/04-image-aspire-dashboard.png\" alt=\"Aspire dashboard with running application\" \/><\/p>\n<h3>Configuring Azure OpenAI<\/h3>\n<p>On first run, you&#8217;ll be prompted to configure Azure OpenAI:<\/p>\n<ol>\n<li><strong>Azure Subscription<\/strong>: Select your subscription<\/li>\n<li><strong>Resource Group<\/strong>: Choose existing or create new<\/li>\n<li><strong>Azure OpenAI Resource<\/strong>: Select or provision<\/li>\n<li><strong>Model Deployments<\/strong>: Ensure you have:\n<ul>\n<li>A chat model (e.g., <code>gpt-4o-mini<\/code>)<\/li>\n<li>An embedding model (e.g., <code>text-embedding-3-small<\/code>)<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<p>The configuration will be saved locally and reused for subsequent runs.<\/p>\n<h3>Testing the Agent<\/h3>\n<p>Once everything is running, click on the web endpoint in the Aspire dashboard (usually <code>https:\/\/localhost:7001<\/code>).<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2025\/10\/05-image-chat-ui.png\" alt=\"Chat interface with documents\" \/><\/p>\n<p>Let&#8217;s test it out:<\/p>\n<ol>\n<li><strong>Basic conversation:<\/strong>\n<pre><code class=\"language-bash\">You: Hello! How are you?\r\nAgent: Hey! I'm great \u2014 fully charged, like an Emergency Survival Kit.<\/code><\/pre>\n<\/li>\n<li><strong>Tool invocation with semantic search:<\/strong>\n<pre><code class=\"language-bash\">You: What should I include in an emergency survival kit?\r\nAgent: Short survival-kit checklist (funny edition) First aid supplies \u2014 bandages, gauze, antiseptics.\r\n      &lt;citation filename='Example_Emergency_Survival_Kit.pdf' page_number='1'&gt;water and food supplies&lt;\/citation&gt;<\/code><\/pre>\n<\/li>\n<li><strong>File-specific queries:<\/strong>\n<pre><code class=\"language-bash\">You: Tell me about the GPS watch features\r\nAgent: The GPS watch includes... \r\n      &lt;citation filename='Example_GPS_Watch.pdf' page_number='2'&gt;real-time tracking&lt;\/citation&gt;<\/code><\/pre>\n<\/li>\n<\/ol>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2025\/10\/06-image-agent-response.png\" alt=\"Agent response with citations\" \/><\/p>\n<p>Here&#8217;s the cool part: check out the Aspire dashboard while the agent is working. You can actually see:<\/p>\n<ul>\n<li>When the agent decides to invoke the search tool<\/li>\n<li>What parameters it passes<\/li>\n<li>The search results it gets back<\/li>\n<li>How it synthesizes everything into a response<\/li>\n<\/ul>\n<p><div style=\"width: 1000px;\" class=\"wp-video\"><video class=\"wp-video-shortcode\" id=\"video-58593-1\" width=\"1000\" height=\"360\" poster=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2025\/10\/07-image-telemetry.png\" preload=\"metadata\" controls=\"controls\"><source type=\"video\/webm\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2025\/10\/07-image-telemetry.webm?_=1\" \/><a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2025\/10\/07-image-telemetry.webm\">https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2025\/10\/07-image-telemetry.webm<\/a><\/video><\/div><\/p>\n<p>This level of observability is invaluable when you&#8217;re debugging or optimizing your agent&#8217;s behavior.<\/p>\n<h2>Advanced Scenarios<\/h2>\n<h3>Adding More Tools to Your Agent<\/h3>\n<p>You can easily extend your agent with additional capabilities:<\/p>\n<pre><code class=\"language-csharp\">public class WeatherFunctions\r\n{\r\n    [Description(\"Gets the current weather for a location\")]\r\n    public async Task&lt;string&gt; GetWeatherAsync(\r\n        [Description(\"The city and state\/country\")] string location)\r\n    {\r\n        \/\/ Call weather API\r\n        return $\"Weather for {location}: Sunny, 72\u00b0F\";\r\n    }\r\n}\r\n\r\n\/\/ In Program.cs\r\nbuilder.Services.AddSingleton&lt;WeatherFunctions&gt;();\r\n\r\nbuilder.AddAIAgent(\"ChatAgent\", (sp, key) =&gt;\r\n{\r\n    var searchFunctions = sp.GetRequiredService&lt;SearchFunctions&gt;();\r\n    var weatherFunctions = sp.GetRequiredService&lt;WeatherFunctions&gt;();\r\n    var chatClient = sp.GetRequiredService&lt;IChatClient&gt;();\r\n\r\n    return chatClient.CreateAIAgent(\r\n        name: key,\r\n        instructions: \"You can search documents and check weather...\",\r\n        tools: [\r\n            AIFunctionFactory.Create(searchFunctions.SearchAsync),\r\n            AIFunctionFactory.Create(weatherFunctions.GetWeatherAsync)\r\n        ]\r\n    ).Build();\r\n});<\/code><\/pre>\n<p><div class=\"alert alert-primary\"><p class=\"alert-divider\"><i class=\"fabric-icon fabric-icon--Info\"><\/i><strong>Note<\/strong><\/p>You can check out the full running sample in <a href=\"https:\/\/aka.ms\/genainet\">Generative AI for Beginners &#8211; .NET<\/a>.<\/div><\/p>\n<h3>Multi-Agent Scenarios<\/h3>\n<p>The Agent Framework makes it easy to coordinate multiple specialized agents:<\/p>\n<pre><code class=\"language-csharp\">\/\/ Register a research agent\r\nbuilder.AddAIAgent(\"ResearchAgent\", (sp, key) =&gt;\r\n{\r\n    var chatClient = sp.GetRequiredService&lt;IChatClient&gt;();\r\n    var searchFunctions = sp.GetRequiredService&lt;SearchFunctions&gt;();\r\n\r\n    return chatClient.CreateAIAgent(\r\n        name: \"ResearchAgent\",\r\n        instructions: \"You are a research specialist. Find and summarize information from documents.\",\r\n        tools: [AIFunctionFactory.Create(searchFunctions.SearchAsync)]\r\n    ).Build();\r\n});\r\n\r\n\/\/ Register a writing agent\r\nbuilder.AddAIAgent(\"WritingAgent\", (sp, key) =&gt;\r\n{\r\n    var chatClient = sp.GetRequiredService&lt;IChatClient&gt;();\r\n\r\n    return chatClient.CreateAIAgent(\r\n        name: \"WritingAgent\",\r\n        instructions: \"You are a writing specialist. Take information and create well-structured, engaging content.\",\r\n        tools: []\r\n    ).Build();\r\n});\r\n\r\n\/\/ Register a coordinator agent that uses both\r\nbuilder.AddAIAgent(\"CoordinatorAgent\", (sp, key) =&gt;\r\n{\r\n    var chatClient = sp.GetRequiredService&lt;IChatClient&gt;();\r\n    var researchAgent = sp.GetRequiredKeyedService&lt;AIAgent&gt;(\"ResearchAgent\");\r\n    var writingAgent = sp.GetRequiredKeyedService&lt;AIAgent&gt;(\"WritingAgent\");\r\n\r\n    \/\/ Create functions that delegate to other agents\r\n    async Task&lt;string&gt; ResearchAsync(string topic)\r\n    {\r\n        var messages = new[] { new ChatMessage(ChatRole.User, topic) };\r\n        var result = await researchAgent.RunAsync(messages);\r\n        return result.Text ?? \"\";\r\n    }\r\n\r\n    async Task&lt;string&gt; WriteAsync(string content)\r\n    {\r\n        var messages = new[] { new ChatMessage(ChatRole.User, $\"Write an article based on: {content}\") };\r\n        var result = await writingAgent.RunAsync(messages);\r\n        return result.Text ?? \"\";\r\n    }\r\n\r\n    return chatClient.CreateAIAgent(\r\n        name: \"CoordinatorAgent\",\r\n        instructions: \"Coordinate research and writing to create comprehensive articles.\",\r\n        tools: [\r\n            AIFunctionFactory.Create(ResearchAsync),\r\n            AIFunctionFactory.Create(WriteAsync)\r\n        ]\r\n    ).Build();\r\n});<\/code><\/pre>\n<p><div class=\"alert alert-primary\"><p class=\"alert-divider\"><i class=\"fabric-icon fabric-icon--Info\"><\/i><strong>Note<\/strong><\/p>For more examples of multi-agent coordination patterns, check out the <a href=\"https:\/\/aka.ms\/genainet\">Generative AI for Beginners &#8211; .NET<\/a>.<\/div><\/p>\n<h3>Custom Agent Middleware<\/h3>\n<p>You can add custom middleware to agents for logging, caching, or custom behavior:<\/p>\n<pre><code class=\"language-csharp\">builder.AddAIAgent(\"ChatAgent\", (sp, key) =&gt;\r\n{\r\n    var chatClient = sp.GetRequiredService&lt;IChatClient&gt;();\r\n    var searchFunctions = sp.GetRequiredService&lt;SearchFunctions&gt;();\r\n    var logger = sp.GetRequiredService&lt;ILogger&lt;Program&gt;&gt;();\r\n\r\n    return chatClient.CreateAIAgent(\r\n        name: key,\r\n        instructions: \"...\",\r\n        tools: [AIFunctionFactory.Create(searchFunctions.SearchAsync)]\r\n        )\r\n    .AsBuilder()\r\n    .Use(async (messages, options, next, cancellationToken) =&gt;\r\n    {\r\n        \/\/ Custom pre-processing\r\n        logger.LogInformation(\"Agent processing {MessageCount} messages\", messages.Count());\r\n\r\n        \/\/ Call next in pipeline\r\n        var result = await next(messages, options, cancellationToken);\r\n\r\n        \/\/ Custom post-processing\r\n        logger.LogInformation(\"Agent generated response with {ContentCount} content items\", result.Contents.Count);\r\n\r\n        return result;\r\n    })\r\n    .UseOpenTelemetry(configure: c =&gt; c.EnableSensitiveData = true)\r\n    .Build();\r\n});<\/code><\/pre>\n<p><div class=\"alert alert-primary\"><p class=\"alert-divider\"><i class=\"fabric-icon fabric-icon--Info\"><\/i><strong>Note<\/strong><\/p>You can find more examples of custom middleware patterns in the <a href=\"https:\/\/aka.ms\/genainet\">Generative AI for Beginners &#8211; .NET<\/a>.<\/div><\/p>\n<h2>Best Practices<\/h2>\n<h3>1. Design Clear Tool Descriptions<\/h3>\n<p>The quality of your agent&#8217;s tool invocations depends heavily on good descriptions:<\/p>\n<pre><code class=\"language-csharp\">[Description(\"Searches for specific information in product documentation. \" +\r\n             \"Use this when the user asks about features, specifications, or how to use products. \" +\r\n             \"Returns relevant excerpts with filename and page numbers for citations.\")]\r\npublic async Task&lt;IEnumerable&lt;string&gt;&gt; SearchAsync(\r\n    [Description(\"The specific phrase, keyword, or question to search for. \" +\r\n                 \"Be specific and include relevant context.\")] \r\n    string searchPhrase,\r\n    [Description(\"Optional: The exact filename to search within (e.g., 'ProductManual.pdf'). \" +\r\n                 \"Leave empty to search all documents.\")] \r\n    string? filenameFilter = null)\r\n{\r\n    \/\/ Implementation\r\n}<\/code><\/pre>\n<h3>2. Test Agent Behavior<\/h3>\n<p>Create unit tests for your agent tools and integration tests for agent workflows:<\/p>\n<pre><code class=\"language-csharp\">public class SearchFunctionsTests\r\n{\r\n    [Fact]\r\n    public async Task SearchAsync_WithValidQuery_ReturnsResults()\r\n    {\r\n        \/\/ Arrange\r\n        var mockSemanticSearch = new Mock&lt;SemanticSearch&gt;();\r\n        mockSemanticSearch\r\n            .Setup(s =&gt; s.SearchAsync(\"test\", null, 5))\r\n            .ReturnsAsync(new List&lt;IngestedChunk&gt;\r\n            {\r\n                new IngestedChunk { DocumentId = \"test.pdf\", PageNumber = 1, Text = \"test content\" }\r\n            });\r\n\r\n        var searchFunctions = new SearchFunctions(mockSemanticSearch.Object);\r\n\r\n        \/\/ Act\r\n        var results = await searchFunctions.SearchAsync(\"test\");\r\n\r\n        \/\/ Assert\r\n        Assert.NotEmpty(results);\r\n        Assert.Contains(\"test content\", results.First());\r\n    }\r\n}<\/code><\/pre>\n<h3>3. Monitor Agent Performance<\/h3>\n<p>Use Application Insights or .NET Aspire&#8217;s dashboard to monitor:<\/p>\n<ul>\n<li><strong>Token usage<\/strong> per agent interaction<\/li>\n<li><strong>Tool invocation patterns<\/strong> (which tools are used, how often)<\/li>\n<li><strong>Response times<\/strong> for agent operations<\/li>\n<li><strong>Error rates<\/strong> for tool calls<\/li>\n<li><strong>User satisfaction<\/strong> through feedback mechanisms<\/li>\n<\/ul>\n<h2>Performance Considerations<\/h2>\n<h3>Streaming vs. Non-Streaming<\/h3>\n<p>The Agent Framework supports both streaming and non-streaming responses:<\/p>\n<p><strong>Use streaming when:<\/strong><\/p>\n<ul>\n<li>Building interactive chat interfaces<\/li>\n<li>Users expect real-time feedback<\/li>\n<li>Processing long-running queries<\/li>\n<\/ul>\n<p><strong>Use non-streaming when:<\/strong><\/p>\n<ul>\n<li>Processing in the background<\/li>\n<li>Batch operations<\/li>\n<li>Simple API endpoints<\/li>\n<\/ul>\n<h3>Tool Call Optimization<\/h3>\n<p>Minimize unnecessary tool calls:<\/p>\n<pre><code class=\"language-csharp\">\/\/ Good: Specific instructions\r\n\"Use the search tool only when the user asks a specific question about the documents. \r\nDon't search if you can answer from general knowledge.\"\r\n\r\n\/\/ Bad: Vague instructions\r\n\"You have access to a search tool.\"<\/code><\/pre>\n<h2>Deployment to Azure<\/h2>\n<p>The application is ready for deployment to Azure using .NET Aspire&#8217;s Azure provisioning:<\/p>\n<pre><code class=\"language-bash\"># Login to Azure\r\naz login\r\n\r\n# Create Azure resources\r\ncd ChatApp20.AppHost\r\nazd init\r\nazd up<\/code><\/pre>\n<p>This will:<\/p>\n<ol>\n<li>Provision Azure OpenAI resources<\/li>\n<li>Deploy the web application to Azure Container Apps<\/li>\n<li>Set up Application Insights for monitoring<\/li>\n<li>Configure service connections and authentication<\/li>\n<\/ol>\n<p>For detailed deployment instructions, see the <a href=\"https:\/\/learn.microsoft.com\/dotnet\/aspire\/deployment\/azd\/aca-deployment-azd-in-depth?tabs=windows\">.NET Aspire Azure deployment documentation<\/a>.<\/p>\n<h2>Summary<\/h2>\n<p>And there you have it! We&#8217;ve taken a standard AI chat app and transformed it into a proper agent system using Microsoft Agent Framework. The upgrade gives you better architecture with clean separation of concerns, easier testing, and built-in observability\u2014all while using the .NET patterns you already know.<\/p>\n<p>What I really appreciate is that Microsoft Agent Framework doesn&#8217;t force you to learn a completely new way of doing things. It builds on familiar concepts like dependency injection, middleware, and telemetry, making it feel natural for C# developers.<\/p>\n<p>If you&#8217;re building AI applications with .NET, I highly recommend giving the Agent Framework a try. Start with the <a href=\"https:\/\/learn.microsoft.com\/dotnet\/ai\/quickstarts\/ai-templates\">AI templates<\/a>, then layer on the agent capabilities as your needs grow. Check out the <a href=\"https:\/\/aka.ms\/agent-framework\">official documentation<\/a> and <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/introducing-microsoft-agent-framework-preview\/\">Luis&#8217; announcement post<\/a> to learn more!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Step-by-step review on how to upgrade your .NET AI chat app to Microsoft Agent Framework for better architecture, tool integration, and intelligent reasoning.<\/p>\n","protected":false},"author":120281,"featured_media":58594,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[685,7781,7783,756],"tags":[8079,568,8080,7768],"class_list":["post-58593","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet","category-ai","category-aspire","category-csharp","tag-agentfx","tag-ai","tag-aifoundry","tag-aspire"],"acf":[],"blog_post_summary":"<p>Step-by-step review on how to upgrade your .NET AI chat app to Microsoft Agent Framework for better architecture, tool integration, and intelligent reasoning.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/58593","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/users\/120281"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=58593"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/58593\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/58594"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=58593"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=58593"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=58593"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}