mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
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:
committed by
Roger Barreto
Unverified
parent
b2f759366a
commit
535af00aa2
@@ -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;
|
||||
|
||||
-55
@@ -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()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user