Address review: remove extension overload; honor UseProvidedChatClientAsIs; drop redundant check

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/6ac3f75d-eeb7-4811-8043-9a27511b0a8b

Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-05-11 18:57:25 +00:00
committed by Roger Barreto
Unverified
parent b2f759366a
commit 535af00aa2
4 changed files with 29 additions and 90 deletions
@@ -197,6 +197,12 @@ public sealed class OpenTelemetryAgent : DelegatingAIAgent, IDisposable
return options;
}
// Respect ChatClientAgentOptions.UseProvidedChatClientAsIs: don't decorate the chat client when the user opted out.
if (this.InnerAgent.GetService<ChatClientAgentOptions>()?.UseProvidedChatClientAsIs is true)
{
return options;
}
// Capture the underlying IChatClient and check whether it is already instrumented.
var chatClient = this.InnerAgent.GetService<IChatClient>();
if (chatClient is null || chatClient.GetService(typeof(OpenTelemetryChatClient)) is not null)
@@ -206,9 +212,7 @@ public sealed class OpenTelemetryAgent : DelegatingAIAgent, IDisposable
string? sourceName = this._sourceName;
IChatClient WrapWithOpenTelemetry(IChatClient cc) =>
cc.GetService(typeof(OpenTelemetryChatClient)) is not null
? cc
: cc.AsBuilder().UseOpenTelemetry(sourceName: sourceName).Build();
cc.AsBuilder().UseOpenTelemetry(sourceName: sourceName).Build();
if (options is ChatClientAgentRunOptions ccOptions)
{
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI;
@@ -49,39 +48,9 @@ public static class OpenTelemetryAgentBuilderExtensions
this AIAgentBuilder builder,
string? sourceName = null,
Action<OpenTelemetryAgent>? configure = null) =>
builder.UseOpenTelemetry(autoWireChatClient: true, sourceName: sourceName, configure: configure);
/// <summary>
/// Adds OpenTelemetry instrumentation to the agent pipeline, with explicit control over whether the underlying
/// <see cref="IChatClient"/> of a <see cref="ChatClientAgent"/> inner agent is automatically wrapped with
/// <see cref="Microsoft.Extensions.AI.OpenTelemetryChatClient"/>.
/// </summary>
/// <param name="builder">The <see cref="AIAgentBuilder"/> to which OpenTelemetry support will be added.</param>
/// <param name="autoWireChatClient">
/// When <see langword="true"/>, the underlying <see cref="IChatClient"/> of a <see cref="ChatClientAgent"/> inner agent
/// is automatically wrapped with <see cref="Microsoft.Extensions.AI.OpenTelemetryChatClient"/> on each invocation so
/// that chat-level telemetry flows alongside agent-level telemetry. If the underlying chat client is already
/// instrumented, no additional wrapping is applied. If the inner agent is not a <see cref="ChatClientAgent"/>, no
/// auto-wiring is performed. Set to <see langword="false"/> to opt out of this behavior.
/// </param>
/// <param name="sourceName">
/// An optional source name that will be used to identify telemetry data from this agent.
/// If not specified, a default source name will be used.
/// </param>
/// <param name="configure">
/// An optional callback that provides additional configuration of the <see cref="OpenTelemetryAgent"/> instance.
/// This allows for fine-tuning telemetry behavior such as enabling sensitive data collection.
/// </param>
/// <returns>The <see cref="AIAgentBuilder"/> with OpenTelemetry instrumentation added, enabling method chaining.</returns>
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
public static AIAgentBuilder UseOpenTelemetry(
this AIAgentBuilder builder,
bool autoWireChatClient,
string? sourceName = null,
Action<OpenTelemetryAgent>? configure = null) =>
Throw.IfNull(builder).Use((innerAgent, services) =>
{
var agent = new OpenTelemetryAgent(innerAgent, sourceName, autoWireChatClient);
var agent = new OpenTelemetryAgent(innerAgent, sourceName);
configure?.Invoke(agent);
return agent;
@@ -123,59 +123,4 @@ public class OpenTelemetryAgentBuilderExtensionsTests
Assert.True(configureWasCalled);
Assert.IsType<OpenTelemetryAgent>(result);
}
/// <summary>
/// Verify that UseOpenTelemetry with autoWireChatClient parameter works correctly.
/// </summary>
[Theory]
[InlineData(true)]
[InlineData(false)]
public void UseOpenTelemetry_WithAutoWireChatClientFlag_ReturnsOpenTelemetryAgent(bool autoWireChatClient)
{
// Arrange
var mockAgent = new Mock<AIAgent>();
var builder = new AIAgentBuilder(mockAgent.Object);
// Act
var result = builder.UseOpenTelemetry(autoWireChatClient: autoWireChatClient).Build();
// Assert
Assert.IsType<OpenTelemetryAgent>(result);
}
/// <summary>
/// Verify that UseOpenTelemetry with autoWireChatClient and all parameters works correctly.
/// </summary>
[Fact]
public void UseOpenTelemetry_WithAutoWireChatClientAndAllParameters_CallsConfigureAction()
{
// Arrange
var mockAgent = new Mock<AIAgent>();
var builder = new AIAgentBuilder(mockAgent.Object);
var configureWasCalled = false;
// Act
var result = builder.UseOpenTelemetry(
autoWireChatClient: false,
sourceName: "TestSource",
configure: agent =>
{
configureWasCalled = true;
Assert.NotNull(agent);
}).Build();
// Assert
Assert.True(configureWasCalled);
Assert.IsType<OpenTelemetryAgent>(result);
}
/// <summary>
/// Verify that UseOpenTelemetry with autoWireChatClient throws ArgumentNullException when builder is null.
/// </summary>
[Fact]
public void UseOpenTelemetry_WithAutoWireChatClient_WithNullBuilder_ThrowsArgumentNullException()
{
// Act & Assert
Assert.Throws<ArgumentNullException>("builder", () => ((AIAgentBuilder)null!).UseOpenTelemetry(autoWireChatClient: true));
}
}
@@ -717,6 +717,27 @@ public class OpenTelemetryAgentTests
Assert.Null(observedOptions);
}
[Fact]
public async Task AutoWireChatClient_UseProvidedChatClientAsIs_DoesNotEmitChatSpan_Async()
{
var sourceName = Guid.NewGuid().ToString();
var activities = new List<Activity>();
using var tracerProvider = OpenTelemetry.Sdk.CreateTracerProviderBuilder()
.AddSource(sourceName)
.AddInMemoryExporter(activities)
.Build();
var fakeChatClient = new AutoWireTestChatClient();
var inner = new ChatClientAgent(fakeChatClient, new ChatClientAgentOptions { UseProvidedChatClientAsIs = true });
using var agent = new OpenTelemetryAgent(inner, sourceName);
_ = await agent.RunAsync("hi");
// UseProvidedChatClientAsIs opts out of auto-wiring, so only the invoke_agent span should be emitted.
var activity = Assert.Single(activities);
Assert.StartsWith("invoke_agent", activity.DisplayName);
}
[Fact]
public async Task AutoWireChatClient_AlreadyInstrumented_DoesNotDoubleWrap_Async()
{