diff --git a/src/Observability/Runtime/DTOs/Builders/ExecuteToolDataBuilder.cs b/src/Observability/Runtime/DTOs/Builders/ExecuteToolDataBuilder.cs index 3412870a..08169058 100644 --- a/src/Observability/Runtime/DTOs/Builders/ExecuteToolDataBuilder.cs +++ b/src/Observability/Runtime/DTOs/Builders/ExecuteToolDataBuilder.cs @@ -89,12 +89,13 @@ private static void AddToolDetails( Dictionary attributes, ToolCallDetails toolCallDetails) { - var (toolName, arguments, toolCallId, description, toolType, endpoint) = toolCallDetails; + var (toolName, arguments, toolCallId, description, toolType, endpoint, toolServerName) = toolCallDetails; AddIfNotNull(attributes, OpenTelemetryConstants.GenAiToolNameKey, toolName); AddIfNotNull(attributes, OpenTelemetryConstants.GenAiToolArgumentsKey, arguments); AddIfNotNull(attributes, OpenTelemetryConstants.GenAiToolCallIdKey, toolCallId); AddIfNotNull(attributes, OpenTelemetryConstants.GenAiToolDescriptionKey, description); AddIfNotNull(attributes, OpenTelemetryConstants.GenAiToolTypeKey, toolType); + AddIfNotNull(attributes, OpenTelemetryConstants.GenAiToolServerNameKey, toolServerName); if (endpoint != null) { AddIfNotNull(attributes, OpenTelemetryConstants.ServerAddressKey, endpoint.Host); diff --git a/src/Observability/Runtime/Tracing/Contracts/ToolCallDetails.cs b/src/Observability/Runtime/Tracing/Contracts/ToolCallDetails.cs index 33805b39..095c5e82 100644 --- a/src/Observability/Runtime/Tracing/Contracts/ToolCallDetails.cs +++ b/src/Observability/Runtime/Tracing/Contracts/ToolCallDetails.cs @@ -20,13 +20,15 @@ public sealed class ToolCallDetails : IEquatable /// Optional description of the tool call. /// Optional type classification for the tool. /// Optional endpoint for remote tool execution. + /// Optional server name for the tool. public ToolCallDetails( string toolName, string? arguments, string? toolCallId = null, string? description = null, string? toolType = null, - Uri? endpoint = null) + Uri? endpoint = null, + string? toolServerName = null) { ToolName = toolName; Arguments = arguments; @@ -34,6 +36,7 @@ public ToolCallDetails( Description = description; ToolType = toolType; Endpoint = endpoint; + ToolServerName = toolServerName; } /// @@ -66,6 +69,11 @@ public ToolCallDetails( /// public Uri? Endpoint { get; } + /// + /// Gets the server name associated with the tool, when provided. + /// + public string? ToolServerName { get; } + /// /// Deconstructs this instance into individual tool call components. /// @@ -75,7 +83,8 @@ public ToolCallDetails( /// Receives the human-readable description. /// Receives the type hint. /// Receives the endpoint. - public void Deconstruct(out string toolName, out string? arguments, out string? toolCallId, out string? description, out string? toolType, out Uri? endpoint) + /// Receives the tool server name. + public void Deconstruct(out string toolName, out string? arguments, out string? toolCallId, out string? description, out string? toolType, out Uri? endpoint, out string? toolServerName) { toolName = ToolName; arguments = Arguments; @@ -83,6 +92,7 @@ public void Deconstruct(out string toolName, out string? arguments, out string? description = Description; toolType = ToolType; endpoint = Endpoint; + toolServerName = ToolServerName; } /// @@ -98,7 +108,8 @@ public bool Equals(ToolCallDetails? other) string.Equals(ToolCallId, other.ToolCallId, StringComparison.Ordinal) && string.Equals(Description, other.Description, StringComparison.Ordinal) && string.Equals(ToolType, other.ToolType, StringComparison.Ordinal) && - EqualityComparer.Default.Equals(Endpoint, other.Endpoint); + EqualityComparer.Default.Equals(Endpoint, other.Endpoint) && + string.Equals(ToolServerName, other.ToolServerName, StringComparison.Ordinal); } /// @@ -119,6 +130,7 @@ public override int GetHashCode() hash = (hash * 31) + (Description != null ? StringComparer.Ordinal.GetHashCode(Description) : 0); hash = (hash * 31) + (ToolType != null ? StringComparer.Ordinal.GetHashCode(ToolType) : 0); hash = (hash * 31) + EqualityComparer.Default.GetHashCode(Endpoint); + hash = (hash * 31) + (ToolServerName != null ? StringComparer.Ordinal.GetHashCode(ToolServerName) : 0); return hash; } } diff --git a/src/Observability/Runtime/Tracing/Scopes/ExecuteToolScope.cs b/src/Observability/Runtime/Tracing/Scopes/ExecuteToolScope.cs index 115b517e..cb6dde68 100644 --- a/src/Observability/Runtime/Tracing/Scopes/ExecuteToolScope.cs +++ b/src/Observability/Runtime/Tracing/Scopes/ExecuteToolScope.cs @@ -56,12 +56,13 @@ private ExecuteToolScope(ToolCallDetails details, AgentDetails agentDetails, Ten conversationId: conversationId, sourceMetadata: sourceMetadata) { - var (toolName, arguments, toolCallId, description, toolType, endpoint) = details; + var (toolName, arguments, toolCallId, description, toolType, endpoint, toolServerName) = details; SetTagMaybe(OpenTelemetryConstants.GenAiToolNameKey, toolName); SetTagMaybe(OpenTelemetryConstants.GenAiToolArgumentsKey, arguments); SetTagMaybe(OpenTelemetryConstants.GenAiToolTypeKey, toolType); SetTagMaybe(OpenTelemetryConstants.GenAiToolCallIdKey, toolCallId); SetTagMaybe(OpenTelemetryConstants.GenAiToolDescriptionKey, description); + SetTagMaybe(OpenTelemetryConstants.GenAiToolServerNameKey, toolServerName); SetTagMaybe(OpenTelemetryConstants.ThreatDiagnosticsSummaryKey, threatDiagnosticsSummary?.ToJson()); if (endpoint !=null) diff --git a/src/Observability/Runtime/Tracing/Scopes/OpenTelemetryConstants.cs b/src/Observability/Runtime/Tracing/Scopes/OpenTelemetryConstants.cs index 473e3354..7388bb66 100644 --- a/src/Observability/Runtime/Tracing/Scopes/OpenTelemetryConstants.cs +++ b/src/Observability/Runtime/Tracing/Scopes/OpenTelemetryConstants.cs @@ -134,6 +134,11 @@ public enum OperationNames /// The GenAI tool type key. /// public const string GenAiToolTypeKey = "gen_ai.tool.type"; + + /// + /// The GenAI tool server name key. + /// + public const string GenAiToolServerNameKey = "gen_ai.tool.server.name"; #endregion /// diff --git a/src/Tests/Microsoft.Agents.A365.Observability.Runtime.IntegrationTests/Agent365ExporterE2ETests.cs b/src/Tests/Microsoft.Agents.A365.Observability.Runtime.IntegrationTests/Agent365ExporterE2ETests.cs index f6d055b9..b23e7fd5 100644 --- a/src/Tests/Microsoft.Agents.A365.Observability.Runtime.IntegrationTests/Agent365ExporterE2ETests.cs +++ b/src/Tests/Microsoft.Agents.A365.Observability.Runtime.IntegrationTests/Agent365ExporterE2ETests.cs @@ -144,7 +144,8 @@ public async Task AddTracing_And_ExecuteToolScope_ExporterMakesExpectedRequest() toolCallId: "call-456", description: "Test tool call description", toolType: "custom-type", - endpoint: endpoint); + endpoint: endpoint, + toolServerName: "test-tool-server"); var expectedThreatDiagnosticsSummary = new ThreatDiagnosticsSummary( blockAction: false, @@ -190,6 +191,7 @@ public async Task AddTracing_And_ExecuteToolScope_ExporterMakesExpectedRequest() this.GetAttribute(attributes, "gen_ai.tool.call.id").Should().Be(toolCallDetails.ToolCallId); this.GetAttribute(attributes, "gen_ai.tool.description").Should().Be(toolCallDetails.Description); this.GetAttribute(attributes, "gen_ai.tool.type").Should().Be(toolCallDetails.ToolType); + this.GetAttribute(attributes, "gen_ai.tool.server.name").Should().Be(toolCallDetails.ToolServerName); this.GetAttribute(attributes, "server.address").Should().Be(endpoint.Host); this.GetAttribute(attributes, "server.port").Should().Be(endpoint.Port.ToString()); this.GetAttribute(attributes, "gen_ai.event.content").Should().Be("Tool response content"); diff --git a/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/DTOs/Builders/ExecuteToolDataBuilderTests.cs b/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/DTOs/Builders/ExecuteToolDataBuilderTests.cs index 81fd2f3d..5469eb8d 100644 --- a/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/DTOs/Builders/ExecuteToolDataBuilderTests.cs +++ b/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/DTOs/Builders/ExecuteToolDataBuilderTests.cs @@ -51,7 +51,7 @@ public void Build_WithFullToolDetails_IncludesAllToolAttributes() { // Arrange var endpoint = new Uri("https://example.com:7071"); - var toolDetails = new ToolCallDetails("toolB", "{b:2}", "call-123", "Test tool", "function", endpoint); + var toolDetails = new ToolCallDetails("toolB", "{b:2}", "call-123", "Test tool", "function", endpoint, "my-tool-server"); var agent = new AgentDetails("agent-2", "AgentTwo", "Desc", agentAUID: "auid", agentUPN: "upn@example.com", agentBlueprintId: "bp-1"); var tenant = new TenantDetails(Guid.NewGuid()); var conversationId = "conv-full"; @@ -64,6 +64,7 @@ public void Build_WithFullToolDetails_IncludesAllToolAttributes() attrs.Should().ContainKey(OpenTelemetryConstants.GenAiToolCallIdKey).WhoseValue.Should().Be("call-123"); attrs.Should().ContainKey(OpenTelemetryConstants.GenAiToolDescriptionKey).WhoseValue.Should().Be("Test tool"); attrs.Should().ContainKey(OpenTelemetryConstants.GenAiToolTypeKey).WhoseValue.Should().Be("function"); + attrs.Should().ContainKey(OpenTelemetryConstants.GenAiToolServerNameKey).WhoseValue.Should().Be("my-tool-server"); attrs.Should().ContainKey(OpenTelemetryConstants.ServerAddressKey).WhoseValue.Should().Be("example.com"); attrs.Should().ContainKey(OpenTelemetryConstants.ServerPortKey).WhoseValue.Should().Be(7071); attrs.Should().ContainKey(OpenTelemetryConstants.GenAiAgentAUIDKey).WhoseValue.Should().Be("auid"); @@ -153,6 +154,7 @@ public void Build_WithNullOptionalParameters_OmitsThoseAttributes() data.Attributes.Should().NotContainKey(OpenTelemetryConstants.GenAiToolCallIdKey); data.Attributes.Should().NotContainKey(OpenTelemetryConstants.GenAiToolDescriptionKey); data.Attributes.Should().NotContainKey(OpenTelemetryConstants.GenAiToolTypeKey); + data.Attributes.Should().NotContainKey(OpenTelemetryConstants.GenAiToolServerNameKey); data.Attributes.Should().ContainKey(OpenTelemetryConstants.GenAiConversationIdKey).WhoseValue.Should().Be(conversationId); data.Attributes.Should().NotContainKey(OpenTelemetryConstants.GenAiEventContent); } @@ -201,7 +203,7 @@ public void Build_WithAllParameters_SetsAllExpectedAttributes() { // Arrange var endpoint = new Uri("https://example.org:6060"); - var toolDetails = new ToolCallDetails("toolJ", "{x:1}", "call-999", "Full tool", "extension", endpoint); + var toolDetails = new ToolCallDetails("toolJ", "{x:1}", "call-999", "Full tool", "extension", endpoint, "full-tool-server"); var agent = new AgentDetails("agent-10", "AgentTen", "Desc", agentAUID: "auid10", agentUPN: "upn10@example.com", agentBlueprintId: "bp-10"); var tenant = new TenantDetails(Guid.NewGuid()); var conversationId = "conv-all"; @@ -228,6 +230,7 @@ public void Build_WithAllParameters_SetsAllExpectedAttributes() attrs.Should().ContainKey(OpenTelemetryConstants.GenAiToolCallIdKey); attrs.Should().ContainKey(OpenTelemetryConstants.GenAiToolDescriptionKey); attrs.Should().ContainKey(OpenTelemetryConstants.GenAiToolTypeKey); + attrs.Should().ContainKey(OpenTelemetryConstants.GenAiToolServerNameKey).WhoseValue.Should().Be("full-tool-server"); attrs.Should().ContainKey(OpenTelemetryConstants.GenAiConversationIdKey); attrs.Should().ContainKey(OpenTelemetryConstants.GenAiEventContent); data.StartTime.Should().Be(start); diff --git a/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Tracing/Scopes/ExecuteToolScopeTest.cs b/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Tracing/Scopes/ExecuteToolScopeTest.cs index 06407830..46d106ef 100644 --- a/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Tracing/Scopes/ExecuteToolScopeTest.cs +++ b/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Tracing/Scopes/ExecuteToolScopeTest.cs @@ -207,4 +207,26 @@ public void RecordThreatDiagnosticsSummary_SetsTagCorrectly() tagValue.Should().Contain("\"reason\":\"Blocked due to policy violation.\""); tagValue.Should().Contain("data-loss-prevention"); } + + [TestMethod] + public void Start_ToolServerName_IsSetCorrectly() + { + // Arrange + const string expectedToolServerName = "test-tool-server"; + var toolCallDetails = new ToolCallDetails( + toolName: "TestTool", + arguments: "args", + toolServerName: expectedToolServerName); + var agentDetails = Util.GetAgentDetails(); + var tenantDetails = Util.GetTenantDetails(); + + // Act + var activity = ListenForActivity(() => + { + using var scope = ExecuteToolScope.Start(toolCallDetails, agentDetails, tenantDetails); + }); + + // Assert + activity.ShouldHaveTag(OpenTelemetryConstants.GenAiToolServerNameKey, expectedToolServerName); + } } \ No newline at end of file