Building Your First AI Agent with Microsoft Agent Framework (.NET) – Part 2

Part 2 of the Microsoft Agent Framework Series

In Part 1, we explored what Microsoft Agent Framework is and why it represents the next evolution in enterprise AI development. Now it’s time to get our hands dirty with code.

In this article, we’ll build a complete, production-ready AI agent using C# and .NET 8. By the end, you’ll have a fully functional customer support agent with multiple tools, proper error handling, and logging — ready to extend for your own use cases.

Prerequisites

Before we begin, make sure you have:

  • .NET 8.0 SDK or later (download here)
  • Visual Studio 2022 or VS Code with C# extension
  • Azure subscription with Azure OpenAI resource provisioned
  • Azure CLI installed and authenticated (az login)

Project Setup

Step 1: Create a New Console Project

# Create new project
dotnet new console -n MAF.CustomerSupport
cd MAF.CustomerSupport

# Add required packages
dotnet add package Microsoft.Agents.AI.OpenAI --prerelease
dotnet add package Azure.Identity
dotnet add package Microsoft.Extensions.Logging.Console
dotnet add package Microsoft.Extensions.Configuration.Json

Step 2: Configure appsettings.json

{
  "AzureOpenAI": {
    "Endpoint": "https://your-resource.openai.azure.com",
    "DeploymentName": "gpt-4o",
    "ApiVersion": "2024-08-01-preview"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.Agents": "Debug"
    }
  }
}
⚠️
SECURITY NOTE

Never commit API keys to source control. Use Azure Key Vault, environment variables, or Managed Identity for production deployments.

Understanding Agent Providers

Microsoft Agent Framework supports multiple providers. Choose the right one for your scenario:

ProviderUse CasePackage
AzureOpenAIClientEnterprise deployments with Azure securityMicrosoft.Agents.AI.OpenAI
OpenAIClientDirect OpenAI API accessMicrosoft.Agents.AI.OpenAI
Azure AI FoundryManaged agent hosting & scalingMicrosoft.Agents.AI.AzureAI

Creating Your First Agent

Let’s start with a simple agent before adding complexity:

using System;
using Azure.Identity;
using OpenAI;
using Microsoft.Agents.AI;

namespace MAF.CustomerSupport;

class Program
{
    static async Task Main(string[] args)
    {
        // Configure Azure OpenAI client with DefaultAzureCredential
        var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") 
            ?? "https://your-resource.openai.azure.com";
        
        var client = new AzureOpenAIClient(
            new Uri(endpoint),
            new DefaultAzureCredential());
        
        // Create the AI agent
        var agent = client
            .GetOpenAIResponseClient("gpt-4o")
            .CreateAIAgent(
                name: "CustomerSupportBot",
                instructions: """
                    You are a helpful customer support agent for TechCorp.
                    - Always be polite and professional
                    - If you don't know something, say so honestly
                    - For account issues, ask for the customer's email
                    - For billing issues, ask for the order number
                    """);
        
        Console.WriteLine("🤖 Customer Support Agent Ready!");
        Console.WriteLine("Type your question (or 'exit' to quit):\n");
        
        while (true)
        {
            Console.Write("You: ");
            var input = Console.ReadLine();
            
            if (string.IsNullOrEmpty(input) || input.ToLower() == "exit")
                break;
            
            // Run the agent
            var result = await agent.RunAsync(input);
            Console.WriteLine($"\nAgent: {result}\n");
        }
    }
}

Adding Tools to Your Agent

The real power of agents comes from tools — functions they can call to perform real actions. Let’s add some customer support tools:

using System.ComponentModel;
using Microsoft.Agents.AI;

namespace MAF.CustomerSupport.Tools;

public class CustomerSupportTools
{
    private static readonly Dictionary _orders = new()
    {
        ["ORD-12345"] = new("ORD-12345", "john@example.com", "Shipped", 149.99m),
        ["ORD-67890"] = new("ORD-67890", "jane@example.com", "Processing", 299.50m),
    };
    
