Skip to content

Commit 349ae77

Browse files
authored
Merge pull request #943 from MicrosoftDocs/main
Merge main to live
2 parents 0def571 + 958cf30 commit 349ae77

6 files changed

Lines changed: 254 additions & 25 deletions

File tree

agent-framework/agents/middleware/agent-vs-run-scope.md

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ When both are registered, agent-level middleware runs first (outermost), followe
2020

2121
:::zone pivot="programming-language-csharp"
2222

23-
In C#, middleware is registered on an agent using the builder pattern. Agent-level middleware is applied during agent construction, while run-level middleware can be provided via `AgentRunOptions`.
23+
In C#, middleware is registered on an agent using the builder pattern with `.AsBuilder().Use(...).Build()`. Agent-level middleware is applied during agent construction and persists across all runs. Run-level middleware uses the same pattern but builds a decorated agent inline before calling `RunAsync` or `RunStreamingAsync`.
2424

2525
### Agent-level middleware
2626

@@ -30,6 +30,7 @@ Agent-level middleware is registered at construction time and applies to every r
3030
using System;
3131
using System.Collections.Generic;
3232
using System.Linq;
33+
using System.Runtime.CompilerServices;
3334
using System.Threading;
3435
using System.Threading.Tasks;
3536
using Azure.AI.OpenAI;
@@ -50,6 +51,20 @@ async Task<AgentResponse> SecurityMiddleware(
5051
return response;
5152
}
5253

