DEV Community

Cover image for Getting Started with Microsoft Agent Framework
Venya Brodetskiy
Venya Brodetskiy

Posted on • Originally published at Medium

Getting Started with Microsoft Agent Framework

Originally published on Medium on December 27, 2025.

If you’re building agents in C#/.NET in 2025, the hardest part often isn’t writing prompts — it’s picking a foundation. Should you use Semantic Kernel? AutoGen? The OpenAI / Azure OpenAI SDKs directly? And if you’re in Python, the menu gets even longer.

Microsoft created Microsoft Agent Framework (MAF) to reduce that “framework roulette”. The idea (based on Microsoft’s own announcement posts) is to unify two things developers kept having to choose between:

  • Semantic Kernel’s enterprise-friendly SDK approach (connectors, platform integration, a stable developer surface)
  • AutoGen-style multi-agent orchestration patterns (coordination, handoffs, richer agent-to-agent flows)

In other words: one open-source SDK + runtime that’s meant to carry you from local experiments to production systems, without rewriting your agent when you move from “demo” to “deployment”.

If you want Microsoft’s official framing and roadmap language, read these two posts:

- Semantic Kernel team perspective (including SK support expectations)

- Foundry announcement (the “why now” + pillars)

In this guide, we’ll focus on the basics through runnable demos from my repo: agents that call functions, produce structured output, persist threads, integrate with Azure AI Foundry, and use RAG.

What is Agent Framework?

Agent Framework is an open-source SDK and runtime for building AI agents and orchestrating them into workflows. Today it’s in public preview, and Microsoft positions it as the successor to Semantic Kernel for agent development — effectively “Semantic Kernel v2”.

That doesn’t mean Semantic Kernel disappears overnight. Microsoft’s stated intent is to keep supporting Semantic Kernel v1.x for the foreseeable future (critical bug fixes and security fixes, plus some features reaching GA), while most new investment goes into Agent Framework.

The framework focuses on two core capabilities:

1. AI Agents: Individual agents that use LLMs to process inputs, call tools, and generate responses

2. Workflows: Graph-based orchestration that connects multiple agents and functions to perform complex, multi-step tasks

Why build something new at all? The short version is: agents need more than prompt templates. MAF is designed around enterprise realities — durability for long-running agents, richer observability, better portability/interoperability, and a clearer path from local dev to managed hosting.

You can learn more in the official Microsoft Agent Framework documentation

Prerequisites

Before diving in, make sure you have:

  • .NET 10.0 SDK or later
  • Azure OpenAI account with:\ – API endpoint\ – API key\ – Model deployment (e.g., gpt-4.1, gpt-5.2)\ – Embedding model deployment (for RAG examples)

Most projects require an appsettings.json or appsettings.Development.json file with your Azure OpenAI configuration:

{
  "ModelName": "your-model-deployment-name",
  "Endpoint": "https://your-resource.openai.azure.com/",
  "ApiKey": "your-api-key",
  "EmbeddingModel": "your-embedding-model-name"
}
Enter fullscreen mode Exit fullscreen mode

⚠️ Note: Agent Framework is currently in public preview. APIs may evolve in future releases.

Core Concepts

Before we jump into examples, let’s establish the foundational concepts:

AIAgent: The core abstraction representing an AI agent. It encapsulates the LLM, instructions, and tools the agent can use.

Tools/Functions: Actions your agent can perform — from simple C# methods to complex API calls. Agent Framework uses AIFunctionFactory to convert methods into tools the LLM can call. For a deep dive on function calling, check out OpenAI’s function calling guide.

AgentThread: Manages conversation context and history. Think of it as the stateful container for your agent’s interactions with users.

With these building blocks in mind, let’s see them in action.

Example 1: Simple Agent with Function Calling

Let’s start with the fundamentals: creating an agent that can call tools.

// Define a function the agent can call
[Description("Get the weather for a given location.")]
static string GetWeather([Description("The location to get the weather for.")] string location)
    => $"The weather in {location} is cloudy with a high of 15°C.";

// Convert the function to a tool
var weatherFunction = AIFunctionFactory.Create(GetWeather);
// Create the agent
var agent = new AzureOpenAIClient(
        new Uri(endpoint),
        new AzureKeyCredential(apiKey))
    .GetChatClient(modelName)
    .CreateAIAgent(
        instructions: "say 'just a second' before answering question",
        tools: [weatherFunction],
        name: "myagent");
// Create a thread for conversation
var thread = agent.GetNewThread();
// Run the agent with streaming
var streamingResponse = agent.RunStreamingAsync(userInput, thread);
await foreach (var chunk in streamingResponse)
{
    Console.Write(chunk);
}
Enter fullscreen mode Exit fullscreen mode

