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"
}
}
}
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:
| Provider | Use Case | Package |
|---|---|---|
| AzureOpenAIClient | Enterprise deployments with Azure security | Microsoft.Agents.AI.OpenAI |
| OpenAIClient | Direct OpenAI API access | Microsoft.Agents.AI.OpenAI |
| Azure AI Foundry | Managed agent hosting & scaling | Microsoft.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
]);
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
| Area | Recommendation |
|---|---|
| Authentication | Use DefaultAzureCredential for flexible auth across environments |
| Configuration | Store settings in appsettings.json, secrets in Key Vault |
| Error Handling | Wrap agent calls in try-catch, provide user-friendly error messages |
| Logging | Use structured logging with correlation IDs |
| Tools | Use descriptive attributes; keep tools focused and atomic |
| Threads | Use threads for multi-turn; consider persistence for production |
| Streaming | Use 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
- Microsoft Agent Framework GitHub
- MAF Quick Start Guide
- .NET Getting Started Samples
- Microsoft.Agents.AI.OpenAI NuGet Package
Discover more from C4: Container, Code, Cloud & Context
Subscribe to get the latest posts sent to your email.