diff --git a/dotnet/src/Microsoft.Agents.AI/OpenTelemetryAgent.cs b/dotnet/src/Microsoft.Agents.AI/OpenTelemetryAgent.cs index 567742102f..d51414769f 100644 --- a/dotnet/src/Microsoft.Agents.AI/OpenTelemetryAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI/OpenTelemetryAgent.cs @@ -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()?.UseProvidedChatClientAsIs is true) + { + return options; + } + // Capture the underlying IChatClient and check whether it is already instrumented. var chatClient = this.InnerAgent.GetService(); 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) { diff --git a/dotnet/src/Microsoft.Agents.AI/OpenTelemetryAgentBuilderExtensions.cs b/dotnet/src/Microsoft.Agents.AI/OpenTelemetryAgentBuilderExtensions.cs index 1ccf5b84a5..8f83a8dda1 100644 --- a/dotnet/src/Microsoft.Agents.AI/OpenTelemetryAgentBuilderExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI/OpenTelemetryAgentBuilderExtensions.cs @@ -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? configure = null) => - builder.UseOpenTelemetry(autoWireChatClient: true, sourceName: sourceName, configure: configure); - - /// - /// Adds OpenTelemetry instrumentation to the agent pipeline, with explicit control over whether the underlying - /// of a inner agent is automatically wrapped with - /// . - /// - /// The to which OpenTelemetry support will be added. - /// - /// When , the underlying of a inner agent - /// is automatically wrapped with 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 , no - /// auto-wiring is performed. Set to to opt out of this behavior. - /// - /// - /// 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. - /// - /// - /// An optional callback that provides additional configuration of the instance. - /// This allows for fine-tuning telemetry behavior such as enabling sensitive data collection. - /// - /// The with OpenTelemetry instrumentation added, enabling method chaining. - /// is . - public static AIAgentBuilder UseOpenTelemetry( - this AIAgentBuilder builder, - bool autoWireChatClient, - string? sourceName = null, - Action? 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; diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/OpenTelemetryAgentBuilderExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/OpenTelemetryAgentBuilderExtensionsTests.cs index d5a1720f6d..3bee00d014 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/OpenTelemetryAgentBuilderExtensionsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/OpenTelemetryAgentBuilderExtensionsTests.cs @@ -123,59 +123,4 @@ public class OpenTelemetryAgentBuilderExtensionsTests Assert.True(configureWasCalled); Assert.IsType(result); } - - /// - /// Verify that UseOpenTelemetry with autoWireChatClient parameter works correctly. - /// - [Theory] - [InlineData(true)] - [InlineData(false)] - public void UseOpenTelemetry_WithAutoWireChatClientFlag_ReturnsOpenTelemetryAgent(bool autoWireChatClient) - { - // Arrange - var mockAgent = new Mock(); - var builder = new AIAgentBuilder(mockAgent.Object); - - // Act - var result = builder.UseOpenTelemetry(autoWireChatClient: autoWireChatClient).Build(); - - // Assert - Assert.IsType(result); - } - - /// - /// Verify that UseOpenTelemetry with autoWireChatClient and all parameters works correctly. - /// - [Fact] - public void UseOpenTelemetry_WithAutoWireChatClientAndAllParameters_CallsConfigureAction() - { - // Arrange - var mockAgent = new Mock(); - 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(result); - } - - /// - /// Verify that UseOpenTelemetry with autoWireChatClient throws ArgumentNullException when builder is null. - /// - [Fact] - public void UseOpenTelemetry_WithAutoWireChatClient_WithNullBuilder_ThrowsArgumentNullException() - { - // Act & Assert - Assert.Throws("builder", () => ((AIAgentBuilder)null!).UseOpenTelemetry(autoWireChatClient: true)); - } } diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/OpenTelemetryAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/OpenTelemetryAgentTests.cs index 4730cc2a84..28e192bc35 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/OpenTelemetryAgentTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/OpenTelemetryAgentTests.cs @@ -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(); + 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() {