What’s happening here?

  1. Function Definition: We define a simple C# method with [Description] attributes. These descriptions are crucial — they’re sent to the LLM to help it understand when and how to use the tool.
  2. Tool Registration: AIFunctionFactory.Create() uses reflection to analyze your method’s signature and attributes, then generates the tool schema the LLM expects. This schema includes parameter names, types, and descriptions.
  3. Agent Creation: CreateAIAgent() is an extension method that wraps the ChatClient and provides agent-specific capabilities. The instructions parameter sets the system prompt that guides the agent’s behavior.
  4. Thread Management: GetNewThread() creates a conversation context that maintains message history. Each thread is isolated, allowing you to manage multiple conversations independently.
  5. Streaming Execution: RunStreamingAsync() sends the user input to the LLM and streams back responses in chunks. This provides immediate feedback to users instead of waiting for the complete response.

Under the Hood: Function Calling Flow

When a user asks “What’s the weather in Paris?”, here’s what happens:

  1. The LLM receives the question and the available tool schemas
  2. It decides to call GetWeather with parameter location: "Paris"
  3. Agent Framework intercepts this, executes your C# method
  4. The result is sent back to the LLM as a “tool message”
  5. The LLM incorporates the result into its final response: “Just a second… The weather in Paris is cloudy with a high of 15°C.”

This orchestration happens automatically — you just define functions and the framework handles the rest.

Why This Matters

Compared to Semantic Kernel’s plugin registration ceremony, this is dramatically simpler:

// Semantic Kernel approach (more verbose)
var kernel = builder.Build();
kernel.ImportPluginFromFunctions("WeatherPlugin",
    new[] { KernelFunctionFactory.CreateFromMethod(GetWeather) });

// Agent Framework approach (concise)
var weatherFunction = AIFunctionFactory.Create(GetWeather);
var agent = chatClient.CreateAIAgent(tools: [weatherFunction]);
Enter fullscreen mode Exit fullscreen mode

You can find the complete implementation in the AgentFramework project.

Example 2: Structured Output

Structured output isn’t unique to Microsoft Agent Framework — it’s a core pattern you’ll want in any serious agent application.

The moment your agent needs to do something concrete (create a ticket, call an API, route a request), free-form text becomes a liability. You don’t want to regex an LLM response — you want a contract.

Why it matters:

  • Reliable downstream code (typed fields, not prose)
  • Less prompt wrangling (“valid JSON only” becomes the default)
  • Easier testing/evals (assert on fields)

There’s a second, underrated effect: a schema becomes a reasoning scaffold. Instead of producing a nice-sounding paragraph, the model has to commit to specific slots (category, entities, action items, etc.), which often makes behavior more decisive.

The tradeoff: if you force the model to fill fields when the input is missing info, it may hallucinate values just to satisfy the schema. Mitigate by designing for uncertainty (nullable fields, "unknown" values, or explicit instructions like “leave fields null if not stated”). OpenAI calls this out explicitly in their Structured Outputs guide: https://platform.openai.com/docs/guides/structured-outputs

MAF supports structured output directly: define a C# type describing the output you want, then call RunAsync<T>(). The framework generates a schema from your type, asks the model for a response that matches it, and deserializes the result back into T.

Here’s how to extract structured information from a meeting transcript:

// Define your output structure with nested types
[Description("Structured meeting information")]
public class MeetingAnalysis
{
    [JsonPropertyName("date")]
    public string? Date { get; set; }
    [JsonPropertyName("duration_minutes")]
    public int? DurationMinutes { get; set; }
    [JsonPropertyName("attendees")]
    public List<string>? Attendees { get; set; }
    [JsonPropertyName("decisions")]
    public List<string>? Decisions { get; set; }
    [JsonPropertyName("action_items")]
    public List<ActionItem>? ActionItems { get; set; }
}
public class ActionItem
{
    [JsonPropertyName("assignee")]
    public string? Assignee { get; set; }
    [JsonPropertyName("task")]
    public string? Task { get; set; }
    [JsonPropertyName("due_date")]
    public string? DueDate { get; set; }
}
// Create the agent
var agent = chatClient.CreateAIAgent(
    name: "MeetingAnalyzer",
    instructions: "You are an assistant that extracts structured information from meeting transcripts.");
// Run with structured output - notice the generic type parameter
var response = await agent.RunAsync<MeetingAnalysis>(
    $"Please analyze this meeting transcript and extract key information:\n\n{meetingTranscript}",
    thread
);
// Access type-safe properties - no parsing needed!
Console.WriteLine($"Meeting Date: {response.Result.Date}");
Console.WriteLine($"Duration: {response.Result.DurationMinutes} minutes");
Console.WriteLine($"Attendees: {string.Join(", ", response.Result.Attendees)}");
foreach (var item in response.Result.ActionItems)
{
    Console.WriteLine($"- {item.Assignee}: {item.Task} (due: {item.DueDate})");
}
Enter fullscreen mode Exit fullscreen mode

