Agent Middleware
Middleware intercepts agent interactions at various stages of execution without modifying the core agent or function logic. It is the right place for cross-cutting concerns such as logging, security validation, error handling, input sanitisation, and result transformation.
Three Types of Middleware
| Type | What it intercepts | Registration point |
|---|---|---|
| Agent Run Middleware | Every RunAsync() call - full input message list and response. | agent.AsBuilder().Use(runFunc: ...) |
| Agent Run Streaming Middleware | Every RunStreamingAsync() call - streamed response updates. | agent.AsBuilder().Use(runStreamingFunc: ...) |
| Function Calling Middleware | Every function/tool call executed by the agent during a run. | agent.AsBuilder().Use(functionCallFunc: ...) |
| IChatClient Middleware | Every raw inference call to the underlying IChatClient. | chatClient.AsBuilder().Use(getResponseFunc: ...) |
When multiple middleware instances of the same type are registered they form a chain. Each middleware must call the next delegate to pass control to the next in the chain (and ultimately to the agent or chat client itself).
Agent Run Middleware
Inspect or modify the input messages before the agent runs and inspect or transform the AgentResponse after it returns.
Agent Run Streaming Middleware
Same idea for streaming. Collect updates as they arrive, then inspect the assembled response after all updates have been yielded.
Function Calling Middleware
Intercept each tool/function call the agent makes during a run. Inspect the function name and arguments, modify the result, or set context.Terminate = true to stop the function calling loop early.
Warning: Setting context.Terminate = true stops the function call loop but may leave the chat history in an inconsistent state (function call content with no corresponding result content). Only terminate when you are certain the session will not be used for further runs.
Note: Function calling middleware is only supported for agents that use FunctionInvokingChatClient (e.g. ChatClientAgent).
IChatClient Middleware
Intercept the raw request sent to the inference service and the raw ChatResponse that comes back. This sits below the agent layer and runs on every inference call regardless of session or function calling state.
Registering Middleware
Agent Run and Function Calling Middleware
Use AsBuilder() on an existing agent, chain Use() calls, then call Build() to get a new middleware-enabled agent. Ideally provide both runFunc and runStreamingFunc together so streaming runs are intercepted correctly.
IChatClient Middleware - via builder before agent creation
IChatClient Middleware - via factory helper
Summary
| Middleware type | Signature shape | Key parameters |
|---|---|---|
| Agent Run | Task<AgentResponse> (messages, session, options, innerAgent, ct) | Full input + full output |
| Agent Run Streaming | IAsyncEnumerable<AgentResponseUpdate> (messages, session, options, innerAgent, ct) | Full input + streamed updates |
| Function Calling | ValueTask<object?> (agent, context, next, ct) | FunctionInvocationContext with function name, args, Terminate flag |
| IChatClient | Task<ChatResponse> (messages, options, innerChatClient, ct) | Raw LLM input and output |
Required Packages
| Package | Purpose |
|---|---|
Microsoft.Agents.AI | AIAgent, AgentResponse, FunctionInvocationContext, builder APIs |
Microsoft.Extensions.AI | IChatClient, ChatResponse, ChatClientBuilder |
Microsoft.Extensions.AI.OpenAI | AsIChatClient() and OpenAI integration |