{"id":2632,"date":"2025-10-23T17:40:31","date_gmt":"2025-10-23T17:40:31","guid":{"rendered":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/2025\/10\/23\/upgrading-to-microsoft-agent-framework-in-your-net-ai-chat-app\/"},"modified":"2025-10-23T17:40:31","modified_gmt":"2025-10-23T17:40:31","slug":"upgrading-to-microsoft-agent-framework-in-your-net-ai-chat-app","status":"publish","type":"post","link":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/2025\/10\/23\/upgrading-to-microsoft-agent-framework-in-your-net-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\u2019s a cool and solid foundation.<\/p>\n<p>But here\u2019s 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\u2019s where <strong>Microsoft Agent Framework<\/strong> comes into play.<\/p>\n<p>In this post, I\u2019m 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\u2019s 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\u2019s preview framework for building AI agents in .NET. Think of it as the next evolution beyond simple chatbots. An AI agent can:<\/p>\n<p><strong>Reason and plan<\/strong> through multi-step workflows<br \/>\n<strong>Use tools and functions<\/strong> to interact with your APIs, databases, and services<br \/>\n<strong>Maintain context<\/strong> across entire conversations<br \/>\n<strong>Make autonomous decisions<\/strong> based on instructions and data<br \/>\n<strong>Coordinate with other agents<\/strong> in multi-agent scenarios<\/p>\n<p>What I really like about it is that it\u2019s 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\u2019 great post about AgentFx<\/a> for all the details.<\/p>\n<h2>Prerequisites<\/h2>\n<p>Before we start, you\u2019ll need:<\/p>\n<p><a href=\"https:\/\/dotnet.microsoft.com\/download\/dotnet\/9.0\">.NET 9 SDK<\/a> installed<br \/>\n<a href=\"https:\/\/visualstudio.microsoft.com\/\">Visual Studio<\/a> or <a href=\"https:\/\/code.visualstudio.com\/\">Visual Studio Code<\/a> with C# Dev Kit<br \/>\nAn <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><br \/>\nThe .NET AI App Templates installed (we\u2019ll do this in the next section)<br \/>\nBasic familiarity with .NET, Blazor, and AI concepts<\/p>\n<h2>Step 1: Creating the Base AI Chat Application<\/h2>\n<p>Let\u2019s start by creating a baseline chat app using the official .NET AI templates. First, we need to install the templates:<\/p>\n<p>dotnet new install Microsoft.Extensions.AI.Templates<\/p>\n<h3>Creating the Project<\/h3>\n<p>Now let\u2019s create the app. You can do this through Visual Studio or the CLI:<\/p>\n<p><strong>Using Visual Studio:<\/strong><\/p>\n<p>Open Visual Studio 2022<br \/>\nSelect <strong>Create a new project<\/strong><br \/>\nSearch for \u201cAI Chat Web App\u201d<br \/>\nConfigure your project name (e.g., ChatApp20) and location<br \/>\nSelect <strong>Azure OpenAI<\/strong> as your AI provider<br \/>\nChoose <strong>Local on-disk<\/strong> for the vector store<br \/>\nChoose .NET Aspire for the orchestration<\/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\u2019ll use dotnet new 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<p>ChatApp20\/<br \/>\n\u251c\u2500\u2500 ChatApp20.Web\/              # Blazor Server app with chat UI<br \/>\n\u251c\u2500\u2500 ChatApp20.AppHost\/          # .NET Aspire orchestration<br \/>\n\u2514\u2500\u2500 ChatApp20.ServiceDefaults\/  # Shared service configurations<\/p>\n<p>We\u2019ll be working mainly in ChatApp20.Web, which includes:<\/p>\n<p><strong>Components\/Pages\/Chat\/<\/strong> \u2013 The Blazor chat interface<br \/>\n<strong>Services\/<\/strong> \u2013 Data ingestion and semantic search services<br \/>\n<strong>Program.cs<\/strong> \u2013 Where all the AI magic gets wired up<br \/>\n<strong>wwwroot\/Data\/<\/strong> \u2013 Sample PDF files (survival kit and GPS watch examples)<\/p>\n<h3>Initial Program.cs Configuration<\/h3>\n<p>Let\u2019s look at what the template sets up for us in Program.cs. This is where all the AI pieces come together:<\/p>\n<p>using Microsoft.Extensions.AI;<br \/>\nusing ChatApp20.Web.Components;<br \/>\nusing ChatApp20.Web.Services;<br \/>\nusing ChatApp20.Web.Services.Ingestion;<\/p>\n<p>var builder = WebApplication.CreateBuilder(args);<br \/>\nbuilder.AddServiceDefaults();<br \/>\nbuilder.Services.AddRazorComponents().AddInteractiveServerComponents();<\/p>\n<p>\/\/ Configure Azure OpenAI with chat client and embeddings<br \/>\nvar openai = builder.AddAzureOpenAIClient(&#8220;openai&#8221;);<br \/>\nopenai.AddChatClient(&#8220;gpt-4o-mini&#8221;)<br \/>\n    .UseFunctionInvocation()<br \/>\n    .UseOpenTelemetry(configure: c =&gt;<br \/>\n        c.EnableSensitiveData = builder.Environment.IsDevelopment());<br \/>\nopenai.AddEmbeddingGenerator(&#8220;text-embedding-3-small&#8221;);<\/p>\n<p>\/\/ Configure vector storage for semantic search<br \/>\nvar vectorStorePath = Path.Combine(AppContext.BaseDirectory, &#8220;vector-store.db&#8221;);<br \/>\nvar vectorStoreConnectionString = $&#8221;Data Source={vectorStorePath}&#8221;;<br \/>\nbuilder.Services.AddSqliteCollection&lt;string, IngestedChunk&gt;(&#8220;data-chatapp20-chunks&#8221;, vectorStoreConnectionString);<br \/>\nbuilder.Services.AddSqliteCollection&lt;string, IngestedDocument&gt;(&#8220;data-chatapp20-documents&#8221;, vectorStoreConnectionString);<br \/>\nbuilder.Services.AddScoped&lt;DataIngestor&gt;();<br \/>\nbuilder.Services.AddSingleton&lt;SemanticSearch&gt;();<\/p>\n<p>var app = builder.Build();<\/p>\n<p>\/\/ &#8230; middleware configuration &#8230;<\/p>\n<p>\/\/ Ingest PDF files on startup<br \/>\nawait DataIngestor.IngestDataAsync(<br \/>\n    app.Services,<br \/>\n    new PDFDirectorySource(Path.Combine(builder.Environment.WebRootPath, &#8220;Data&#8221;)));<\/p>\n<p>app.Run();<\/p>\n<h3>The Basic Chat Component<\/h3>\n<p>The initial Chat.razor component uses IChatClient directly:<\/p>\n<p>@inject IChatClient ChatClient<br \/>\n@inject SemanticSearch Search<\/p>\n<p>@code {<br \/>\n    private async Task AddUserMessageAsync(ChatMessage userMessage)<br \/>\n    {<br \/>\n        messages.Add(userMessage);<\/p>\n<p>        var responseText = new TextContent(&#8220;&#8221;);<br \/>\n        currentResponseMessage = new ChatMessage(ChatRole.Assistant, [responseText]);<\/p>\n<p>        await foreach (var update in ChatClient.GetStreamingResponseAsync(<br \/>\n            messages.Skip(statefulMessageCount),<br \/>\n            chatOptions,<br \/>\n            currentResponseCancellation.Token))<br \/>\n        {<br \/>\n            messages.AddMessages(update, filter: c =&gt; c is not TextContent);<br \/>\n            responseText.Text += update.Text;<br \/>\n            ChatMessageItem.NotifyChanged(currentResponseMessage);<br \/>\n        }<\/p>\n<p>        messages.Add(currentResponseMessage);<br \/>\n    }<\/p>\n<p>    [Description(&#8220;Searches for information using a phrase or keyword&#8221;)]<br \/>\n    private async Task&lt;IEnumerable&lt;string&gt;&gt; SearchAsync(<br \/>\n        [Description(&#8220;The phrase to search for.&#8221;)] string searchPhrase,<br \/>\n        [Description(&#8220;If possible, specify the filename to search.&#8221;)] string? filenameFilter = null)<br \/>\n    {<br \/>\n        var results = await Search.SearchAsync(searchPhrase, filenameFilter, maxResults: 5);<br \/>\n        return results.Select(result =&gt;<br \/>\n            $&#8221;&lt;result filename=&#8221;{result.DocumentId}&#8221; page_number=&#8221;{result.PageNumber}&#8221;&gt;{result.Text}&lt;\/result&gt;&#8221;);<br \/>\n    }<br \/>\n}<\/p>\n<p>This works great for getting started! But as your app grows, you\u2019ll want more flexibility:<\/p>\n<p><strong>Better separation of concerns<\/strong> \u2013 Moving tool functions out of UI components<br \/>\n<strong>Easier testing<\/strong> \u2013 Testing agent behavior independently from the UI<br \/>\n<strong>More sophisticated patterns<\/strong> \u2013 Support for complex reasoning and multi-step workflows<br \/>\n<strong>Agent orchestration<\/strong> \u2013 Coordinating multiple specialized agents<br \/>\n<strong>Richer telemetry<\/strong> \u2013 Better observability into how your AI makes decisions<\/p>\n<p>That\u2019s exactly what Microsoft Agent Framework brings to the table. Let\u2019s see how!<\/p>\n<h2>Step 2: Adding Microsoft Agent Framework<\/h2>\n<p>Now for the fun part\u2014let\u2019s 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 ChatApp20.Web.csproj:<\/p>\n\n<p>&lt;ItemGroup&gt;<br \/>\n  &lt;!&#8211; Keep existing packages &#8211;&gt;<br \/>\n  &lt;PackageReference Include=&#8221;Aspire.Azure.AI.OpenAI&#8221; Version=&#8221;9.5.1-preview.1.25502.11&#8243; \/&gt;<br \/>\n  &lt;PackageReference Include=&#8221;Microsoft.Extensions.AI.OpenAI&#8221; Version=&#8221;9.10.0-preview.1.25513.3&#8243; \/&gt;<br \/>\n  &lt;PackageReference Include=&#8221;Microsoft.Extensions.AI&#8221; Version=&#8221;9.10.0&#8243; \/&gt;<br \/>\n  &lt;PackageReference Include=&#8221;Microsoft.SemanticKernel.Core&#8221; Version=&#8221;1.66.0&#8243; \/&gt;<\/p>\n<p>  &lt;!&#8211; Add Microsoft Agent Framework packages &#8211;&gt;<br \/>\n  &lt;PackageReference Include=&#8221;Microsoft.Agents.AI&#8221; Version=&#8221;1.0.0-preview.251009.1&#8243; \/&gt;<br \/>\n  &lt;PackageReference Include=&#8221;Microsoft.Agents.AI.Abstractions&#8221; Version=&#8221;1.0.0-preview.251009.1&#8243; \/&gt;<br \/>\n  &lt;PackageReference Include=&#8221;Microsoft.Agents.AI.Hosting&#8221; Version=&#8221;1.0.0-preview.251009.1&#8243; \/&gt;<br \/>\n  &lt;PackageReference Include=&#8221;Microsoft.Agents.AI.Hosting.OpenAI&#8221; Version=&#8221;1.0.0-alpha.251009.1&#8243; \/&gt;<br \/>\n  &lt;PackageReference Include=&#8221;Microsoft.Agents.AI.OpenAI&#8221; Version=&#8221;1.0.0-preview.251009.1&#8243; \/&gt;<\/p>\n<p>  &lt;!&#8211; Keep other existing packages &#8211;&gt;<br \/>\n  &lt;PackageReference Include=&#8221;PdfPig&#8221; Version=&#8221;0.1.12-alpha-20251015-255e7&#8243; \/&gt;<br \/>\n  &lt;PackageReference Include=&#8221;System.Linq.Async&#8221; Version=&#8221;7.0.0-preview.1.g24680b5469&#8243; \/&gt;<br \/>\n  &lt;PackageReference Include=&#8221;Microsoft.SemanticKernel.Connectors.SqliteVec&#8221; Version=&#8221;1.66.0-preview&#8221; \/&gt;<br \/>\n&lt;\/ItemGroup&gt;<\/p>\n<p>The key Agent Framework packages are:<\/p>\n<p><strong>Microsoft.Agents.AI<\/strong> \u2013 Core agent abstractions and implementations<br \/>\n<strong>Microsoft.Agents.AI.Abstractions<\/strong> \u2013 Base interfaces and types<br \/>\n<strong>Microsoft.Agents.AI.Hosting<\/strong> \u2013 Dependency injection and hosting extensions<br \/>\n<strong>Microsoft.Agents.AI.Hosting.OpenAI<\/strong> \u2013 OpenAI-specific hosting support<br \/>\n<strong>Microsoft.Agents.AI.OpenAI<\/strong> \u2013 OpenAI integration for agents<\/p>\n<h3>Creating a Dedicated Search Functions Service<\/h3>\n<p>To promote better separation of concerns and testability, create a new SearchFunctions.cs service that wraps the semantic search functionality:<\/p>\n<p>using System.ComponentModel;<\/p>\n<p>namespace ChatApp20.Web.Services;<\/p>\n<p>\/\/\/ &lt;summary&gt;<br \/>\n\/\/\/ Functions exposed to the AI Agent. Wraps SemanticSearch so we can inject dependencies via DI.<br \/>\n\/\/\/ &lt;\/summary&gt;<br \/>\npublic class SearchFunctions<br \/>\n{<br \/>\n    private readonly SemanticSearch _semanticSearch;<\/p>\n<p>    public SearchFunctions(SemanticSearch semanticSearch)<br \/>\n    {<br \/>\n        _semanticSearch = semanticSearch;<br \/>\n    }<\/p>\n<p>    [Description(&#8220;Searches for information using a phrase or keyword&#8221;)]<br \/>\n    public async Task&lt;IEnumerable&lt;string&gt;&gt; SearchAsync(<br \/>\n        [Description(&#8220;The phrase to search for.&#8221;)] string searchPhrase,<br \/>\n        [Description(&#8220;If possible, specify the filename to search that file only. If not provided or empty, the search includes all files.&#8221;)] string? filenameFilter = null)<br \/>\n    {<br \/>\n        \/\/ Perform semantic search over ingested chunks<br \/>\n        var results = await _semanticSearch.SearchAsync(searchPhrase, filenameFilter, maxResults: 5);<\/p>\n<p>        \/\/ Format results as XML for the agent<br \/>\n        return results.Select(result =&gt;<br \/>\n            $&#8221;&lt;result filename=&#8221;{result.DocumentId}&#8221; page_number=&#8221;{result.PageNumber}&#8221;&gt;{result.Text}&lt;\/result&gt;&#8221;);<br \/>\n    }<br \/>\n}<\/p>\n<p><strong>Why this is important:<\/strong><\/p>\n<p>The SearchFunctions class is now a dedicated service that can be injected into the agent<br \/>\nIt\u2019s testable in isolation from the UI<br \/>\nThe [Description] attributes provide metadata that helps the AI understand when and how to use the tool<br \/>\nThe agent can invoke this function automatically when it needs to search for information<\/p>\n<h3>Registering the AI Agent in Program.cs<\/h3>\n<p>Now, let\u2019s configure the AI agent in Program.cs using the Agent Framework\u2019s hosting extensions:<\/p>\n<p>using ChatApp20.Web.Components;<br \/>\nusing ChatApp20.Web.Services;<br \/>\nusing ChatApp20.Web.Services.Ingestion;<br \/>\nusing Microsoft.Agents.AI;<br \/>\nusing Microsoft.Agents.AI.Hosting;<br \/>\nusing Microsoft.Extensions.AI;<br \/>\nusing System.ComponentModel;<\/p>\n<p>var builder = WebApplication.CreateBuilder(args);<br \/>\nbuilder.AddServiceDefaults();<br \/>\nbuilder.Services.AddRazorComponents().AddInteractiveServerComponents();<\/p>\n<p>\/\/ Configure Azure OpenAI<br \/>\nvar openai = builder.AddAzureOpenAIClient(&#8220;openai&#8221;);<br \/>\nopenai.AddChatClient(&#8220;gpt-4o-mini&#8221;)<br \/>\n    .UseFunctionInvocation()<br \/>\n    .UseOpenTelemetry(configure: c =&gt;<br \/>\n        c.EnableSensitiveData = builder.Environment.IsDevelopment());<\/p>\n<p>\/\/ Register the AI Agent using the Agent Framework<br \/>\nbuilder.AddAIAgent(&#8220;ChatAgent&#8221;, (sp, key) =&gt;<br \/>\n{<br \/>\n    \/\/ Get required services<br \/>\n    var logger = sp.GetRequiredService&lt;ILogger&lt;Program&gt;&gt;();<br \/>\n    logger.LogInformation(&#8220;Configuring AI Agent with key &#8216;{Key}&#8217; for model &#8216;{Model}'&#8221;, key, &#8220;gpt-4o-mini&#8221;);<\/p>\n<p>    var searchFunctions = sp.GetRequiredService&lt;SearchFunctions&gt;();<br \/>\n    var chatClient = sp.GetRequiredService&lt;IChatClient&gt;();<\/p>\n<p>    \/\/ Create and configure the AI agent<br \/>\n    var aiAgent = chatClient.CreateAIAgent(<br \/>\n        name: key,<br \/>\n        instructions: &#8220;You are a useful agent that helps users with short and funny answers.&#8221;,<br \/>\n        description: &#8220;An AI agent that helps users with short and funny answers.&#8221;,<br \/>\n        tools: [AIFunctionFactory.Create(searchFunctions.SearchAsync)]<br \/>\n        )<br \/>\n    .AsBuilder()<br \/>\n    .UseOpenTelemetry(configure: c =&gt;<br \/>\n        c.EnableSensitiveData = builder.Environment.IsDevelopment())<br \/>\n    .Build();<\/p>\n<p>    return aiAgent;<br \/>\n});<\/p>\n<p>\/\/ Configure embeddings and vector storage<br \/>\nopenai.AddEmbeddingGenerator(&#8220;text-embedding-3-small&#8221;);<\/p>\n<p>var vectorStorePath = Path.Combine(AppContext.BaseDirectory, &#8220;vector-store.db&#8221;);<br \/>\nvar vectorStoreConnectionString = $&#8221;Data Source={vectorStorePath}&#8221;;<br \/>\nbuilder.Services.AddSqliteCollection&lt;string, IngestedChunk&gt;(&#8220;data-chatapp20-chunks&#8221;, vectorStoreConnectionString);<br \/>\nbuilder.Services.AddSqliteCollection&lt;string, IngestedDocument&gt;(&#8220;data-chatapp20-documents&#8221;, vectorStoreConnectionString);<br \/>\nbuilder.Services.AddScoped&lt;DataIngestor&gt;();<br \/>\nbuilder.Services.AddSingleton&lt;SemanticSearch&gt;();<\/p>\n<p>\/\/ Register SearchFunctions for DI injection into the agent<br \/>\nbuilder.Services.AddSingleton&lt;SearchFunctions&gt;();<\/p>\n<p>var app = builder.Build();<\/p>\n<p>\/\/ &#8230; rest of the configuration &#8230;<\/p>\n<p><strong>Key points about the agent registration:<\/strong><\/p>\n<p><strong>Keyed Service Registration<\/strong>: The agent is registered with the key &#8220;ChatAgent&#8221; using builder.AddAIAgent(). This allows you to register multiple agents in the same application.<br \/>\n<strong>Agent Configuration<\/strong>: The agent is created with:<\/p>\n<p>A <strong>name<\/strong> for identification<br \/>\n<strong>Instructions<\/strong> (system prompt) that define its personality and behavior<br \/>\nA <strong>description<\/strong> that explains its purpose<br \/>\n<strong>Tools<\/strong> that the agent can use (in this case, the SearchAsync function)<\/p>\n<p><strong>Tool Binding<\/strong>: The AIFunctionFactory.Create() method converts the SearchAsync method into a tool that the agent can invoke. The framework automatically handles:<\/p>\n<p>Parameter validation based on the [Description] attributes<br \/>\nJSON serialization\/deserialization<br \/>\nError handling and retries<\/p>\n<p><strong>Telemetry<\/strong>: The UseOpenTelemetry() call ensures that all agent interactions are logged and can be observed through Application Insights or other monitoring tools.<br \/>\n<strong>Dependency Injection<\/strong>: The agent factory receives an IServiceProvider, allowing it to resolve dependencies like SearchFunctions and IChatClient.<\/p>\n<h3>Updating the Chat Component<\/h3>\n<p>Finally, we need to update Chat.razor to use our new AI agent. The changes are pretty straightforward:<\/p>\n<p><strong>Key changes in the code-behind:<\/strong><\/p>\n<p><strong>Inject the IServiceProvider<\/strong> instead of IChatClient:<\/p>\n<p>@inject IServiceProvider ServiceProvider<br \/>\n@using Microsoft.Agents.AI<\/p>\n<p><strong>Resolve the agent<\/strong> in OnInitialized():<\/p>\n<p>private AIAgent aiAgent = default!;<\/p>\n<p>protected override void OnInitialized()<br \/>\n{<br \/>\n    \/\/ Resolve the keyed AI agent registered as &#8220;ChatAgent&#8221; in Program.cs<br \/>\n    aiAgent = ServiceProvider.GetRequiredKeyedService&lt;AIAgent&gt;(&#8220;ChatAgent&#8221;);<br \/>\n    \/\/ &#8230; rest of initialization &#8230;<br \/>\n}<\/p>\n<p><strong>Use agent streaming<\/strong> in AddUserMessageAsync():<\/p>\n<p>\/\/ Replace ChatClient.GetStreamingResponseAsync with agent streaming<br \/>\nawait foreach (var update in aiAgent.RunStreamingAsync(<br \/>\n    messages: messages.Skip(statefulMessageCount),<br \/>\n    cancellationToken: currentResponseCancellation.Token))<br \/>\n{<br \/>\n    var responseUpdate = update.AsChatResponseUpdate();<br \/>\n    messages.AddMessages(responseUpdate, filter: c =&gt; c is not TextContent);<br \/>\n    responseText.Text += update.Text;<br \/>\n    chatOptions.ConversationId = responseUpdate.ConversationId;<br \/>\n    ChatMessageItem.NotifyChanged(currentResponseMessage);<br \/>\n}<\/p>\n<p>That\u2019s 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<p><strong>Service discovery<\/strong> between components<br \/>\n<strong>Unified logging<\/strong> and telemetry in the Aspire dashboard<br \/>\n<strong>Health checks<\/strong> for all services<br \/>\n<strong>Easy configuration<\/strong> for all your secrets and settings<\/p>\n<p>Run the app. The Aspire dashboard opens automatically in your browser<\/p>\n\n<h3>Configuring Azure OpenAI<\/h3>\n<p>On first run, you\u2019ll be prompted to configure Azure OpenAI:<\/p>\n<p><strong>Azure Subscription<\/strong>: Select your subscription<br \/>\n<strong>Resource Group<\/strong>: Choose existing or create new<br \/>\n<strong>Azure OpenAI Resource<\/strong>: Select or provision<br \/>\n<strong>Model Deployments<\/strong>: Ensure you have:<\/p>\n<p>A chat model (e.g., gpt-4o-mini)<br \/>\nAn embedding model (e.g., text-embedding-3-small)<\/p>\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 https:\/\/localhost:7001).<\/p>\n\n<p>Let\u2019s test it out:<\/p>\n<p><strong>Basic conversation:<\/strong><br \/>\nYou: Hello! How are you?<br \/>\nAgent: Hey! I&#8217;m great \u2014 fully charged, like an Emergency Survival Kit.<\/p>\n<p><strong>Tool invocation with semantic search:<\/strong><br \/>\nYou: What should I include in an emergency survival kit?<br \/>\nAgent: Short survival-kit checklist (funny edition) First aid supplies \u2014 bandages, gauze, antiseptics.<br \/>\n      &lt;citation filename=&#8217;Example_Emergency_Survival_Kit.pdf&#8217; page_number=&#8217;1&#8242;&gt;water and food supplies&lt;\/citation&gt;<\/p>\n<p><strong>File-specific queries:<\/strong><br \/>\nYou: Tell me about the GPS watch features<br \/>\nAgent: The GPS watch includes&#8230;<br \/>\n      &lt;citation filename=&#8217;Example_GPS_Watch.pdf&#8217; page_number=&#8217;2&#8242;&gt;real-time tracking&lt;\/citation&gt;<\/p>\n<p>Here\u2019s the cool part: check out the Aspire dashboard while the agent is working. You can actually see:<\/p>\n<p>When the agent decides to invoke the search tool<br \/>\nWhat parameters it passes<br \/>\nThe search results it gets back<br \/>\nHow it synthesizes everything into a response<\/p>\n<div class=\"wp-video\"><!--[if lt IE 9]&gt;document.createElement('video');&lt;![endif]--><br \/>\n<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><\/div>\n<p>This level of observability is invaluable when you\u2019re debugging or optimizing your agent\u2019s 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<p>public class WeatherFunctions<br \/>\n{<br \/>\n    [Description(&#8220;Gets the current weather for a location&#8221;)]<br \/>\n    public async Task&lt;string&gt; GetWeatherAsync(<br \/>\n        [Description(&#8220;The city and state\/country&#8221;)] string location)<br \/>\n    {<br \/>\n        \/\/ Call weather API<br \/>\n        return $&#8221;Weather for {location}: Sunny, 72\u00b0F&#8221;;<br \/>\n    }<br \/>\n}<\/p>\n<p>\/\/ In Program.cs<br \/>\nbuilder.Services.AddSingleton&lt;WeatherFunctions&gt;();<\/p>\n<p>builder.AddAIAgent(&#8220;ChatAgent&#8221;, (sp, key) =&gt;<br \/>\n{<br \/>\n    var searchFunctions = sp.GetRequiredService&lt;SearchFunctions&gt;();<br \/>\n    var weatherFunctions = sp.GetRequiredService&lt;WeatherFunctions&gt;();<br \/>\n    var chatClient = sp.GetRequiredService&lt;IChatClient&gt;();<\/p>\n<p>    return chatClient.CreateAIAgent(<br \/>\n        name: key,<br \/>\n        instructions: &#8220;You can search documents and check weather&#8230;&#8221;,<br \/>\n        tools: [<br \/>\n            AIFunctionFactory.Create(searchFunctions.SearchAsync),<br \/>\n            AIFunctionFactory.Create(weatherFunctions.GetWeatherAsync)<br \/>\n        ]<br \/>\n    ).Build();<br \/>\n});<\/p>\n<div class=\"alert alert-primary\">\n<p class=\"alert-divider\"><strong>Note<\/strong><\/p>\n<p>You can check out the full running sample in <a href=\"https:\/\/aka.ms\/genainet\">Generative AI for Beginners \u2013 .NET<\/a>.<\/p><\/div>\n<h3>Multi-Agent Scenarios<\/h3>\n<p>The Agent Framework makes it easy to coordinate multiple specialized agents:<\/p>\n<p>\/\/ Register a research agent<br \/>\nbuilder.AddAIAgent(&#8220;ResearchAgent&#8221;, (sp, key) =&gt;<br \/>\n{<br \/>\n    var chatClient = sp.GetRequiredService&lt;IChatClient&gt;();<br \/>\n    var searchFunctions = sp.GetRequiredService&lt;SearchFunctions&gt;();<\/p>\n<p>    return chatClient.CreateAIAgent(<br \/>\n        name: &#8220;ResearchAgent&#8221;,<br \/>\n        instructions: &#8220;You are a research specialist. Find and summarize information from documents.&#8221;,<br \/>\n        tools: [AIFunctionFactory.Create(searchFunctions.SearchAsync)]<br \/>\n    ).Build();<br \/>\n});<\/p>\n<p>\/\/ Register a writing agent<br \/>\nbuilder.AddAIAgent(&#8220;WritingAgent&#8221;, (sp, key) =&gt;<br \/>\n{<br \/>\n    var chatClient = sp.GetRequiredService&lt;IChatClient&gt;();<\/p>\n<p>    return chatClient.CreateAIAgent(<br \/>\n        name: &#8220;WritingAgent&#8221;,<br \/>\n        instructions: &#8220;You are a writing specialist. Take information and create well-structured, engaging content.&#8221;,<br \/>\n        tools: []<br \/>\n    ).Build();<br \/>\n});<\/p>\n<p>\/\/ Register a coordinator agent that uses both<br \/>\nbuilder.AddAIAgent(&#8220;CoordinatorAgent&#8221;, (sp, key) =&gt;<br \/>\n{<br \/>\n    var chatClient = sp.GetRequiredService&lt;IChatClient&gt;();<br \/>\n    var researchAgent = sp.GetRequiredKeyedService&lt;AIAgent&gt;(&#8220;ResearchAgent&#8221;);<br \/>\n    var writingAgent = sp.GetRequiredKeyedService&lt;AIAgent&gt;(&#8220;WritingAgent&#8221;);<\/p>\n<p>    \/\/ Create functions that delegate to other agents<br \/>\n    async Task&lt;string&gt; ResearchAsync(string topic)<br \/>\n    {<br \/>\n        var messages = new[] { new ChatMessage(ChatRole.User, topic) };<br \/>\n        var result = await researchAgent.RunAsync(messages);<br \/>\n        return result.Text ?? &#8220;&#8221;;<br \/>\n    }<\/p>\n<p>    async Task&lt;string&gt; WriteAsync(string content)<br \/>\n    {<br \/>\n        var messages = new[] { new ChatMessage(ChatRole.User, $&#8221;Write an article based on: {content}&#8221;) };<br \/>\n        var result = await writingAgent.RunAsync(messages);<br \/>\n        return result.Text ?? &#8220;&#8221;;<br \/>\n    }<\/p>\n<p>    return chatClient.CreateAIAgent(<br \/>\n        name: &#8220;CoordinatorAgent&#8221;,<br \/>\n        instructions: &#8220;Coordinate research and writing to create comprehensive articles.&#8221;,<br \/>\n        tools: [<br \/>\n            AIFunctionFactory.Create(ResearchAsync),<br \/>\n            AIFunctionFactory.Create(WriteAsync)<br \/>\n        ]<br \/>\n    ).Build();<br \/>\n});<\/p>\n<div class=\"alert alert-primary\">\n<p class=\"alert-divider\"><strong>Note<\/strong><\/p>\n<p>For more examples of multi-agent coordination patterns, check out the <a href=\"https:\/\/aka.ms\/genainet\">Generative AI for Beginners \u2013 .NET<\/a>.<\/p><\/div>\n<h3>Custom Agent Middleware<\/h3>\n<p>You can add custom middleware to agents for logging, caching, or custom behavior:<\/p>\n<p>builder.AddAIAgent(&#8220;ChatAgent&#8221;, (sp, key) =&gt;<br \/>\n{<br \/>\n    var chatClient = sp.GetRequiredService&lt;IChatClient&gt;();<br \/>\n    var searchFunctions = sp.GetRequiredService&lt;SearchFunctions&gt;();<br \/>\n    var logger = sp.GetRequiredService&lt;ILogger&lt;Program&gt;&gt;();<\/p>\n<p>    return chatClient.CreateAIAgent(<br \/>\n        name: key,<br \/>\n        instructions: &#8220;&#8230;&#8221;,<br \/>\n        tools: [AIFunctionFactory.Create(searchFunctions.SearchAsync)]<br \/>\n        )<br \/>\n    .AsBuilder()<br \/>\n    .Use(async (messages, options, next, cancellationToken) =&gt;<br \/>\n    {<br \/>\n        \/\/ Custom pre-processing<br \/>\n        logger.LogInformation(&#8220;Agent processing {MessageCount} messages&#8221;, messages.Count());<\/p>\n<p>        \/\/ Call next in pipeline<br \/>\n        var result = await next(messages, options, cancellationToken);<\/p>\n<p>        \/\/ Custom post-processing<br \/>\n        logger.LogInformation(&#8220;Agent generated response with {ContentCount} content items&#8221;, result.Contents.Count);<\/p>\n<p>        return result;<br \/>\n    })<br \/>\n    .UseOpenTelemetry(configure: c =&gt; c.EnableSensitiveData = true)<br \/>\n    .Build();<br \/>\n});<\/p>\n<div class=\"alert alert-primary\">\n<p class=\"alert-divider\"><strong>Note<\/strong><\/p>\n<p>You can find more examples of custom middleware patterns in the <a href=\"https:\/\/aka.ms\/genainet\">Generative AI for Beginners \u2013 .NET<\/a>.<\/p><\/div>\n<h2>Best Practices<\/h2>\n<h3>1. Design Clear Tool Descriptions<\/h3>\n<p>The quality of your agent\u2019s tool invocations depends heavily on good descriptions:<\/p>\n<p>[Description(&#8220;Searches for specific information in product documentation. &#8221; +<br \/>\n             &#8220;Use this when the user asks about features, specifications, or how to use products. &#8221; +<br \/>\n             &#8220;Returns relevant excerpts with filename and page numbers for citations.&#8221;)]<br \/>\npublic async Task&lt;IEnumerable&lt;string&gt;&gt; SearchAsync(<br \/>\n    [Description(&#8220;The specific phrase, keyword, or question to search for. &#8221; +<br \/>\n                 &#8220;Be specific and include relevant context.&#8221;)]<br \/>\n    string searchPhrase,<br \/>\n    [Description(&#8220;Optional: The exact filename to search within (e.g., &#8216;ProductManual.pdf&#8217;). &#8221; +<br \/>\n                 &#8220;Leave empty to search all documents.&#8221;)]<br \/>\n    string? filenameFilter = null)<br \/>\n{<br \/>\n    \/\/ Implementation<br \/>\n}<\/p>\n<h3>2. Test Agent Behavior<\/h3>\n<p>Create unit tests for your agent tools and integration tests for agent workflows:<\/p>\n<p>public class SearchFunctionsTests<br \/>\n{<br \/>\n    [Fact]<br \/>\n    public async Task SearchAsync_WithValidQuery_ReturnsResults()<br \/>\n    {<br \/>\n        \/\/ Arrange<br \/>\n        var mockSemanticSearch = new Mock&lt;SemanticSearch&gt;();<br \/>\n        mockSemanticSearch<br \/>\n            .Setup(s =&gt; s.SearchAsync(&#8220;test&#8221;, null, 5))<br \/>\n            .ReturnsAsync(new List&lt;IngestedChunk&gt;<br \/>\n            {<br \/>\n                new IngestedChunk { DocumentId = &#8220;test.pdf&#8221;, PageNumber = 1, Text = &#8220;test content&#8221; }<br \/>\n            });<\/p>\n<p>        var searchFunctions = new SearchFunctions(mockSemanticSearch.Object);<\/p>\n<p>        \/\/ Act<br \/>\n        var results = await searchFunctions.SearchAsync(&#8220;test&#8221;);<\/p>\n<p>        \/\/ Assert<br \/>\n        Assert.NotEmpty(results);<br \/>\n        Assert.Contains(&#8220;test content&#8221;, results.First());<br \/>\n    }<br \/>\n}<\/p>\n<h3>3. Monitor Agent Performance<\/h3>\n<p>Use Application Insights or .NET Aspire\u2019s dashboard to monitor:<\/p>\n<p><strong>Token usage<\/strong> per agent interaction<br \/>\n<strong>Tool invocation patterns<\/strong> (which tools are used, how often)<br \/>\n<strong>Response times<\/strong> for agent operations<br \/>\n<strong>Error rates<\/strong> for tool calls<br \/>\n<strong>User satisfaction<\/strong> through feedback mechanisms<\/p>\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<p>Building interactive chat interfaces<br \/>\nUsers expect real-time feedback<br \/>\nProcessing long-running queries<\/p>\n<p><strong>Use non-streaming when:<\/strong><\/p>\n<p>Processing in the background<br \/>\nBatch operations<br \/>\nSimple API endpoints<\/p>\n<h3>Tool Call Optimization<\/h3>\n<p>Minimize unnecessary tool calls:<\/p>\n<p>\/\/ Good: Specific instructions<br \/>\n&#8220;Use the search tool only when the user asks a specific question about the documents.<br \/>\nDon&#8217;t search if you can answer from general knowledge.&#8221;<\/p>\n<p>\/\/ Bad: Vague instructions<br \/>\n&#8220;You have access to a search tool.&#8221;<\/p>\n<h2>Deployment to Azure<\/h2>\n<p>The application is ready for deployment to Azure using .NET Aspire\u2019s Azure provisioning:<\/p>\n<p># Login to Azure<br \/>\naz login<\/p>\n<p># Create Azure resources<br \/>\ncd ChatApp20.AppHost<br \/>\nazd init<br \/>\nazd up<\/p>\n<p>This will:<\/p>\n<p>Provision Azure OpenAI resources<br \/>\nDeploy the web application to Azure Container Apps<br \/>\nSet up Application Insights for monitoring<br \/>\nConfigure service connections and authentication<\/p>\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\u2019ve 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\u2019t 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\u2019re 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\u2019 announcement post<\/a> to learn more!<\/p>\n<p>The post <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/upgrading-to-microsoft-agent-framework-in-your-dotnet-ai-chat-app\/\">Upgrading to Microsoft Agent Framework in Your .NET AI Chat App<\/a> appeared first on <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\">.NET Blog<\/a>.<\/p>","protected":false},"excerpt":{"rendered":"<p>The AI App Templates let you spin up a working chat application in minutes, complete with AI integration, custom data [&hellip;]<\/p>\n","protected":false},"author":0,"featured_media":0,"comment_status":"","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"categories":[7],"tags":[],"class_list":["post-2632","post","type-post","status-publish","format-standard","hentry","category-dotnet"],"_links":{"self":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/posts\/2632","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/types\/post"}],"replies":[{"embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/comments?post=2632"}],"version-history":[{"count":0,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/posts\/2632\/revisions"}],"wp:attachment":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/media?parent=2632"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/categories?post=2632"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/tags?post=2632"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}