    private static readonly Dictionary _customers = new()
    {
        ["john@example.com"] = new("john@example.com", "John Doe", "Premium"),
        ["jane@example.com"] = new("jane@example.com", "Jane Smith", "Standard"),
    };

    [Description("Look up an order by its order number. Returns order status and details.")]
    public static string LookupOrder(
        [Description("The order number, e.g., ORD-12345")] string orderNumber)
    {
        if (_orders.TryGetValue(orderNumber.ToUpper(), out var order))
        {
            return $"""
                Order Found:
                - Order Number: {order.OrderNumber}
                - Customer: {order.CustomerEmail}
                - Status: {order.Status}
                - Amount: ${order.Amount:F2}
                """;
        }
        return $"Order {orderNumber} not found. Please verify the order number.";
    }
    
    [Description("Look up a customer by their email address.")]
    public static string LookupCustomer(
        [Description("Customer email address")] string email)
    {
        if (_customers.TryGetValue(email.ToLower(), out var customer))
        {
            return $"""
                Customer Found:
                - Name: {customer.Name}
                - Email: {customer.Email}
                - Tier: {customer.Tier}
                """;
        }
        return $"Customer with email {email} not found.";
    }
    
    [Description("Submit a support ticket for issues requiring escalation.")]
    public static string CreateSupportTicket(
        [Description("Customer's email address")] string customerEmail,
        [Description("Brief description of the issue")] string issueDescription,
        [Description("Priority: Low, Medium, High, or Critical")] string priority = "Medium")
    {
        var ticketId = $"TKT-{DateTime.Now:yyyyMMddHHmmss}";
        
        // In a real scenario, this would create a ticket in your ticketing system
        return $"""
            Support Ticket Created:
            - Ticket ID: {ticketId}
            - Customer: {customerEmail}
            - Priority: {priority}
            - Description: {issueDescription}
            
            A support specialist will contact the customer within 24 hours.
            """;
    }
    
    [Description("Get frequently asked questions and their answers.")]
    public static string GetFAQ(
        [Description("Topic to search for: shipping, returns, payments, account")] string topic)
    {
        var faqs = new Dictionary
        {
            ["shipping"] = "Standard shipping takes 5-7 business days. Express shipping takes 2-3 business days.",
            ["returns"] = "Returns are accepted within 30 days of delivery. Items must be in original condition.",
            ["payments"] = "We accept Visa, Mastercard, American Express, and PayPal.",
            ["account"] = "To reset your password, click 'Forgot Password' on the login page."
        };
        
        if (faqs.TryGetValue(topic.ToLower(), out var answer))
            return $"FAQ for '{topic}': {answer}";
            
        return $"No FAQ found for topic '{topic}'. Available topics: shipping, returns, payments, account";
    }
}

// Data records
public record OrderInfo(string OrderNumber, string CustomerEmail, string Status, decimal Amount);
public record CustomerInfo(string Email, string Name, string Tier);

Registering Tools with the Agent

Now update your agent to use these tools:

using MAF.CustomerSupport.Tools;

// Create the AI agent WITH tools
var agent = client
    .GetOpenAIResponseClient("gpt-4o")
    .CreateAIAgent(
        name: "CustomerSupportBot",
        instructions: """
            You are a helpful customer support agent for TechCorp.
            
            You have access to the following tools:
            - LookupOrder: Check order status
            - LookupCustomer: Find customer information
            - CreateSupportTicket: Escalate issues
            - GetFAQ: Answer common questions
            
            Always use tools when appropriate to provide accurate information.
            """,
        tools: [
            CustomerSupportTools.LookupOrder,
            CustomerSupportTools.LookupCustomer,
            CustomerSupportTools.CreateSupportTicket,
            CustomerSupportTools.GetFAQ
        ]);
💡
TIP

