Skip to content

Commit 56e0d11

Browse files
authored
Add middleware tutorial and more bug fixes (#706)
* Add middleware tutorial and more bug fixes * Add prerequisites section
1 parent 568f246 commit 56e0d11

4 files changed

Lines changed: 188 additions & 12 deletions

File tree

agent-framework/tutorials/agents/agent-as-mcp-tool.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ dotnet add package Azure.AI.OpenAI
2929
dotnet add package Microsoft.Agents.AI.OpenAI --prerelease
3030
```
3131

32-
To add support for hosting a tool over the Model Context Protocol (MCP), all add the following Nuget packages
32+
To add support for hosting a tool over the Model Context Protocol (MCP), add the following Nuget packages
3333

3434
```powershell
3535
dotnet add package Microsoft.Extensions.Hosting --prerelease

agent-framework/tutorials/agents/enable-observability.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,16 @@ ms.service: semantic-kernel
1616
This tutorial shows how to enable OpenTelemetry on an agent so that interactions with the agent are automatically logged and exported.
1717
In this tutorial, output is written to the console using the OpenTelemetry console exporter.
1818

19+
> [!NOTE]
20+
> See [Semantic Conventions for GenAI agent and framework spans](https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-agent-spans/) from Open Telemetry for more information about the standards followed by the Microsoft Agent Framework.
21+
1922
## Prerequisites
2023

2124
For prerequisites, see the [Create and run a simple agent](./run-agent.md) step in this tutorial.
2225

2326
## Installing Nuget packages
2427

25-
To use the AgentFramework with Azure OpenAI, you need to install the following NuGet packages:
28+
To use the Agent Framework with Azure OpenAI, you need to install the following NuGet packages:
2629

2730
```powershell
2831
dotnet add package Azure.Identity
@@ -39,8 +42,8 @@ dotnet add package OpenTelemetry.Exporter.Console
3942

4043
## Enable OpenTelemetry in your app
4144

42-
Enable the agent framework telemetry and create an OpenTelemetry TracerProvider that exports to the console.
43-
Note that the tracerProvider must remain alive while you run the agent so traces are exported.
45+
Enable the agent framework telemetry and create an OpenTelemetry `TracerProvider` that exports to the console.
46+
Note that the `TracerProvider` must remain alive while you run the agent so traces are exported.
4447

4548
```csharp
4649
using System;
@@ -56,7 +59,7 @@ using var tracerProvider = Sdk.CreateTracerProviderBuilder()
5659

5760
## Create and instrument the agent
5861

59-
Create an agent, then call `WithOpenTelemetry` to provide a source name.
62+
Create an agent, and using the builder pattern, call `UseOpenTelemetry` to provide a source name.
6063
Note that the string literal "agent-telemetry-source" is the OpenTelemetry source name
6164
that we used above, when we created the tracer provider.
6265

agent-framework/tutorials/agents/middleware.md

Lines changed: 168 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,174 @@ Learn how to add middleware to your agents in a few simple steps. Middleware all
1515

1616
::: zone pivot="programming-language-csharp"
1717

18-
Tutorial coming soon.
18+
## Prerequisites
19+
20+
For prerequisites and installing nuget packages, see the [Create and run a simple agent](./run-agent.md) step in this tutorial.
21+
22+
## Step 1: Create a Simple Agent
23+
24+
First, let's create a basic agent with a function tool.
25+
26+
```csharp
27+
using System;
28+
using Azure.AI.OpenAI;
29+
using Azure.Identity;
30+
using Microsoft.Agents.AI;
31+
using Microsoft.Extensions.AI;
32+
using OpenAI;
33+
34+
[Description("The current datetime offset.")]
35+
static string GetDateTime()
36+
=> DateTimeOffset.Now.ToString();
37+
38+
AIAgent baseAgent = new AzureOpenAIClient(
39+
new Uri("https://<myresource>.openai.azure.com"),
40+
new AzureCliCredential())
41+
.GetChatClient("gpt-4o-mini")
42+
.CreateAIAgent(
43+
instructions: "You are an AI assistant that helps people find information.",
44+
tools: [AIFunctionFactory.Create(GetDateTime, name: nameof(GetDateTime))]);
45+
```
46+
47+
## Step 2: Create Your Agent Run Middleware
48+
49+
Next, we'll create a function that will get invoked for each agent run.
50+
It allows us to inspect the input and output from the agent.
51+
52+
Unless the intention is to use the middleware to stop executing the run, the function
53+
should call `RunAsync` on the provided `innerAgent`.
54+
55+
This sample middleware just inspects the input and output from the agent run and
56+
outputs the number of messages passed into and out of the agent.
57+
58+
```csharp
59+
async Task<AgentRunResponse> CustomAgentRunMiddleware(
60+
IEnumerable<ChatMessage> messages,
61+
AgentThread? thread,
62+
AgentRunOptions? options,
63+
AIAgent innerAgent,
64+
CancellationToken cancellationToken)
65+
{
66+
Console.WriteLine(messages.Count());
67+
var response = await innerAgent.RunAsync(messages, thread, options, cancellationToken).ConfigureAwait(false);
68+
Console.WriteLine(response.Messages.Count);
69+
return response;
70+
}
71+
```
72+
73+
## Step 3: Add Agent Run Middleware to Your Agent
74+
75+
To add this middleware function to the `baseAgent` we created in step 1,
76+
we should use the builder pattern.
77+
This creates a new agent that has the middleware applied.
78+
The original `baseAgent` is not modified.
79+
80+
```csharp
81+
var middlewareEnabledAgent = baseAgent
82+
.AsBuilder()
83+
.Use(CustomAgentRunMiddleware)
84+
.Build();
85+
```
86+
87+
## Step 4: Create Function calling Middleware
88+
89+
> [!NOTE]
90+
> Function calling middleware is currently only supported with an `AIAgent` that uses `Microsoft.Extensions.AI.FunctionInvokingChatClient`, e.g. `ChatClientAgent`.
91+
92+
We can also create middleware that gets called for each function tool that is invoked.
93+
Here is an example of function calling middleware, that can inspect and/or modify the function being called, and the result from the function call.
94+
95+
Unless the intention is to use the middleware to not execute the function tool, the middleware
96+
should call the provided `next` `Func`.
97+
98+
```csharp
99+
async ValueTask<object?> CustomFunctionCallingMiddleware(
100+
AIAgent agent,
101+
FunctionInvocationContext context,
102+
Func<FunctionInvocationContext, CancellationToken, ValueTask<object?>> next,
103+
CancellationToken cancellationToken)
104+
{
105+
Console.WriteLine($"Function Name: {context!.Function.Name}");
106+
var result = await next(context, cancellationToken);
107+
Console.WriteLine($"Function Call Result: {result}");
108+
109+
return result;
110+
}
111+
```
112+
113+
## Step 5: Add Function calling Middleware to Your Agent
114+
115+
Same as with adding agent run middleware, we can add function calling middleware as follows:
116+
117+
```csharp
118+
var middlewareEnabledAgent = baseAgent
119+
.AsBuilder()
120+
.Use(CustomFunctionCallingMiddleware)
121+
.Build();
122+
```
123+
124+
Now, when executing the agent with a query that invokes a function, the middleware should get invoked,
125+
outputting the function name and call result.
126+
127+
```csharp
128+
await middlewareEnabledAgent.RunAsync("What's the current time?");
129+
```
130+
131+
## Step 6: Create Chat Client Middleware
132+
133+
For agents that are built using `IChatClient` developers may want to intercept calls going from the agent to the `IChatClient`.
134+
In this case it is possible to use middleware for the `IChatClient`.
135+
136+
Here is an example of chat client middleware, that can inspect and/or modify the input and output for the request to the inference service that the chat client provides.
137+
138+
```csharp
139+
async Task<ChatResponse> CustomChatClientMiddleware(
140+
IEnumerable<ChatMessage> messages,
141+
ChatOptions? options,
142+
IChatClient innerChatClient,
143+
CancellationToken cancellationToken)
144+
{
145+
Console.WriteLine(messages.Count());
146+
var response = await innerChatClient.GetResponseAsync(messages, options, cancellationToken);
147+
Console.WriteLine(response.Messages.Count);
148+
149+
return response;
150+
}
151+
```
152+
153+
> [!NOTE]
154+
> For more information about `IChatClient` middleware, see [Custom IChatClient middleware](/dotnet/ai/microsoft-extensions-ai#custom-ichatclient-middleware)
155+
> in the Microsoft.Extensions.AI documentation.
156+
157+
## Step 7: Add Chat client Middleware to an `IChatClient`
158+
159+
To add middleware to your `IChatClient`, you can use the builder pattern.
160+
After adding the middleware, you can use the `IChatClient` with your agent as usual.
161+
162+
```csharp
163+
var chatClient = new AzureOpenAIClient(new Uri("https://<myresource>.openai.azure.com"), new AzureCliCredential())
164+
.GetChatClient(deploymentName)
165+
.AsIChatClient();
166+
167+
var middlewareEnabledChatClient = chatClient
168+
.AsBuilder()
169+
.Use(getResponseFunc: CustomChatClientMiddleware, getStreamingResponseFunc: null)
170+
.Build();
171+
172+
var agent = new ChatClientAgent(middlewareEnabledChatClient, instructions: "You are a helpful assistant.");
173+
```
174+
175+
`IChatClient` middleware can also be registered using a factory method when constructing
176+
an agent via one of the helper methods on SDK clients.
177+
178+
```csharp
179+
var agent = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
180+
.GetChatClient(deploymentName)
181+
.CreateAIAgent("You are a helpful assistant.", clientFactory: (chatClient) => chatClient
182+
.AsBuilder()
183+
.Use(getResponseFunc: CustomChatClientMiddleware, getStreamingResponseFunc: null)
184+
.Build());
185+
```
19186

20187
::: zone-end
21188
::: zone pivot="programming-language-python"

agent-framework/user-guide/agents/agent-middleware.md

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ var agent = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
5757
.GetChatClient(deploymentName)
5858
.CreateAIAgent("You are a helpful assistant.", clientFactory: (chatClient) => chatClient
5959
.AsBuilder()
60-
.Use(getResponseFunc: ChatClientMiddleware, getStreamingResponseFunc: null)
60+
.Use(getResponseFunc: CustomChatClientMiddleware, getStreamingResponseFunc: null)
6161
.Build());
6262
```
6363

@@ -66,14 +66,16 @@ var agent = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
6666
Here is an example of agent run middleware, that can inspect and/or modify the input and output from the agent run.
6767

6868
```csharp
69-
async Task<AgentRunResponse> PIIMiddleware(IEnumerable<ChatMessage> messages, AgentThread? thread, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
69+
async Task<AgentRunResponse> CustomAgentRunMiddleware(
70+
IEnumerable<ChatMessage> messages,
71+
AgentThread? thread,
72+
AgentRunOptions? options,
73+
AIAgent innerAgent,
74+
CancellationToken cancellationToken)
7075
{
7176
Console.WriteLine(messages.Count());
72-
7377
var response = await innerAgent.RunAsync(messages, thread, options, cancellationToken).ConfigureAwait(false);
74-
7578
Console.WriteLine(response.Messages.Count);
76-
7779
return response;
7880
}
7981
```
@@ -113,7 +115,11 @@ If there were more than one function available for invocation during this iterat
113115
Here is an example of chat client middleware, that can inspect and/or modify the input and output for the request to the inference service that the chat client provides.
114116

115117
```csharp
116-
async Task<ChatResponse> CustomChatClientMiddleware(IEnumerable<ChatMessage> messages, ChatOptions? options, IChatClient innerChatClient, CancellationToken cancellationToken)
118+
async Task<ChatResponse> CustomChatClientMiddleware(
119+
IEnumerable<ChatMessage> messages,
120+
ChatOptions? options,
121+
IChatClient innerChatClient,
122+
CancellationToken cancellationToken)
117123
{
118124
Console.WriteLine(messages.Count());
119125
var response = await innerChatClient.GetResponseAsync(messages, options, cancellationToken);

0 commit comments

Comments
 (0)