From 4bb307058fa2680cadcf0f2a434b1c681b8f9dd0 Mon Sep 17 00:00:00 2001 From: "Nikhil Chitlur Navakiran (from Dev Box)" Date: Tue, 26 May 2026 13:10:13 -0600 Subject: [PATCH 1/2] Use dynamic filtering for extra attributes Extra attributes can now add any key that the builder did not already set on the span, rather than being blocked by a hardcoded 40-key allowlist. This allows attributes like gen_ai.request.model to be set on invoke_agent spans via extraAttributes while still preventing overrides of builder-set keys. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Runtime/DTOs/Builders/BaseDataBuilder.cs | 51 ++----------------- .../Builders/InvokeAgentDataBuilderTests.cs | 25 +++++++++ 2 files changed, 28 insertions(+), 48 deletions(-) diff --git a/src/Microsoft.OpenTelemetry/Agent365/Runtime/DTOs/Builders/BaseDataBuilder.cs b/src/Microsoft.OpenTelemetry/Agent365/Runtime/DTOs/Builders/BaseDataBuilder.cs index dc62235..668da1d 100644 --- a/src/Microsoft.OpenTelemetry/Agent365/Runtime/DTOs/Builders/BaseDataBuilder.cs +++ b/src/Microsoft.OpenTelemetry/Agent365/Runtime/DTOs/Builders/BaseDataBuilder.cs @@ -14,52 +14,6 @@ namespace Microsoft.Agents.A365.Observability.Runtime.DTOs.Builders /// public abstract class BaseDataBuilder where T : BaseData { - // Reserved attribute keys managed by specific builder methods; extra attributes must NOT override these. - private static readonly HashSet ReservedAttributeKeys = new HashSet(StringComparer.Ordinal) - { - OpenTelemetryConstants.GenAiInputMessagesKey, - OpenTelemetryConstants.GenAiOutputMessagesKey, - OpenTelemetryConstants.GenAiAgentIdKey, - OpenTelemetryConstants.GenAiAgentNameKey, - OpenTelemetryConstants.GenAiAgentDescriptionKey, - OpenTelemetryConstants.GenAiAgentVersionKey, - OpenTelemetryConstants.AgentAUIDKey, - OpenTelemetryConstants.AgentEmailKey, - OpenTelemetryConstants.AgentBlueprintIdKey, - OpenTelemetryConstants.AgentPlatformIdKey, - OpenTelemetryConstants.TenantIdKey, - OpenTelemetryConstants.GenAiProviderNameKey, - OpenTelemetryConstants.ServerAddressKey, - OpenTelemetryConstants.ServerPortKey, - OpenTelemetryConstants.ChannelNameKey, - OpenTelemetryConstants.ChannelLinkKey, - OpenTelemetryConstants.UserIdKey, - OpenTelemetryConstants.UserEmailKey, - OpenTelemetryConstants.UserNameKey, - OpenTelemetryConstants.CallerAgentNameKey, - OpenTelemetryConstants.CallerAgentIdKey, - OpenTelemetryConstants.CallerAgentBlueprintIdKey, - OpenTelemetryConstants.CallerAgentAUIDKey, - OpenTelemetryConstants.CallerAgentEmailKey, - OpenTelemetryConstants.CallerAgentPlatformIdKey, - OpenTelemetryConstants.CallerAgentVersionKey, - OpenTelemetryConstants.CallerClientIpKey, - OpenTelemetryConstants.GenAiConversationIdKey, - OpenTelemetryConstants.SessionIdKey, - OpenTelemetryConstants.GenAiToolNameKey, - OpenTelemetryConstants.GenAiToolArgumentsKey, - OpenTelemetryConstants.GenAiToolCallIdKey, - OpenTelemetryConstants.GenAiToolDescriptionKey, - OpenTelemetryConstants.GenAiToolTypeKey, - OpenTelemetryConstants.GenAiToolCallResultKey, - OpenTelemetryConstants.GenAiOperationNameKey, - OpenTelemetryConstants.GenAiRequestModelKey, - OpenTelemetryConstants.GenAiUsageInputTokensKey, - OpenTelemetryConstants.GenAiUsageOutputTokensKey, - OpenTelemetryConstants.GenAiResponseFinishReasonsKey, - OpenTelemetryConstants.GenAiAgentThoughtProcessKey - }; - /// /// Adds attributes for input messages. /// @@ -188,7 +142,8 @@ protected static void AddIfNotNull(IDictionary attributes, stri } /// - /// Adds extra attributes to the attributes dictionary while ignoring reserved keys. + /// Adds extra attributes to the attributes dictionary. + /// Extra attributes cannot override keys already set by the builder on this span. /// protected static void AddExtraAttributes(IDictionary attributes, IDictionary? extraAttributes) { @@ -196,7 +151,7 @@ protected static void AddExtraAttributes(IDictionary attributes foreach (var kvp in extraAttributes) { - if ((kvp.Value != null && !ReservedAttributeKeys.Contains(kvp.Key))) + if (kvp.Value != null && !attributes.ContainsKey(kvp.Key)) { attributes[kvp.Key] = kvp.Value; } diff --git a/test/Microsoft.OpenTelemetry.Agent365.Tests/Runtime/DTOs/Builders/InvokeAgentDataBuilderTests.cs b/test/Microsoft.OpenTelemetry.Agent365.Tests/Runtime/DTOs/Builders/InvokeAgentDataBuilderTests.cs index f59bd97..dcba472 100644 --- a/test/Microsoft.OpenTelemetry.Agent365.Tests/Runtime/DTOs/Builders/InvokeAgentDataBuilderTests.cs +++ b/test/Microsoft.OpenTelemetry.Agent365.Tests/Runtime/DTOs/Builders/InvokeAgentDataBuilderTests.cs @@ -435,6 +435,31 @@ public void Build_IgnoresNullValues_InExtraAttributes() telemetry.Attributes.Should().ContainKey("custom.good").WhoseValue.Should().Be("ok"); } + [TestMethod] + public void Build_AllowsRequestModel_ViaExtraAttributes_WhenNotSetByBuilder() + { + // Arrange + var endpoint = new Uri("https://example.com"); + var agentDetails = new AgentDetails("agent-model", "ModelAgent"); + var scopeDetails = new InvokeAgentScopeDetails(endpoint: endpoint); + var conversationId = "conv-model"; + var extras = new Dictionary + { + {OpenTelemetryConstants.GenAiRequestModelKey, "gpt-4o"}, + }; + + // Act + var telemetry = InvokeAgentDataBuilder.Build( + scopeDetails, + agentDetails, + conversationId, + extraAttributes: extras); + + // Assert - gen_ai.request.model is allowed because InvokeAgentDataBuilder does not set it + telemetry.Attributes.Should().ContainKey(OpenTelemetryConstants.GenAiRequestModelKey) + .WhoseValue.Should().Be("gpt-4o"); + } + [TestMethod] public void Build_WithAgentPlatformId_SetsExpectedAttributes() { From 4314f95406c9ae77779ffddb24b907b67bdb38fd Mon Sep 17 00:00:00 2001 From: "Nikhil Chitlur Navakiran (from Dev Box)" Date: Mon, 6 Jul 2026 10:04:25 -0600 Subject: [PATCH 2/2] Add changelog entry for dynamic extra-attribute filtering Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index be4aa76..9afa027 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Unreleased + +- Filter extra attributes dynamically instead of against a static reserved-key list: `BaseDataBuilder.AddExtraAttributes` now skips any key already set by the builder on the span (`!attributes.ContainsKey(key)`), removing the hard-coded `ReservedAttributeKeys` set so newly added builder attributes are protected automatically ([#107](https://github.com/microsoft/opentelemetry-distro-dotnet/pull/107)) + ## 1.0.6 - 2026-07-01 - Make the `invoke_agent` span compliant with OpenTelemetry GenAI semantic conventions v1.42: add request/response GenAI attributes (`gen_ai.request.*`, `gen_ai.data_source.id`, `gen_ai.output.type`, `gen_ai.system_instructions`, `gen_ai.response.finish_reasons`, `gen_ai.usage.*`) via reusable `GenAiRequestParameters`/`GenAiResponseParameters`, and emit `gen_ai.provider.name` on spans that carry agent details (e.g. `invoke_agent`, inference). Token usage is now emitted as integers and `gen_ai.response.finish_reasons` as a string array across all spans ([#120](https://github.com/microsoft/opentelemetry-distro-dotnet/pull/120))