From ed85c5d9ebec133d611a9bb30e3512cbdeaffacc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Oct 2025 15:27:22 +0000 Subject: [PATCH 1/3] Initial plan From 019f710f774c284adcdb50b06a8f2985ac2ee5c2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Oct 2025 15:41:06 +0000 Subject: [PATCH 2/3] Add Name property to OtelMessage and populate from ChatMessage.AuthorName Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> --- .../ChatCompletion/OpenTelemetryChatClient.cs | 2 + .../OpenTelemetryChatClientTests.cs | 83 +++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryChatClient.cs b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryChatClient.cs index 95358dd25a6..9935a7834fc 100644 --- a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryChatClient.cs +++ b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryChatClient.cs @@ -239,6 +239,7 @@ internal static string SerializeChatMessages( message.Role == ChatRole.Tool ? "tool" : message.Role == ChatRole.System || message.Role == new ChatRole("developer") ? "system" : "user", + Name = message.AuthorName, }; foreach (AIContent content in message.Contents) @@ -595,6 +596,7 @@ private void AddOutputMessagesTags(ChatResponse response, Activity? activity) private sealed class OtelMessage { public string? Role { get; set; } + public string? Name { get; set; } public List Parts { get; set; } = []; public string? FinishReason { get; set; } } diff --git a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/OpenTelemetryChatClientTests.cs b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/OpenTelemetryChatClientTests.cs index 88f02191163..e421630f184 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/OpenTelemetryChatClientTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/OpenTelemetryChatClientTests.cs @@ -401,5 +401,88 @@ public async Task UnknownContentTypes_Ignored() private sealed class NonSerializableAIContent : AIContent; + [Fact] + public async Task AuthorName_IncludedInTelemetry() + { + var sourceName = Guid.NewGuid().ToString(); + var activities = new List(); + using var tracerProvider = OpenTelemetry.Sdk.CreateTracerProviderBuilder() + .AddSource(sourceName) + .AddInMemoryExporter(activities) + .Build(); + + using var innerClient = new TestChatClient + { + GetResponseAsyncCallback = async (messages, options, cancellationToken) => + { + await Task.Yield(); + return new ChatResponse(new ChatMessage(ChatRole.Assistant, "Response")); + }, + }; + + using var chatClient = innerClient + .AsBuilder() + .UseOpenTelemetry(null, sourceName, configure: instance => + { + instance.EnableSensitiveData = true; + instance.JsonSerializerOptions = TestJsonSerializerContext.Default.Options; + }) + .Build(); + + List messages = + [ + new(ChatRole.User, "Hello!") { AuthorName = "Alice" }, + new(ChatRole.Assistant, "Hi there!") { AuthorName = "BotAssistant" }, + new(ChatRole.User, "How are you?") { AuthorName = "Bob" }, + ]; + + var response = await chatClient.GetResponseAsync(messages); + Assert.NotNull(response); + + var activity = Assert.Single(activities); + Assert.NotNull(activity); + + var inputMessages = activity.Tags.First(kvp => kvp.Key == "gen_ai.input.messages").Value; + Assert.Contains("\"name\": \"Alice\"", inputMessages); + Assert.Contains("\"name\": \"BotAssistant\"", inputMessages); + Assert.Contains("\"name\": \"Bob\"", inputMessages); + + // Verify the exact structure + Assert.Equal(ReplaceWhitespace(""" + [ + { + "role": "user", + "name": "Alice", + "parts": [ + { + "type": "text", + "content": "Hello!" + } + ] + }, + { + "role": "assistant", + "name": "BotAssistant", + "parts": [ + { + "type": "text", + "content": "Hi there!" + } + ] + }, + { + "role": "user", + "name": "Bob", + "parts": [ + { + "type": "text", + "content": "How are you?" + } + ] + } + ] + """), ReplaceWhitespace(inputMessages)); + } + private static string ReplaceWhitespace(string? input) => Regex.Replace(input ?? "", @"\s+", " ").Trim(); } From 9354681ac955440751ddc979e978c396e8382d67 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Oct 2025 16:05:29 +0000 Subject: [PATCH 3/3] Refactor: integrate AuthorName test into existing ExpectedInformationLogged_Async test Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> --- .../OpenTelemetryChatClientTests.cs | 89 +------------------ 1 file changed, 4 insertions(+), 85 deletions(-) diff --git a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/OpenTelemetryChatClientTests.cs b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/OpenTelemetryChatClientTests.cs index e421630f184..f3236123969 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/OpenTelemetryChatClientTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/OpenTelemetryChatClientTests.cs @@ -105,10 +105,10 @@ async static IAsyncEnumerable CallbackAsync( List messages = [ new(ChatRole.System, "You are a close friend."), - new(ChatRole.User, "Hey!"), + new(ChatRole.User, "Hey!") { AuthorName = "Alice" }, new(ChatRole.Assistant, [new FunctionCallContent("12345", "GetPersonName")]), new(ChatRole.Tool, [new FunctionResultContent("12345", "John")]), - new(ChatRole.Assistant, "Hey John, what's up?"), + new(ChatRole.Assistant, "Hey John, what's up?") { AuthorName = "BotAssistant" }, new(ChatRole.User, "What's the biggest animal?") ]; @@ -201,6 +201,7 @@ async static IAsyncEnumerable CallbackAsync( }, { "role": "user", + "name": "Alice", "parts": [ { "type": "text", @@ -230,6 +231,7 @@ async static IAsyncEnumerable CallbackAsync( }, { "role": "assistant", + "name": "BotAssistant", "parts": [ { "type": "text", @@ -401,88 +403,5 @@ public async Task UnknownContentTypes_Ignored() private sealed class NonSerializableAIContent : AIContent; - [Fact] - public async Task AuthorName_IncludedInTelemetry() - { - var sourceName = Guid.NewGuid().ToString(); - var activities = new List(); - using var tracerProvider = OpenTelemetry.Sdk.CreateTracerProviderBuilder() - .AddSource(sourceName) - .AddInMemoryExporter(activities) - .Build(); - - using var innerClient = new TestChatClient - { - GetResponseAsyncCallback = async (messages, options, cancellationToken) => - { - await Task.Yield(); - return new ChatResponse(new ChatMessage(ChatRole.Assistant, "Response")); - }, - }; - - using var chatClient = innerClient - .AsBuilder() - .UseOpenTelemetry(null, sourceName, configure: instance => - { - instance.EnableSensitiveData = true; - instance.JsonSerializerOptions = TestJsonSerializerContext.Default.Options; - }) - .Build(); - - List messages = - [ - new(ChatRole.User, "Hello!") { AuthorName = "Alice" }, - new(ChatRole.Assistant, "Hi there!") { AuthorName = "BotAssistant" }, - new(ChatRole.User, "How are you?") { AuthorName = "Bob" }, - ]; - - var response = await chatClient.GetResponseAsync(messages); - Assert.NotNull(response); - - var activity = Assert.Single(activities); - Assert.NotNull(activity); - - var inputMessages = activity.Tags.First(kvp => kvp.Key == "gen_ai.input.messages").Value; - Assert.Contains("\"name\": \"Alice\"", inputMessages); - Assert.Contains("\"name\": \"BotAssistant\"", inputMessages); - Assert.Contains("\"name\": \"Bob\"", inputMessages); - - // Verify the exact structure - Assert.Equal(ReplaceWhitespace(""" - [ - { - "role": "user", - "name": "Alice", - "parts": [ - { - "type": "text", - "content": "Hello!" - } - ] - }, - { - "role": "assistant", - "name": "BotAssistant", - "parts": [ - { - "type": "text", - "content": "Hi there!" - } - ] - }, - { - "role": "user", - "name": "Bob", - "parts": [ - { - "type": "text", - "content": "How are you?" - } - ] - } - ] - """), ReplaceWhitespace(inputMessages)); - } - private static string ReplaceWhitespace(string? input) => Regex.Replace(input ?? "", @"\s+", " ").Trim(); }