How It Works: JSON Schema Generation

When you call RunAsync<MeetingAnalysis>(), Agent Framework:

  1. Generates a JSON schema from your C# class using reflection
  2. Sends it to the LLM as part of the function calling specification
  3. Constrains the response to match your schema exactly
  4. Deserializes the JSON response into your strongly-typed object

The [Description] attributes on your class help the LLM understand what each field represents, improving extraction accuracy. The [JsonPropertyName] attributes control the JSON property names, which is especially useful when working with LLMs that expect specific naming conventions (like snake_case).

Check out the full example in the AgentFrameworkStructuredOutput project.

Example 3: Thread Persistence

In production scenarios, you often need to save and resume conversations. Agent Framework makes this straightforward with custom storage providers.

var agent = chatClient.CreateAIAgent(new ChatClientAgentOptions
{
    Name = "Assistant",
    ChatOptions = new() { Instructions = "You are a helpful assistant." },

    // This is the key: you plug in your own message store.
    // Agent Framework will call it to load/save chat history.
    ChatMessageStoreFactory = ctx => new FileChatMessageStore(ctx.SerializedState)
});

var threadStore = new FileThreadStore(
    storageDirectory: Path.Combine(Environment.CurrentDirectory, "ThreadStorage"));

AgentThread thread;
if (threadStore.Exists)
{
    // Restore the thread state via the agent (not via the thread itself)
    thread = threadStore.Load(serialized => agent.DeserializeThread(serialized));

    // Chat history is loaded from the message store (see below)
    await DisplayHistoricalMessagesAsync(thread);
}
else
{
    thread = agent.GetNewThread();
}

// After each user turn, save thread state
threadStore.Save(thread);
Enter fullscreen mode Exit fullscreen mode

How it works in this demo (two layers)

This project persists two things:

  1. Thread state (via thread.Serialize()): this is what lets you resume the same AgentThread object.
  2. Chat messages (via ChatMessageStore): this is what lets you show conversation history even after a restart.

Thread state is saved as JSON by FileThreadStore. Restoring is done by the agent:

  • Save: threadStore.Save(thread)
  • Load: threadStore.Load(serialized => agent.DeserializeThread(serialized))

The message store is intentionally small. In FileChatMessageStore, you mainly override 2–3 members:

  • AddMessagesAsync(...) to append and persist messages
  • GetMessagesAsync(...) to load messages
  • Serialize(...) to persist a stable store id into the thread state (this demo stores a generated thread id, then uses it as a filename)

If you want Redis/SQL/etc, you just need to create your own implementation of ChatMessageStore

Explore the complete implementation in the AgentFrameworkThreadPersistancy project.

Example 4: Azure AI Foundry Integration

Microsoft Foundry (old name: Azure AI Foundry) provides managed agent infrastructure in the cloud. Instead of managing agent lifecycle yourself, you can leverage Foundry’s persistent agents.

// Connect to Azure AI Foundry
var credential = new AzureCliCredential();
var client = new PersistentAgentsClient(
    new Uri(projectEndpoint),
    credential
);

// Create or retrieve a managed agent
var agent = await client.CreateAgentAsync(
    model: modelName,
    instructions: "You are a helpful assistant",
    tools: tools
);
// Use the agent like any other
var thread = await client.CreateThreadAsync();
var response = await client.RunAsync(agent.Id, thread.Id, userMessage);
Enter fullscreen mode Exit fullscreen mode

Why use Foundry?

  • Cloud-native persistence: Agents and threads are automatically stored
  • Lifecycle management: No need to handle agent state yourself
  • Azure integration: Seamless authentication and monitoring
  • Scale: Leverage Azure’s infrastructure for production workloads

For teams building production systems, Foundry removes significant operational overhead.

See the working example in the AgentFrameworkFoundryAgent project.

Example 5: RAG with Vector Search

Retrieval-Augmented Generation (RAG) enhances agents with external knowledge. In this demo, Agent Framework integrates RAG via TextSearchProvider as an AI context provider: the agent can trigger retrieval on demand, and the provider injects relevant snippets into the prompt.

Setting Up the Vector Store

First, define your document schema using vector store attributes:

private sealed class SearchRecord
{
    // Embedding dimension must match your model (e.g., text-embedding-3-large is 3072)
    private const int EmbeddingDimensions = 3072;
    [VectorStoreKey]
    public required string SourceId { get; init; }
    [VectorStoreData]
    public string? SourceName { get; init; }
    [VectorStoreData]
    public string? SourceLink { get; init; }
    [VectorStoreData(IsFullTextIndexed = true)]
    public string? Text { get; init; }
    [VectorStoreVector(EmbeddingDimensions)]
    public ReadOnlyMemory<float> TextEmbedding { get; init; }
}
Enter fullscreen mode Exit fullscreen mode