Notice how we use [Description] attributes on methods and parameters. The Agent Framework automatically generates JSON schemas from these, which the LLM uses to understand when and how to call your tools.

Streaming Responses

For a better user experience, especially with longer responses, use streaming:

// Stream the response for real-time output
Console.Write("\nAgent: ");
await foreach (var update in agent.RunStreamAsync(input))
{
    if (update.Text != null)
    {
        Console.Write(update.Text);
    }
}
Console.WriteLine("\n");

Multi-Turn Conversations with Threads

Real customer support requires conversation context. Agent Framework makes this simple with Threads:

using Microsoft.Agents.AI;

// Create a thread for conversation persistence
var thread = agent.GetNewThread();

while (true)
{
    Console.Write("You: ");
    var input = Console.ReadLine();
    
    if (string.IsNullOrEmpty(input) || input.ToLower() == "exit")
        break;
    
    // Run with thread - maintains conversation history
    var result = await agent.RunAsync(input, thread);
    Console.WriteLine($"\nAgent: {result}\n");
}

// The thread maintains full conversation history
// Each message references previous context

Adding Middleware for Logging and Error Handling

Production agents need proper observability. Here’s how to add middleware:

using Microsoft.Extensions.Logging;
using Microsoft.Agents.AI.Middleware;

public class LoggingMiddleware : IAgentMiddleware
{
    private readonly ILogger _logger;
    
    public LoggingMiddleware(ILogger logger)
    {
        _logger = logger;
    }
    
    public async Task InvokeAsync(
        AgentContext context, 
        AgentMiddlewareDelegate next)
    {
        var stopwatch = Stopwatch.StartNew();
        
        _logger.LogInformation(
            "Agent {AgentName} processing message: {Message}",
            context.Agent.Name,
            context.Messages.Last().Content?.ToString()?[..Math.Min(100, context.Messages.Last().Content?.ToString()?.Length ?? 0)]);
        
        try
        {
            var result = await next(context);
            
            stopwatch.Stop();
            _logger.LogInformation(
                "Agent {AgentName} completed in {ElapsedMs}ms. Tools called: {ToolCount}",
                context.Agent.Name,
                stopwatch.ElapsedMilliseconds,
                result.ToolCalls?.Count ?? 0);
            
            return result;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, 
                "Agent {AgentName} failed after {ElapsedMs}ms",
                context.Agent.Name,
                stopwatch.ElapsedMilliseconds);
            throw;
        }
    }
}

Complete Production-Ready Example

Here’s the full implementation bringing everything together:

using System;
using System.Diagnostics;
using Azure.Identity;
using OpenAI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Agents.AI;
using MAF.CustomerSupport.Tools;

namespace MAF.CustomerSupport;

