Getting Started with Microsoft Agent Framework in C#
Getting started with microsoft agent framework c# requires only a few NuGet packages, a valid LLM provider key, and about twenty lines of code before you have a working AI agent. That's the whole point of MAF -- it removes the orchestration boilerplate and lets you focus on what the agent actually does. This tutorial walks you through every step: project setup, package installation, provider configuration, creating your first agent, and using both blocking and streaming response modes.
MAF is in public preview at version 1.0.0-rc1. Expect the API to be stable enough for development and pilot work, but account for possible changes before GA. The Microsoft.Agents.AI package is the entry point, and it builds directly on Microsoft.Extensions.AI -- the provider-agnostic chat client ecosystem that Microsoft introduced for .NET AI development.
Prerequisites
Before you start, make sure you have the following:
- .NET 10 SDK -- MAF targets modern .NET. Download it from the official .NET site if you don't have it installed.
- An LLM provider API key -- OpenAI or Azure OpenAI both work. The configuration pattern covered in this tutorial makes it easy to switch between them.
- A C# project -- a console app is perfect for getting started. Create one with
dotnet new console -n MafDemo.
For the dependency injection integration covered later in this article, familiarity with IServiceCollection is helpful. If you're new to DI in .NET, the complete guide to dependency injection in C# is a good primer.
Installing the Required NuGet Packages
MAF requires three packages for a standard OpenAI setup:
<PackageReference Include="Microsoft.Agents.AI" Version="1.0.0-rc1" />
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" Version="9.5.0-preview.*" />
<PackageReference Include="OpenAI" Version="2.*" />
Add these to your .csproj file, then run dotnet restore.
For Azure OpenAI, replace the OpenAI package with Azure.AI.OpenAI:
<PackageReference Include="Microsoft.Agents.AI" Version="1.0.0-rc1" />
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" Version="9.5.0-preview.*" />
<PackageReference Include="Azure.AI.OpenAI" Version="2.*" />
The Microsoft.Agents.AI package is the same in both cases. The only difference is which provider package you use to build the IChatClient underneath.
Configuring Your Provider with appsettings.json
Hardcoding API keys is a bad practice. The cleanest approach is to drive your provider setup from appsettings.json using IConfiguration. Add an appsettings.json file to your project with the following structure:
{
"AIProvider": {
"Type": "openai",
"ModelId": "gpt-4o-mini",
"ApiKey": "",
"Endpoint": ""
}
}
For Azure OpenAI, set "Type": "azureopenai", provide your Endpoint, and set ApiKey to your Azure API key. The ModelId should be your deployment name.
Set Copy to Output Directory to Copy if newer for the file in your project. Then read the configuration at startup:
using Microsoft.Extensions.Configuration;
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: false)
.AddEnvironmentVariables()
.Build();
var providerType = config["AIProvider:Type"] ?? "openai";
var modelId = config["AIProvider:ModelId"] ?? "gpt-4o-mini";
var apiKey = config["AIProvider:ApiKey"] ?? Environment.GetEnvironmentVariable("OPENAI_API_KEY")!;
var endpoint = config["AIProvider:Endpoint"];
Adding AddEnvironmentVariables() means you can override the JSON values at runtime -- useful in CI/CD and container environments where credentials are injected as environment variables. This pattern integrates well with the fluent DI configuration approach used across the .NET ecosystem.
Creating Your IChatClient
With configuration loaded, build an IChatClient based on the provider type:
using Microsoft.Extensions.AI;
using OpenAI;
IChatClient chatClient;
if (providerType.Equals("azureopenai", StringComparison.OrdinalIgnoreCase))
{
// Azure OpenAI path
chatClient = new Azure.AI.OpenAI.AzureOpenAIClient(
new Uri(endpoint!),
new Azure.AzureKeyCredential(apiKey))
.GetChatClient(modelId)
.AsIChatClient();
}
else
{
// OpenAI path
chatClient = new OpenAIClient(new ApiKeyCredential(apiKey))
.GetChatClient(modelId)
.AsIChatClient();
}
The AsIChatClient() extension method comes from Microsoft.Extensions.AI.OpenAI and converts the provider-specific chat client into the standard IChatClient interface. Everything above this line is provider-specific. Everything below is provider-agnostic MAF code.
Creating Your First Agent
With an IChatClient ready, creating an agent is one line:
using Microsoft.Agents.AI;
var agent = chatClient.AsAIAgent(
instructions: "You are a helpful C# coding assistant. Be concise and give code examples when relevant.");
The instructions parameter becomes the system prompt for every conversation this agent handles. It shapes the agent's persona, behavior constraints, and response style. Choose instructions that match your use case -- this is the primary way you customize agent behavior.
The AsAIAgent() extension returns an IAIAgent. Behind the scenes, it creates a ChatClientAgent that wraps your IChatClient with MAF's agent orchestration layer.
Running the Agent with RunAsync
RunAsync is the blocking (but still async) path. It sends a prompt, waits for the full response, and returns an AgentResponse:
AgentResponse response = await agent.RunAsync(
"Explain the difference between IEnumerable and IQueryable in C#");
Console.WriteLine(response.Text);
AgentResponse.Text gives you the full text of the model's reply.This is the right mode for scenarios where you need the complete response before doing something with it -- writing to a database, sending an email, or processing the output programmatically.
RunAsync is also where you pass an AgentSession for multi-turn conversations, which is covered in depth in the session management article. For single-turn calls like the example above, no session is needed.
Streaming Responses with RunStreamingAsync
For interactive applications -- chat UIs, console tools, API endpoints that stream back to clients -- RunStreamingAsync is the better choice. It returns IAsyncEnumerable<StreamingAgentResponse> and yields each token or chunk as the model generates it:
Console.Write("Agent: ");
await foreach (StreamingAgentResponse chunk in agent.RunStreamingAsync(
"Write a C# extension method that converts a string to title case"))
{
Console.Write(chunk.Text);
}
Console.WriteLine();
Each StreamingAgentResponse has a Text property containing the incremental content for that chunk. The await foreach loop processes each chunk as it arrives. For web applications, you'd forward these chunks to the client via server-sent events or WebSockets.
Streaming also works with tool-calling agents. MAF handles the tool invocation loop internally and streams the final text response once all tool calls are complete.
Putting It All Together: A Complete Console App
Here's a full console application that combines configuration loading, agent creation, and both response modes:
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
using OpenAI;
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: false)
.AddEnvironmentVariables()
.Build();
var modelId = config["AIProvider:ModelId"] ?? "gpt-4o-mini";
var apiKey = config["AIProvider:ApiKey"] ?? Environment.GetEnvironmentVariable("OPENAI_API_KEY")!;
IChatClient chatClient = new OpenAIClient(new ApiKeyCredential(apiKey))
.GetChatClient(modelId)
.AsIChatClient();
var agent = chatClient.AsAIAgent(
instructions: "You are a helpful C# coding assistant. Be concise.");
// Blocking call
AgentResponse response = await agent.RunAsync("What is a record type in C#?");
Console.WriteLine("=== Blocking Response ===");
Console.WriteLine(response.Text);
// Streaming call
Console.WriteLine("\n=== Streaming Response ===");
Console.Write("Agent: ");
await foreach (StreamingAgentResponse chunk in agent.RunStreamingAsync(
"Show me a minimal example of IAsyncEnumerable in C# 10"))
{
Console.Write(chunk.Text);
}
Console.WriteLine();
Run this with dotnet run. You should see a complete response followed by a streaming response that prints token-by-token.
Registering Your Agent with Dependency Injection
For production applications, you'll want to register the agent in the DI container rather than constructing it manually. Here's how to do that with Microsoft.Extensions.DependencyInjection:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
var host = Host.CreateDefaultBuilder(args)
.ConfigureServices((context, services) =>
{
var config = context.Configuration;
var apiKey = config["AIProvider:ApiKey"]!;
var modelId = config["AIProvider:ModelId"] ?? "gpt-4o-mini";
IChatClient chatClient = new OpenAIClient(new ApiKeyCredential(apiKey))
.GetChatClient(modelId)
.AsIChatClient();
var agent = chatClient.AsAIAgent(
instructions: "You are a helpful C# assistant.");
services.AddSingleton<IAIAgent>(agent);
})
.Build();
With the agent registered as IAIAgent, any service that needs it can declare a constructor parameter of type IAIAgent and receive it via injection. This approach works with the ASP.NET Core DI patterns you're already using.
The plugin architecture approach also pairs well here -- you could register different named agents for different use cases using keyed services.
What to Explore Next
With your first agent running, the natural next steps are:
- Multi-turn conversations -- pass an
AgentSessiontoRunAsyncto maintain context across prompts - Tool calling -- register functions via
AIFunctionFactory.Create()so the agent can fetch data or take actions - Core abstractions -- understand
IAIAgent,ChatClientAgent, and howAsAIAgent()works under the hood
Each of these topics builds directly on the setup you've just completed. If you're already exploring AI coding tools for development, MAF is the next logical step for building purpose-driven agents in your own applications.
FAQ
What is the minimum .NET version required for Microsoft Agent Framework?
Microsoft Agent Framework targets modern .NET. .NET 10 is the recommended version for new projects. The exact minimum supported TFM may shift before the GA release since MAF is in public preview (1.0.0-rc1).
Do I need Azure OpenAI to use Microsoft Agent Framework?
No. MAF works with any IChatClient implementation. OpenAI's public API, Azure OpenAI, and local models (via Ollama with the Microsoft.Extensions.AI.Ollama package) all work. You choose the provider when building the IChatClient -- MAF is fully provider-agnostic above that layer.
How do I handle API key security in a production application?
Never hardcode API keys in source code. Load them from environment variables, Azure Key Vault, AWS Secrets Manager, or a secrets management service appropriate for your hosting environment. The appsettings.json pattern shown in this article is designed to be overridden by environment variables at runtime, which is the right pattern for containers and cloud deployments.
Can I use multiple agents in the same application?
Yes. You can create multiple IAIAgent instances with different instructions, tools, and even different providers. Register each as a named or keyed service in the DI container. This is common in applications where different tasks (customer support, code generation, data analysis) warrant specialized agents.
What is the difference between RunAsync and RunStreamingAsync?
RunAsync waits for the full response and returns it as an AgentResponse. RunStreamingAsync yields chunks as the model generates them via IAsyncEnumerable<StreamingAgentResponse>. Use RunAsync when you need the complete response before proceeding. Use RunStreamingAsync for interactive UIs, console streaming, or server-sent events where latency-to-first-token matters.
Is Microsoft Agent Framework compatible with .NET dependency injection?
Yes. You register your IAIAgent instance in IServiceCollection and inject it wherever needed. MAF doesn't impose any specific DI pattern -- it integrates naturally with standard Microsoft.Extensions.DependencyInjection and any DI container compatible with the IServiceProvider abstraction.
How do I update the agent's instructions after creation?
The current MAF API sets instructions at agent creation time via AsAIAgent(instructions: "..."). If you need dynamic instructions, create a new agent instance for each distinct instruction set, or manage system prompt injection at the session level by prepending messages before passing the session. This may evolve as the API matures toward GA.