54+
async IAsyncEnumerable<AgentResponseUpdate> SecurityStreamingMiddleware(
55+
IEnumerable<ChatMessage> messages,
56+
AgentSession? session,
57+
AgentRunOptions? options,
58+
AIAgent innerAgent,
59+
[EnumeratorCancellation] CancellationToken cancellationToken)
60+
{
61+
Console.WriteLine("[Security] Validating streaming request...");
62+
await foreach (var update in innerAgent.RunStreamingAsync(messages, session, options, cancellationToken))
63+
{
64+
yield return update;
65+
}
66+
}
67+
5368
AIAgent baseAgent = new AzureOpenAIClient(
5469
new Uri("https://<myresource>.openai.azure.com"),
5570
new AzureCliCredential())
@@ -59,15 +74,15 @@ AIAgent baseAgent = new AzureOpenAIClient(
5974
// Register middleware at the agent level
6075
var agentWithMiddleware = baseAgent
6176
.AsBuilder()
62-
.Use(runFunc: SecurityMiddleware, runStreamingFunc: null)
77+
.Use(runFunc: SecurityMiddleware, runStreamingFunc: SecurityStreamingMiddleware)
6378
.Build();
6479

6580
Console.WriteLine(await agentWithMiddleware.RunAsync("What's the weather in Paris?"));
6681
```
6782

6883
### Run-level middleware
6984

70-
Run-level middleware is provided per request via `AgentRunOptions`:
85+
Run-level middleware uses the same builder pattern, applied inline for a specific invocation:
7186

7287
```csharp
7388
// Run-level middleware: applied to a specific run only
@@ -84,11 +99,31 @@ async Task<AgentResponse> DebugMiddleware(
8499
return response;
85100
}
86101

87-
// Pass run-level middleware via AgentRunOptions for this specific call
88-
var runOptions = new AgentRunOptions { RunMiddleware = DebugMiddleware };
89-
Console.WriteLine(await baseAgent.RunAsync("What's the weather in Tokyo?", options: runOptions));
102+
async IAsyncEnumerable<AgentResponseUpdate> DebugStreamingMiddleware(
103+
IEnumerable<ChatMessage> messages,
104+
AgentSession? session,
105+
AgentRunOptions? options,
106+
AIAgent innerAgent,
107+
[EnumeratorCancellation] CancellationToken cancellationToken)
108+
{
109+
Console.WriteLine($"[Debug] Input messages: {messages.Count()}");
110+
await foreach (var update in innerAgent.RunStreamingAsync(messages, session, options, cancellationToken))
111+
{
112+
yield return update;
113+
}
114+
}
115+
116+
// Apply run-level middleware by building a decorated agent inline for this specific call
117+
Console.WriteLine(await baseAgent
118+
.AsBuilder()
119+
.Use(runFunc: DebugMiddleware, runStreamingFunc: DebugStreamingMiddleware)
120+
.Build()
121+
.RunAsync("What's the weather in Tokyo?"));
90122
```
91123

124+
> [!TIP]
125+
> The `.AsBuilder().Use(...).Build()` pattern creates a lightweight wrapper around the original agent. You can chain multiple `.Use()` calls to compose several middleware for a single invocation.
126+
92127
:::zone-end
93128

94129
:::zone pivot="programming-language-python"

agent-framework/agents/observability.md

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ var instrumentedChatClient = new AzureOpenAIClient(new Uri(endpoint), new Defaul
3131
.GetChatClient(deploymentName)
3232
.AsIChatClient() // Converts a native OpenAI SDK ChatClient into a Microsoft.Extensions.AI.IChatClient
3333
.AsBuilder()
34-
.UseOpenTelemetry(sourceName: "MyApplication", configure: (cfg) => cfg.EnableSensitiveData = true) // Enable OpenTelemetry instrumentation with sensitive data
34+
.UseOpenTelemetry(sourceName: SourceName, configure: (cfg) => cfg.EnableSensitiveData = true) // Enable OpenTelemetry instrumentation with sensitive data
3535
.Build();
3636
```
3737

@@ -46,7 +46,7 @@ var agent = new ChatClientAgent(
4646
name: "OpenTelemetryDemoAgent",
4747
instructions: "You are a helpful assistant that provides concise and informative responses.",
4848
tools: [AIFunctionFactory.Create(GetWeatherAsync)]
49-
).WithOpenTelemetry(sourceName: "MyApplication", enableSensitiveData: true); // Enable OpenTelemetry instrumentation with sensitive data
49+
).WithOpenTelemetry(sourceName: SourceName, configure: (cfg) => cfg.EnableSensitiveData = true); // Enable OpenTelemetry instrumentation with sensitive data
5050
```
5151

5252
> [!IMPORTANT]
@@ -70,7 +70,9 @@ using OpenTelemetry.Trace;
7070
using OpenTelemetry.Resources;
7171
using System;
7272

73-
var SourceName = "MyApplication";
73+
// The source name under which all activities, metrics, and logs will be emitted.
74+
const string SourceName = "MyApplication";
75+
const string ServiceName = "AgentOpenTelemetry";
7476

7577
var applicationInsightsConnectionString = Environment.GetEnvironmentVariable("APPLICATION_INSIGHTS_CONNECTION_STRING")
7678
?? throw new InvalidOperationException("APPLICATION_INSIGHTS_CONNECTION_STRING is not set.");
@@ -82,12 +84,13 @@ var resourceBuilder = ResourceBuilder
8284
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
8385
.SetResourceBuilder(resourceBuilder)
8486
.AddSource(SourceName)
85-
.AddSource("*Microsoft.Extensions.AI") // Listen to the Experimental.Microsoft.Extensions.AI source for chat client telemetry.
86-
.AddSource("*Microsoft.Extensions.Agents*") // Listen to the Experimental.Microsoft.Extensions.Agents source for agent telemetry.
8787
.AddAzureMonitorTraceExporter(options => options.ConnectionString = applicationInsightsConnectionString)
8888
.Build();
8989
```
9090