These attributes tell the vector store what’s searchable text, what’s metadata, and which field contains the embedding.

Creating and Populating the Knowledge Base

var azureOpenAiClient = new AzureOpenAIClient(
    new Uri(endpoint),
    ncew AzureKeyCredential(apiKey));

// In this demo, we use an in-memory vector store and Azure OpenAI embeddings.
var embeddingGenerator = azureOpenAiClient
    .GetEmbeddingClient(embeddingModel)
    .AsIEmbeddingGenerator();
VectorStore vectorStore = new InMemoryVectorStore(new InMemoryVectorStoreOptions
{
    EmbeddingGenerator = embeddingGenerator
});
// This helper creates the collection and uploads sample documents.
var knowledgeBase = await RagKnowledgeBase.CreateAsync(vectorStore, embeddingGenerator);
Enter fullscreen mode Exit fullscreen mode

Integrating RAG with Your Agent

Func<string, CancellationToken, Task<IEnumerable<TextSearchProvider.TextSearchResult>>> searchAdapter =
    knowledgeBase.SearchAsync;

TextSearchProviderOptions textSearchOptions = new()
{
    SearchTime = TextSearchProviderOptions.TextSearchBehavior.OnDemandFunctionCalling,
    RecentMessageMemoryLimit = 6,
};

var agent = azureOpenAiClient
    .GetChatClient(modelName)
    .CreateAIAgent(new ChatClientAgentOptions
    {
        Name = "myagent",
        ChatOptions = new ChatOptions
        {
            Instructions = "Say 'just a second' before answering."
        },

        // RAG is wired as context provider (not as a normal tool list).
        AIContextProviderFactory = ctx => new TextSearchProvider(
            searchAdapter,
            ctx.SerializedState,
            ctx.JsonSerializerOptions,
            textSearchOptions)
    });

var thread = agent.GetNewThread();
var response = await agent.RunAsync("What's your return policy?", thread);
Enter fullscreen mode Exit fullscreen mode

What Happens Under the Hood:

  1. User query arrives: “What’s your return policy?”
  2. The provider forms a search query (including a bit of recent chat history).
  3. The vector store returns the top matches (this demo uses top 3).
  4. The provider injects the retrieved text into the model’s context.
  5. The model answers grounded in that context.

Terminal demo of a Microsoft Agent Framework RAG agent answering a question about BrightTrail Gear's return policy.

In production you’d typically swap the in-memory store for a persistent vector backend, but the wiring stays the same.

Dive into the full RAG implementation in the AgentFrameworkRag project.

Bonus: Python Support

Agent Framework isn’t just for C# developers. Microsoft provides Python support, enabling cross-language agent development.

from azure.ai.agents import AIAgent
from azure.ai.openai import AzureOpenAIClient

# Create agent
client = AzureOpenAIClient(endpoint=endpoint, credential=credential)
agent = client.create_agent(
    model=model_name,
    instructions="You are a helpful assistant",
    tools=[weather_tool]
)
# Run the agent
thread = agent.create_thread()
response = agent.run(user_input, thread)
Enter fullscreen mode Exit fullscreen mode

The Python API mirrors the C# design, making it easy to work across languages or migrate existing Python projects to Agent Framework.

Check out the Python example in the AgentFrameworkPython directory.

What’s Next?

We’ve covered the fundamentals — individual agents with various capabilities. But what happens when you need to orchestrate multiple agents and deterministic logic into complex workflows?

That’s where Agent Framework’s workflow system comes in. Workflows let you build graph-based processes that combine LLM agents with business rules, conditional routing, and shared state management.

In the next article, we’ll explore how to build a real-world customer support email triage system that automatically classifies emails, applies business policies, and routes to either automated responses or human escalation.

In Conclusion

Microsoft Agent Framework represents a significant step forward in AI agent development. By learning from Semantic Kernel and AutoGen, it delivers a cleaner, more intuitive API that accelerates development without sacrificing power.

Whether you’re building simple chatbots or complex multi-agent systems, Agent Framework provides the building blocks you need — and as the successor to SK and AutoGen, it’s the future direction for Microsoft’s AI agent ecosystem.

🔗 Explore the complete demo repository: AgentFrameworkPlayground on GitHub

🤝 Your feedback is invaluable! Feel free to drop comments, ask questions, or share your insights and optimizations. Every contribution helps to enhance our collective knowledge and build a resourceful developer community.

Happy Coding! 🚀

Top comments (0)