Microsoft Agent Framework Middleware Created: 23 Feb 2026 Updated: 23 Feb 2026

Chat-Level Middleware

Chat-level middleware intercepts every call that travels from the agent to the underlying IChatClient (the LLM inference service). This is the lowest interception point in the pipeline - below sessions, conversation history management, and function calling logic.

Typical uses: detailed prompt logging, modifying messages before they reach the AI service, transforming responses, tracking token usage or cost.

How Chat-Level Middleware Works

PhaseWhat you can do
Before the LLM callInspect or modify the full message list and ChatOptions.
After the LLM callInspect or transform the raw ChatResponse before it is returned to the agent.

Defining the Middleware Function

A chat middleware function has a fixed signature. It receives the messages, the chat options, the inner IChatClient, and a cancellation token. It must call innerChatClient.GetResponseAsync() to forward the request (unless intentionally short-circuiting).

async Task<ChatResponse> LoggingChatMiddleware(
IEnumerable<ChatMessage> messages,
ChatOptions? options,
IChatClient innerChatClient,
CancellationToken cancellationToken)
{
// --- Before the LLM call ---
Console.WriteLine($"[ChatLog] Sending {messages.Count()} message(s) to model...");
foreach (ChatMessage msg in messages)
{
string preview = msg.Text?.Substring(0, Math.Min(msg.Text.Length, 80)) ?? "(no text)";
Console.WriteLine($"[ChatLog] {msg.Role}: {preview}");
}

ChatResponse response = await innerChatClient
.GetResponseAsync(messages, options, cancellationToken);

// --- After the LLM call ---
Console.WriteLine($"[ChatLog] Received {response.Messages.Count} response message(s).");

return response;
}

Registering the Middleware

Chat-level middleware is applied to the IChatClient using the builder pattern, before the agent is created. The resulting middleware-wrapped client is then passed to AsAIAgent().

using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI;

// Step 1: Build the raw IChatClient and wrap it with middleware.
IChatClient rawChatClient = new OpenAIClient("<your_api_key>")
.GetChatClient("gpt-4o-mini")
.AsIChatClient();

IChatClient middlewareClient = rawChatClient
.AsBuilder()
.Use(getResponseFunc: LoggingChatMiddleware,
getStreamingResponseFunc: null)
.Build();

// Step 2: Create the agent using the middleware-wrapped IChatClient.
AIAgent agent = middlewareClient
.AsAIAgent(
instructions: "You are a helpful assistant.",
name: "Assistant");

// Every RunAsync() call now flows through LoggingChatMiddleware.
AgentResponse response = await agent.RunAsync("Hello, how are you?");
Console.WriteLine(response.Text);

Full Example Output

Running agent.RunAsync("Hello, how are you?") with the logging middleware active produces output similar to:

[ChatLog] Sending 2 message(s) to model...
[ChatLog] system: You are a helpful assistant.
[ChatLog] user: Hello, how are you?
[ChatLog] Received 1 response message(s).

Agent: I'm doing well, thank you! How can I help you today?

Multiple Middleware in a Chain

Multiple chat-level middleware functions can be chained by calling Use() more than once. They execute in registration order:

IChatClient middlewareClient = rawChatClient
.AsBuilder()
.Use(getResponseFunc: LoggingChatMiddleware, getStreamingResponseFunc: null)
.Use(getResponseFunc: ValidationChatMiddleware, getStreamingResponseFunc: null)
.Build();

Each middleware must call the provided inner client (or be intentionally short-circuiting) to pass control down the chain.

Difference from Agent Run Middleware


Agent Run MiddlewareChat-Level Middleware
InterceptsOne call per RunAsync() invocationOne call per LLM inference request (may be multiple per run when function calling is active)
SeesUser-facing messages and full AgentResponseThe raw message list sent to the LLM including injected history and tool results
Registrationagent.AsBuilder().Use(...)chatClient.AsBuilder().Use(...) before agent creation

Summary

ConceptAPIDescription
Middleware function signatureTask<ChatResponse>(messages, options, innerChatClient, ct)Receives the full raw message list going to the LLM.
Register middlewarechatClient.AsBuilder().Use(getResponseFunc: ...).Build()Wraps the IChatClient with middleware before agent creation.
Forward requestinnerChatClient.GetResponseAsync(...)Must be called to continue the chain (unless short-circuiting).

Required Packages

PackagePurpose
Microsoft.Agents.AIAIAgent, AgentResponse
Microsoft.Extensions.AIIChatClient, ChatResponse, ChatMessage, ChatClientBuilder
Microsoft.Extensions.AI.OpenAIAsIChatClient() and OpenAI integration


Share this lesson: