mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
fix: propagate EnableSensitiveData to auto-wired inner OpenTelemetryChatClient
When _autoWireChatClient=true and the caller sets EnableSensitiveData=true on the outer OpenTelemetryAgent, the auto-wired inner OpenTelemetryChatClient now also has EnableSensitiveData propagated, so the inner chat span correctly captures message content (gen_ai.input.messages / gen_ai.output.messages). Red test added first to reproduce the bug, then the fix applied (green). Fixes #5873 Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/fda69dd4-9576-4f3f-b954-514321652ea9 Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
bd4fc64b4d
commit
ce242ebeb2
@@ -242,10 +242,11 @@ public sealed class OpenTelemetryAgent : DelegatingAIAgent, IDisposable
|
||||
}
|
||||
|
||||
string sourceName = this._sourceName;
|
||||
static IChatClient WrapIfNeeded(IChatClient cc, string sourceName) =>
|
||||
bool enableSensitiveData = this.EnableSensitiveData;
|
||||
static IChatClient WrapIfNeeded(IChatClient cc, string sourceName, bool enableSensitiveData) =>
|
||||
cc.GetService(typeof(OpenTelemetryChatClient)) is not null
|
||||
? cc
|
||||
: cc.AsBuilder().UseOpenTelemetry(sourceName: sourceName).Build();
|
||||
: cc.AsBuilder().UseOpenTelemetry(sourceName: sourceName, configure: o => o.EnableSensitiveData = enableSensitiveData).Build();
|
||||
|
||||
if (options is ChatClientAgentRunOptions ccOptions)
|
||||
{
|
||||
@@ -253,7 +254,7 @@ public sealed class OpenTelemetryAgent : DelegatingAIAgent, IDisposable
|
||||
// If the user factory already returns an OpenTelemetry-instrumented client, don't double-wrap.
|
||||
var clone = (ChatClientAgentRunOptions)ccOptions.Clone();
|
||||
var userFactory = clone.ChatClientFactory;
|
||||
clone.ChatClientFactory = cc => WrapIfNeeded(userFactory is null ? cc : userFactory(cc), sourceName);
|
||||
clone.ChatClientFactory = cc => WrapIfNeeded(userFactory is null ? cc : userFactory(cc), sourceName, enableSensitiveData);
|
||||
return clone;
|
||||
}
|
||||
|
||||
@@ -261,7 +262,7 @@ public sealed class OpenTelemetryAgent : DelegatingAIAgent, IDisposable
|
||||
// any base AgentRunOptions properties from the caller so they reach the inner agent.
|
||||
var newOptions = new ChatClientAgentRunOptions
|
||||
{
|
||||
ChatClientFactory = cc => WrapIfNeeded(cc, sourceName),
|
||||
ChatClientFactory = cc => WrapIfNeeded(cc, sourceName, enableSensitiveData),
|
||||
};
|
||||
|
||||
if (options is not null)
|
||||
|
||||
@@ -1054,6 +1054,61 @@ public class OpenTelemetryAgentTests
|
||||
Assert.Contains(activities, a => string.Equals(a.GetTagItem("gen_ai.operation.name") as string, "chat", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false, false)]
|
||||
[InlineData(false, true)]
|
||||
[InlineData(true, false)]
|
||||
[InlineData(true, true)]
|
||||
public async Task AutoWireChatClient_EnableSensitiveData_PropagatedToInnerChatClient_Async(bool enableSensitiveData, bool streaming)
|
||||
{
|
||||
// Regression test for: when EnableSensitiveData is set on OpenTelemetryAgent, the auto-wired
|
||||
// inner OpenTelemetryChatClient must also have EnableSensitiveData propagated to it. Previously,
|
||||
// GetRunOptionsWithChatClientWiring created the inner client without passing EnableSensitiveData,
|
||||
// so the inner chat span would never emit gen_ai.input.messages / gen_ai.output.messages even
|
||||
// when the caller explicitly set EnableSensitiveData = true.
|
||||
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);
|
||||
using var agent = new OpenTelemetryAgent(inner, sourceName) { EnableSensitiveData = enableSensitiveData };
|
||||
|
||||
if (streaming)
|
||||
{
|
||||
await foreach (var _ in agent.RunStreamingAsync([new ChatMessage(ChatRole.User, "hello")]))
|
||||
{
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = await agent.RunAsync([new ChatMessage(ChatRole.User, "hello")]);
|
||||
}
|
||||
|
||||
// There should be 2 activities: the invoke_agent span and the inner chat span.
|
||||
Assert.Equal(2, activities.Count);
|
||||
|
||||
var chatSpan = activities.Single(a => string.Equals(a.GetTagItem("gen_ai.operation.name") as string, "chat", StringComparison.Ordinal));
|
||||
var chatTags = chatSpan.Tags.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
||||
|
||||
if (enableSensitiveData)
|
||||
{
|
||||
// When EnableSensitiveData=true on the outer agent, the auto-wired inner client must also
|
||||
// capture message content in the chat span.
|
||||
Assert.True(chatTags.ContainsKey("gen_ai.input.messages"), "gen_ai.input.messages must be present in the inner chat span when EnableSensitiveData=true");
|
||||
Assert.True(chatTags.ContainsKey("gen_ai.output.messages"), "gen_ai.output.messages must be present in the inner chat span when EnableSensitiveData=true");
|
||||
}
|
||||
else
|
||||
{
|
||||
// By default (EnableSensitiveData=false) message content must NOT be captured.
|
||||
Assert.False(chatTags.ContainsKey("gen_ai.input.messages"), "gen_ai.input.messages must NOT be present in the inner chat span when EnableSensitiveData=false");
|
||||
Assert.False(chatTags.ContainsKey("gen_ai.output.messages"), "gen_ai.output.messages must NOT be present in the inner chat span when EnableSensitiveData=false");
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class AutoWireTestChatClient : IChatClient
|
||||
{
|
||||
public Action<IEnumerable<ChatMessage>, ChatOptions?>? OnGetResponseAsync { get; set; }
|
||||
|
||||
Reference in New Issue
Block a user