91+
> [!TIP]
92+
> The `AddSource` method is used to specify the source name which the provider will listen to. Make sure it matches the source name you used in your instrumentation code (e.g., `UseOpenTelemetry(sourceName: SourceName)`). If a source name is not specified in the instrumentation code, it will default to `Experimental.Microsoft.Agents.AI`, in which case you should use `AddSource("Experimental.Microsoft.Agents.AI")` in your tracer provider and meter provider configuration.
93+
9194
> [!TIP]
9295
> Depending on your backend, you can use different exporters. For more information, see the [OpenTelemetry .NET documentation](https://opentelemetry.io/docs/instrumentation/net/exporters/). For local development, consider using the [Aspire Dashboard](#aspire-dashboard).
9396
@@ -112,7 +115,6 @@ var resourceBuilder = ResourceBuilder
112115
using var meterProvider = Sdk.CreateMeterProviderBuilder()
113116
.SetResourceBuilder(resourceBuilder)
114117
.AddSource(SourceName)
115-
.AddMeter("*Microsoft.Agents.AI") // Agent Framework metrics
116118
.AddAzureMonitorMetricExporter(options => options.ConnectionString = applicationInsightsConnectionString)
117119
.Build();
118120
```
@@ -154,8 +156,6 @@ Consider using the Aspire Dashboard as a quick way to visualize your traces and
154156
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
155157
.SetResourceBuilder(resourceBuilder)
156158
.AddSource(SourceName)
157-
.AddSource("*Microsoft.Extensions.AI") // Listen to the Experimental.Microsoft.Extensions.AI source for chat client telemetry.
158-
.AddSource("*Microsoft.Extensions.Agents*") // Listen to the Experimental.Microsoft.Extensions.Agents source for agent telemetry.
159159
.AddOtlpExporter(options => options.Endpoint = new Uri("http://localhost:4317"))
160160
.Build();
161161
```
@@ -174,23 +174,27 @@ See a full example of an agent with OpenTelemetry enabled in the [Agent Framewor
174174
## Dependencies
175175

176176
### Included packages
177+
177178
To enable observability in your Python application, the following OpenTelemetry packages are installed by default:
179+
178180
- [opentelemetry-api](https://pypi.org/project/opentelemetry-api/)
179181
- [opentelemetry-sdk](https://pypi.org/project/opentelemetry-sdk/)
180182
- [opentelemetry-semantic-conventions-ai](https://pypi.org/project/opentelemetry-semantic-conventions-ai/)
181183

182-
183184
### Exporters
185+
184186
We do *not* install exporters by default to prevent unnecessary dependencies and potential issues with auto instrumentation. There is a large variety of exporters available for different backends, so you can choose the ones that best fit your needs.
185187

186188
Some common exporters you may want to install based on your needs:
189+
187190
- For gRPC protocol support: install `opentelemetry-exporter-otlp-proto-grpc`
188191
- For HTTP protocol support: install `opentelemetry-exporter-otlp-proto-http`
189192
- For Azure Application Insights: install `azure-monitor-opentelemetry`
190193

191194
Use the [OpenTelemetry Registry](https://opentelemetry.io/ecosystem/registry/?language=python&component=instrumentation) to find more exporters and instrumentation packages.
192195

193196
## Enable Observability (Python)
197+
194198
### Five patterns for configuring observability
195199

196200
We've identified multiple ways to configure observability in your application, depending on your needs:
@@ -330,6 +334,7 @@ The following environment variables control Agent Framework observability:
330334
The `configure_otel_providers()` function automatically reads standard OpenTelemetry environment variables:
331335

332336
**OTLP Configuration** (for Aspire Dashboard, Jaeger, etc.):
337+
333338
- `OTEL_EXPORTER_OTLP_ENDPOINT` - Base endpoint for all signals (e.g., `http://localhost:4317`)
334339
- `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` - Traces-specific endpoint (overrides base)
335340
- `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT` - Metrics-specific endpoint (overrides base)
@@ -338,6 +343,7 @@ The `configure_otel_providers()` function automatically reads standard OpenTelem
338343
- `OTEL_EXPORTER_OTLP_HEADERS` - Headers for all signals (e.g., `key1=value1,key2=value2`)
339344

340345
**Service Identification**:
346+
341347
- `OTEL_SERVICE_NAME` - Service name (default: `agent_framework`)
342348
- `OTEL_SERVICE_VERSION` - Service version (default: package version)
343349
- `OTEL_RESOURCE_ATTRIBUTES` - Additional resource attributes
@@ -356,7 +362,8 @@ Make sure you have your Foundry configured with a Azure Monitor instance, see [d
356362
pip install azure-monitor-opentelemetry
357363
```
358364

359-
#### Configure observability directly from the `AzureAIClient`:
365+
#### Configure observability directly from the `AzureAIClient`
366+
360367
For Foundry projects, you can configure observability directly from the `AzureAIClient`:
361368

362369
```python
@@ -377,8 +384,8 @@ async def main():
377384
> [!TIP]
378385
> The arguments for `client.configure_azure_monitor()` are passed through to the underlying `configure_azure_monitor()` function from the `azure-monitor-opentelemetry` package, see [documentation](/python/api/overview/azure/monitor-opentelemetry-readme#usage) for details, we take care of setting the connection string and resource.
379386
387+
#### Configure azure monitor and optionally enable instrumentation
380388

381-
#### Configure azure monitor and optionally enable instrumentation:
382389
For non-Foundry projects with Application Insights, make sure you setup a custom agent in Foundry, see [details](/azure/ai-foundry/control-plane/register-custom-agent).
383390

384391
Then run your agent with the same _OpenTelemetry agent ID_ as registered in Foundry, and configure azure monitor as follows:

agent-framework/workflows/events.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ SuperStepCompletedEvent // Superstep completes
5252
RequestInfoEvent // A request is issued
5353
```
5454

55+
> [!NOTE]
56+
> When agents use approval-required tools, `RequestInfoEvent` typically carries a `ToolApprovalRequestContent` payload for tool calls that require human approval. See [Human-in-the-Loop](./human-in-the-loop.md) for details on handling these events.
57+
5558
::: zone-end
5659

5760
::: zone pivot="programming-language-python"
@@ -81,6 +84,9 @@ WorkflowEvent.type == "superstep_completed" # Superstep completes
8184
WorkflowEvent.type == "request_info" # A request is issued
8285
```
8386

87+
> [!NOTE]
88+
> When agents use approval-required tools, `request_info` events typically carry a `Content` payload with `type == "function_approval_request"` for tool calls that require human approval. See [Human-in-the-Loop](./human-in-the-loop.md) for details on handling these events.
89+
8490
::: zone-end
8591

8692
## Consuming Events

agent-framework/workflows/human-in-the-loop.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ ms.service: agent-framework
1717
| Overview | ✅ | ✅ | |
1818
| Enable Request and Response Handling | ✅ | ✅ | C# uses RequestPort; Python uses ctx.request_info |
1919
| Handling Requests and Responses | ✅ | ✅ | |
20+
| HITL with Agent Orchestrations | ✅ | ✅ | No zone pivots; links to orchestration docs |
2021
| Checkpoints and Requests | ✅ | ✅ | |
2122
| Next Steps | ✅ | ✅ | |
2223
-->
@@ -230,6 +231,19 @@ while pending_responses is not None:
230231
231232
::: zone-end
232233

234+
## Human-in-the-Loop with Agent Orchestrations
235+
236+
The `RequestPort` pattern described above works with custom executors and `WorkflowBuilder`. When using **agent orchestrations** (such as sequential, concurrent, or group chat workflows), **tool approval** is achieved through the human-in-the-loop request/response mechanism.
237+
238+
Agents can use tools that require human approval before execution. When the agent attempts to call an approval-required tool, the workflow pauses and emits a `RequestInfoEvent` just like the `RequestPort` pattern, but the event payload contains a `ToolApprovalRequestContent` (C#) or a `Content` with `type == "function_approval_request"` (Python) instead of a custom request type.
239+
240+
> [!TIP]
241+
> For complete examples with code, see:
242+
> - [Sequential orchestration with HITL](./orchestrations/sequential.md#sequential-orchestration-with-human-in-the-loop)
243+
> - [GroupChatToolApproval sample (C#)](https://github.com/microsoft/agent-framework/tree/main/dotnet/samples/03-workflows/Agents/GroupChatToolApproval)
244+
> - [Sequential tool approval sample (Python)](https://github.com/microsoft/agent-framework/blob/main/python/samples/03-workflows/tool-approval/sequential_builder_tool_approval.py)
245+
> - [Sequential request info sample (Python)](https://github.com/microsoft/agent-framework/blob/main/python/samples/03-workflows/human-in-the-loop/sequential_request_info.py)
246+
233247
## Checkpoints and Requests
234248

235249
To learn more about checkpoints, see [Checkpoints](./checkpoints.md).
@@ -238,6 +252,7 @@ When a checkpoint is created, pending requests are also saved as part of the che
238252

239253
## Next Steps
240254

255+
- [Learn about sequential orchestration with HITL](./orchestrations/sequential.md#sequential-orchestration-with-human-in-the-loop).
241256
- [Learn how to manage state](./state.md) in workflows.
242257
- [Learn how to create checkpoints and resume from them](./checkpoints.md).
243258
- [Learn how to monitor workflows](./observability.md).

agent-framework/workflows/orchestrations/index.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ Agent Framework provides several built-in multi-agent orchestration patterns:
2020
| [Group Chat](group-chat.md) | Agents collaborate in a shared conversation |
2121
| [Magentic](magentic.md) | A manager agent dynamically coordinates specialized agents |
2222

23+
> [!TIP]
24+
> Orchestrations support **human-in-the-loop** interactions through tool approval and request info. Agents can use approval-required tools that pause the workflow for human review before execution. See [Human-in-the-Loop](../human-in-the-loop.md) and the [sequential orchestration HITL tutorial](sequential.md#sequential-orchestration-with-human-in-the-loop) for details.
25+
2326
## Next steps
2427

2528
> [!div class="nextstepaction"]

0 commit comments

Comments
 (0)