class Program
{
    static async Task Main(string[] args)
    {
        // Setup dependency injection
        var services = new ServiceCollection();
        services.AddLogging(builder => 
        {
            builder.AddConsole();
            builder.SetMinimumLevel(LogLevel.Information);
        });
        
        var serviceProvider = services.BuildServiceProvider();
        var logger = serviceProvider.GetRequiredService<ILogger<Program>>();
        
        try
        {
            // Configure Azure OpenAI
            var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
                ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT not set");
            
            var client = new AzureOpenAIClient(
                new Uri(endpoint),
                new DefaultAzureCredential());
            
            // Create the agent with tools
            var agent = client
                .GetOpenAIResponseClient("gpt-4o")
                .CreateAIAgent(
                    name: "CustomerSupportBot",
                    instructions: """
                        You are a helpful customer support agent for TechCorp.
                        
                        Guidelines:
                        - Always be polite and professional
                        - Use tools to look up real information
                        - For complex issues, create support tickets
                        - Provide clear, actionable responses
                        
                        Available tools:
                        - LookupOrder: Check order status by order number
                        - LookupCustomer: Find customer by email
                        - CreateSupportTicket: Escalate issues requiring human attention
                        - GetFAQ: Answer common questions about shipping, returns, payments, account
                        """,
                    tools: new object[]
                    {
                        CustomerSupportTools.LookupOrder,
                        CustomerSupportTools.LookupCustomer,
                        CustomerSupportTools.CreateSupportTicket,
                        CustomerSupportTools.GetFAQ
                    });
            
            // Create thread for multi-turn conversation
            var thread = agent.GetNewThread();
            
            Console.WriteLine("═══════════════════════════════════════════════════════════");
            Console.WriteLine("  🤖 TechCorp Customer Support Agent");
            Console.WriteLine("═══════════════════════════════════════════════════════════");
            Console.WriteLine("  Type your question or 'exit' to quit.\n");
            
            while (true)
            {
                Console.Write("You: ");
                var input = Console.ReadLine();
                
                if (string.IsNullOrWhiteSpace(input))
                    continue;
                    
                if (input.Equals("exit", StringComparison.OrdinalIgnoreCase))
                    break;
                
                try
                {
                    var stopwatch = Stopwatch.StartNew();
                    
                    Console.Write("\nAgent: ");
                    await foreach (var update in agent.RunStreamAsync(input, thread))
                    {
                        if (update.Text != null)
                        {
                            Console.Write(update.Text);
                        }
                    }
                    
                    stopwatch.Stop();
                    Console.WriteLine($"\n\n[Response time: {stopwatch.ElapsedMilliseconds}ms]\n");
                }
                catch (Exception ex)
                {
                    logger.LogError(ex, "Error processing request");
                    Console.WriteLine($"\n❌ An error occurred: {ex.Message}\n");
                }
            }
            
            Console.WriteLine("\nThank you for using TechCorp Support. Goodbye!");
        }
        catch (Exception ex)
        {
            logger.LogCritical(ex, "Application startup failed");
            throw;
        }
    }
}

Testing Your Agent

Try these sample conversations to see your agent in action:

You: What's the status of order ORD-12345?
Agent: [Uses LookupOrder tool] Your order ORD-12345 is currently Shipped...

You: What shipping options do you have?
Agent: [Uses GetFAQ tool] We offer Standard shipping (5-7 days) and Express shipping (2-3 days)...

You: I need to report a damaged item
Agent: I'm sorry to hear that. I can help you with this. Could you please provide your email address so I can look up your account?

You: john@example.com
Agent: [Uses LookupCustomer, then CreateSupportTicket] I've found your account, John. I've created support ticket TKT-20251008123456...

Best Practices Summary

AreaRecommendation
AuthenticationUse DefaultAzureCredential for flexible auth across environments
ConfigurationStore settings in appsettings.json, secrets in Key Vault
Error HandlingWrap agent calls in try-catch, provide user-friendly error messages
LoggingUse structured logging with correlation IDs
ToolsUse descriptive attributes; keep tools focused and atomic
ThreadsUse threads for multi-turn; consider persistence for production
StreamingUse RunStreamAsync for better perceived performance

What’s Next

In Part 3, we’ll build the same customer support agent in Python, exploring async patterns, decorators, and Python-specific best practices.

In Part 4, we’ll dive deep into Tools & Function Calling — including MCP integration, external API connections, and advanced tool patterns.


📦 Source Code

All code examples from this article series are available on GitHub:

👉 https://github.com/nithinmohantk/microsoft-agent-framework-series-examples

Clone the repository to follow along:

git clone https://github.com/nithinmohantk/microsoft-agent-framework-series-examples.git
cd microsoft-agent-framework-series-examples

Series Navigation

  • Part 1: Introduction to Microsoft Agent Framework
  • Part 2: Building Your First AI Agent with MAF (.NET) ← You are here
  • Part 3: Building Your First AI Agent with MAF (Python) — Coming soon
  • Part 4: Tools & Function Calling Deep Dive — Coming soon

References


Discover more from C4: Container, Code, Cloud & Context

Subscribe to get the latest posts sent to your email.

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.