From f16cac7163925cfa988a0482ed95f08566faddc2 Mon Sep 17 00:00:00 2001 From: Thanika Reddy Date: Tue, 10 Feb 2026 15:29:35 -0800 Subject: [PATCH] Expose CallerDetails on ExecuteTool and Inference scopes. --- .../Runtime/DTOs/Builders/BaseDataBuilder.cs | 2 ++ .../Builders/ExecuteInferenceDataBuilder.cs | 7 ++++ .../DTOs/Builders/ExecuteToolDataBuilder.cs | 8 ++++- .../Runtime/Etw/A365EtwLogger.cs | 12 ++++--- .../Runtime/Etw/IA365EtwLogger.cs | 8 +++-- .../Tracing/Scopes/ExecuteToolScope.cs | 8 +++-- .../Runtime/Tracing/Scopes/InferenceScope.cs | 8 +++-- .../Tracing/Scopes/InvokeAgentScope.cs | 13 ++------ .../Tracing/Scopes/OpenTelemetryScope.cs | 12 ++++++- .../Agent365ExporterE2ETests.cs | 28 ++++++++++++++-- .../ExecuteInferenceDataBuilderTests.cs | 9 ++++- .../Builders/ExecuteToolDataBuilderTests.cs | 9 ++++- .../Etw/EtwLoggingBuilderTests.cs | 23 +++++++++++-- .../Tracing/Scopes/ExecuteToolScopeTest.cs | 29 ++++++++++++++++ .../Tracing/Scopes/InferenceScopeTest.cs | 33 +++++++++++++++++++ 15 files changed, 177 insertions(+), 32 deletions(-) diff --git a/src/Observability/Runtime/DTOs/Builders/BaseDataBuilder.cs b/src/Observability/Runtime/DTOs/Builders/BaseDataBuilder.cs index 0ed05d25..c68d4abb 100644 --- a/src/Observability/Runtime/DTOs/Builders/BaseDataBuilder.cs +++ b/src/Observability/Runtime/DTOs/Builders/BaseDataBuilder.cs @@ -40,6 +40,7 @@ public abstract class BaseDataBuilder where T : BaseData OpenTelemetryConstants.GenAiCallerAgentAUIDKey, OpenTelemetryConstants.GenAiCallerAgentUPNKey, OpenTelemetryConstants.GenAiCallerAgentTenantKey, + OpenTelemetryConstants.GenAiCallerClientIpKey, OpenTelemetryConstants.GenAiConversationIdKey, OpenTelemetryConstants.SessionIdKey, OpenTelemetryConstants.GenAiToolNameKey, @@ -145,6 +146,7 @@ protected static void AddCallerDetails(IDictionary attributes, AddIfNotNull(attributes, OpenTelemetryConstants.GenAiCallerIdKey, callerDetails.CallerId); AddIfNotNull(attributes, OpenTelemetryConstants.GenAiCallerUpnKey, callerDetails.CallerUpn); AddIfNotNull(attributes, OpenTelemetryConstants.GenAiCallerNameKey, callerDetails.CallerName); + AddIfNotNull(attributes, OpenTelemetryConstants.GenAiCallerClientIpKey, callerDetails.CallerClientIP?.ToString()); AddIfNotNull(attributes, OpenTelemetryConstants.GenAiCallerTenantIdKey, callerDetails.TenantId); } diff --git a/src/Observability/Runtime/DTOs/Builders/ExecuteInferenceDataBuilder.cs b/src/Observability/Runtime/DTOs/Builders/ExecuteInferenceDataBuilder.cs index 920ea885..4549a2b6 100644 --- a/src/Observability/Runtime/DTOs/Builders/ExecuteInferenceDataBuilder.cs +++ b/src/Observability/Runtime/DTOs/Builders/ExecuteInferenceDataBuilder.cs @@ -29,6 +29,7 @@ public class ExecuteInferenceDataBuilder : BaseDataBuilder /// Optional source metadata for the inference call. /// Optional agent thought process for the inference. /// Optional hiring manager ID. + /// Optional details about the non-agentic caller. /// Optional dictionary of extra attributes. /// An ExecuteInferenceData object containing all telemetry data. public static ExecuteInferenceData Build( @@ -45,6 +46,7 @@ public static ExecuteInferenceData Build( SourceMetadata? sourceMetadata = null, string? thoughtProcess = null, string? hiringManagerId = null, + CallerDetails? callerDetails = null, IDictionary? extraAttributes = null) { var attributes = BuildAttributes( @@ -57,6 +59,7 @@ public static ExecuteInferenceData Build( sourceMetadata, thoughtProcess, hiringManagerId, + callerDetails, extraAttributes); return new ExecuteInferenceData(attributes, startTime, endTime, spanId, parentSpanId); @@ -72,6 +75,7 @@ public static ExecuteInferenceData Build( SourceMetadata? sourceMetadata, string? thoughtProcess, string? hiringManagerId, + CallerDetails? callerDetails, IDictionary? extraAttributes = null) { var attributes = new Dictionary(); @@ -99,6 +103,9 @@ public static ExecuteInferenceData Build( // Add hiring manager ID AddIfNotNull(attributes, HiringManagerIdKey, hiringManagerId); + // Add caller details + AddCallerDetails(attributes, callerDetails); + // Add any extra attributes AddExtraAttributes(attributes, extraAttributes); diff --git a/src/Observability/Runtime/DTOs/Builders/ExecuteToolDataBuilder.cs b/src/Observability/Runtime/DTOs/Builders/ExecuteToolDataBuilder.cs index 029a7b1c..38bc4ef5 100644 --- a/src/Observability/Runtime/DTOs/Builders/ExecuteToolDataBuilder.cs +++ b/src/Observability/Runtime/DTOs/Builders/ExecuteToolDataBuilder.cs @@ -30,6 +30,7 @@ public class ExecuteToolDataBuilder : BaseDataBuilder /// Optional parent span ID for distributed tracing. /// Optional source metadata for the operation. /// Optional hiring manager ID. + /// Optional details about the non-agentic caller. /// Optional dictionary of extra attributes. /// An ExecuteToolData object containing all telemetry data. public static ExecuteToolData Build( @@ -44,9 +45,10 @@ public static ExecuteToolData Build( string? parentSpanId = null, SourceMetadata? sourceMetadata = null, string? hiringManagerId = null, + CallerDetails? callerDetails = null, IDictionary? extraAttributes = null) { - var attributes = BuildAttributes(toolCallDetails, agentDetails, tenantDetails, conversationId, responseContent, sourceMetadata, hiringManagerId, extraAttributes); + var attributes = BuildAttributes(toolCallDetails, agentDetails, tenantDetails, conversationId, responseContent, sourceMetadata, hiringManagerId, callerDetails, extraAttributes); return new ExecuteToolData(attributes, startTime, endTime, spanId, parentSpanId); } @@ -59,6 +61,7 @@ public static ExecuteToolData Build( string? responseContent, SourceMetadata? sourceMetadata, string? hiringManagerId, + CallerDetails? callerDetails, IDictionary? extraAttributes = null) { var attributes = new Dictionary(); @@ -85,6 +88,9 @@ public static ExecuteToolData Build( // Add hiring manager ID AddIfNotNull(attributes, OpenTelemetryConstants.HiringManagerIdKey, hiringManagerId); + // Add caller details + AddCallerDetails(attributes, callerDetails); + // Add any extra attributes AddExtraAttributes(attributes, extraAttributes); diff --git a/src/Observability/Runtime/Etw/A365EtwLogger.cs b/src/Observability/Runtime/Etw/A365EtwLogger.cs index ca43222a..2de32540 100644 --- a/src/Observability/Runtime/Etw/A365EtwLogger.cs +++ b/src/Observability/Runtime/Etw/A365EtwLogger.cs @@ -45,7 +45,8 @@ public void LogInferenceCall( DateTimeOffset? endTime, string? spanId, string? parentSpanId, - SourceMetadata? sourceMetadata) + SourceMetadata? sourceMetadata, + CallerDetails? callerDetails) { var data = ExecuteInferenceDataBuilder.Build( inferenceCallDetails, @@ -58,7 +59,8 @@ public void LogInferenceCall( endTime, spanId, parentSpanId, - sourceMetadata); + sourceMetadata, + callerDetails: callerDetails); logger.Log( LogLevel.Information, @@ -118,7 +120,8 @@ public void LogToolCall( DateTimeOffset? endTime, string? spanId, string? parentSpanId, - SourceMetadata? sourceMetadata) + SourceMetadata? sourceMetadata, + CallerDetails? callerDetails) { var data = ExecuteToolDataBuilder.Build( toolCallDetails, @@ -130,7 +133,8 @@ public void LogToolCall( endTime, spanId, parentSpanId, - sourceMetadata); + sourceMetadata, + callerDetails: callerDetails); logger.Log( LogLevel.Information, diff --git a/src/Observability/Runtime/Etw/IA365EtwLogger.cs b/src/Observability/Runtime/Etw/IA365EtwLogger.cs index e4a46451..6105db79 100644 --- a/src/Observability/Runtime/Etw/IA365EtwLogger.cs +++ b/src/Observability/Runtime/Etw/IA365EtwLogger.cs @@ -51,6 +51,7 @@ public void LogInvokeAgent( /// Optional span ID for tracing. /// Optional parent span ID for tracing. /// Optional source metadata for the inference call. + /// Optional details of the non-agentic caller. public void LogInferenceCall( InferenceCallDetails inferenceCallDetails, AgentDetails agentDetails, @@ -62,7 +63,8 @@ public void LogInferenceCall( DateTimeOffset? endTime = null, string? spanId = null, string? parentSpanId = null, - SourceMetadata? sourceMetadata = null); + SourceMetadata? sourceMetadata = null, + CallerDetails? callerDetails = null); /// /// Logs an execute_tool event. @@ -77,6 +79,7 @@ public void LogInferenceCall( /// Optional span ID for tracing. /// Optional parent span ID for tracing. /// Optional source metadata for the tool call. + /// Optional details of the non-agentic caller. public void LogToolCall( ToolCallDetails toolCallDetails, AgentDetails agentDetails, @@ -87,7 +90,8 @@ public void LogToolCall( DateTimeOffset? endTime = null, string? spanId = null, string? parentSpanId = null, - SourceMetadata? sourceMetadata = null); + SourceMetadata? sourceMetadata = null, + CallerDetails? callerDetails = null); /// /// Logs an output_messages event. diff --git a/src/Observability/Runtime/Tracing/Scopes/ExecuteToolScope.cs b/src/Observability/Runtime/Tracing/Scopes/ExecuteToolScope.cs index cb6dde68..9a64e5c8 100644 --- a/src/Observability/Runtime/Tracing/Scopes/ExecuteToolScope.cs +++ b/src/Observability/Runtime/Tracing/Scopes/ExecuteToolScope.cs @@ -29,6 +29,7 @@ public sealed class ExecuteToolScope : OpenTelemetryScope /// Optional conversation or session correlation ID for the tool execution. /// Optional metadata describing the source of the call (e.g., component, file, line) for observability. /// Optional threat diagnostics summary containing security-related information about blocked actions. + /// Optional details about the non-agentic caller. /// A new ExecuteToolScope instance. /// /// @@ -43,9 +44,9 @@ public sealed class ExecuteToolScope : OpenTelemetryScope /// Learn more about certification requirements /// /// - public static ExecuteToolScope Start(ToolCallDetails details, AgentDetails agentDetails, TenantDetails tenantDetails, string? parentId = null, string? conversationId = null, SourceMetadata? sourceMetadata = null, ThreatDiagnosticsSummary? threatDiagnosticsSummary = null) => new ExecuteToolScope(details, agentDetails, tenantDetails, parentId, conversationId, sourceMetadata, threatDiagnosticsSummary); + public static ExecuteToolScope Start(ToolCallDetails details, AgentDetails agentDetails, TenantDetails tenantDetails, string? parentId = null, string? conversationId = null, SourceMetadata? sourceMetadata = null, ThreatDiagnosticsSummary? threatDiagnosticsSummary = null, CallerDetails? callerDetails = null) => new ExecuteToolScope(details, agentDetails, tenantDetails, parentId, conversationId, sourceMetadata, threatDiagnosticsSummary, callerDetails); - private ExecuteToolScope(ToolCallDetails details, AgentDetails agentDetails, TenantDetails tenantDetails, string? parentId = null, string? conversationId = null, SourceMetadata? sourceMetadata = null, ThreatDiagnosticsSummary? threatDiagnosticsSummary = null) + private ExecuteToolScope(ToolCallDetails details, AgentDetails agentDetails, TenantDetails tenantDetails, string? parentId = null, string? conversationId = null, SourceMetadata? sourceMetadata = null, ThreatDiagnosticsSummary? threatDiagnosticsSummary = null, CallerDetails? callerDetails = null) : base( kind: ActivityKind.Internal, agentDetails: agentDetails, @@ -54,7 +55,8 @@ private ExecuteToolScope(ToolCallDetails details, AgentDetails agentDetails, Ten activityName: $"{OperationName} {details.ToolName}", parentId: parentId, conversationId: conversationId, - sourceMetadata: sourceMetadata) + sourceMetadata: sourceMetadata, + callerDetails: callerDetails) { var (toolName, arguments, toolCallId, description, toolType, endpoint, toolServerName) = details; SetTagMaybe(OpenTelemetryConstants.GenAiToolNameKey, toolName); diff --git a/src/Observability/Runtime/Tracing/Scopes/InferenceScope.cs b/src/Observability/Runtime/Tracing/Scopes/InferenceScope.cs index 9cd86757..fa79988e 100644 --- a/src/Observability/Runtime/Tracing/Scopes/InferenceScope.cs +++ b/src/Observability/Runtime/Tracing/Scopes/InferenceScope.cs @@ -24,6 +24,7 @@ public sealed class InferenceScope : OpenTelemetryScope /// Optional parent Activity ID used to link this span to an upstream operation. /// Optional conversation or session correlation ID for the inference. /// Optional metadata describing the source of the call (e.g., component, file, line) for observability. + /// Optional details about the non-agentic caller. /// A new InferenceScope instance. /// /// @@ -38,9 +39,9 @@ public sealed class InferenceScope : OpenTelemetryScope /// Learn more about certification requirements /// /// - public static InferenceScope Start(InferenceCallDetails details, AgentDetails agentDetails, TenantDetails tenantDetails, string? parentId = null, string? conversationId = null, SourceMetadata? sourceMetadata = null) => new InferenceScope(details, agentDetails, tenantDetails, parentId, conversationId, sourceMetadata); + public static InferenceScope Start(InferenceCallDetails details, AgentDetails agentDetails, TenantDetails tenantDetails, string? parentId = null, string? conversationId = null, SourceMetadata? sourceMetadata = null, CallerDetails? callerDetails = null) => new InferenceScope(details, agentDetails, tenantDetails, parentId, conversationId, sourceMetadata, callerDetails); - private InferenceScope(InferenceCallDetails details, AgentDetails agentDetails, TenantDetails tenantDetails, string? parentId = null, string? conversationId = null, SourceMetadata? sourceMetadata = null) + private InferenceScope(InferenceCallDetails details, AgentDetails agentDetails, TenantDetails tenantDetails, string? parentId = null, string? conversationId = null, SourceMetadata? sourceMetadata = null, CallerDetails? callerDetails = null) : base( kind: ActivityKind.Client, agentDetails: agentDetails, @@ -49,7 +50,8 @@ private InferenceScope(InferenceCallDetails details, AgentDetails agentDetails, activityName: $"{details.OperationName} {details.Model}", parentId: parentId, conversationId: conversationId, - sourceMetadata: sourceMetadata) + sourceMetadata: sourceMetadata, + callerDetails: callerDetails) { SetTagMaybe(GenAiOperationNameKey, details.OperationName.ToString()); SetTagMaybe(GenAiRequestModelKey, details.Model); diff --git a/src/Observability/Runtime/Tracing/Scopes/InvokeAgentScope.cs b/src/Observability/Runtime/Tracing/Scopes/InvokeAgentScope.cs index aef22e0b..755221aa 100644 --- a/src/Observability/Runtime/Tracing/Scopes/InvokeAgentScope.cs +++ b/src/Observability/Runtime/Tracing/Scopes/InvokeAgentScope.cs @@ -62,7 +62,8 @@ private InvokeAgentScope(InvokeAgentDetails invokeAgentDetails, TenantDetails te ? OperationName : $"invoke_agent {invokeAgentDetails.Details.AgentName}", conversationId: conversationId, - sourceMetadata: request?.SourceMetadata) + sourceMetadata: request?.SourceMetadata, + callerDetails: callerDetails) { var (endpoint, _, sessionId) = invokeAgentDetails; @@ -77,16 +78,6 @@ private InvokeAgentScope(InvokeAgentDetails invokeAgentDetails, TenantDetails te SetTagMaybe(OpenTelemetryConstants.ServerPortKey, endpoint?.Port); } - // Set caller details tags - if (callerDetails != null) - { - SetTagMaybe(OpenTelemetryConstants.GenAiCallerIdKey, callerDetails.CallerId); - SetTagMaybe(OpenTelemetryConstants.GenAiCallerUpnKey, callerDetails.CallerUpn); - SetTagMaybe(OpenTelemetryConstants.GenAiCallerNameKey, callerDetails.CallerName); - SetTagMaybe(OpenTelemetryConstants.GenAiCallerClientIpKey, callerDetails.CallerClientIP?.ToString()); - SetTagMaybe(OpenTelemetryConstants.GenAiCallerTenantIdKey, callerDetails.TenantId); - } - // Set caller agent details tags if (callerAgentDetails != null) { diff --git a/src/Observability/Runtime/Tracing/Scopes/OpenTelemetryScope.cs b/src/Observability/Runtime/Tracing/Scopes/OpenTelemetryScope.cs index 79c5e847..14e22b42 100644 --- a/src/Observability/Runtime/Tracing/Scopes/OpenTelemetryScope.cs +++ b/src/Observability/Runtime/Tracing/Scopes/OpenTelemetryScope.cs @@ -46,7 +46,8 @@ public abstract class OpenTelemetryScope : IDisposable /// Optional parent ID for the activity. /// Optional conversation id. /// Optional source metadata. - protected OpenTelemetryScope(ActivityKind kind, AgentDetails agentDetails, TenantDetails tenantDetails, string operationName, string activityName, DateTimeOffset? startTime = null, string? parentId = null, string? conversationId = null, SourceMetadata? sourceMetadata = null) + /// Optional details about the non-agentic caller. + protected OpenTelemetryScope(ActivityKind kind, AgentDetails agentDetails, TenantDetails tenantDetails, string operationName, string activityName, DateTimeOffset? startTime = null, string? parentId = null, string? conversationId = null, SourceMetadata? sourceMetadata = null, CallerDetails? callerDetails = null) { customStartTime = startTime; activity = ActivitySource.CreateActivity(activityName, kind, default(ActivityContext)); @@ -104,6 +105,15 @@ protected OpenTelemetryScope(ActivityKind kind, AgentDetails agentDetails, Tenan SetTagMaybe(OpenTelemetryConstants.GenAiChannelLinkKey, sourceMetadata.Description); } + if (callerDetails != null) + { + SetTagMaybe(OpenTelemetryConstants.GenAiCallerIdKey, callerDetails.CallerId); + SetTagMaybe(OpenTelemetryConstants.GenAiCallerUpnKey, callerDetails.CallerUpn); + SetTagMaybe(OpenTelemetryConstants.GenAiCallerNameKey, callerDetails.CallerName); + SetTagMaybe(OpenTelemetryConstants.GenAiCallerClientIpKey, callerDetails.CallerClientIP?.ToString()); + SetTagMaybe(OpenTelemetryConstants.GenAiCallerTenantIdKey, callerDetails.TenantId); + } + activity?.Start(); } 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 b23e7fd5..245a6763 100644 --- a/src/Tests/Microsoft.Agents.A365.Observability.Runtime.IntegrationTests/Agent365ExporterE2ETests.cs +++ b/src/Tests/Microsoft.Agents.A365.Observability.Runtime.IntegrationTests/Agent365ExporterE2ETests.cs @@ -153,8 +153,15 @@ public async Task AddTracing_And_ExecuteToolScope_ExporterMakesExpectedRequest() reason: "No threats detected during tool execution.", diagnostics: null); + var expectedToolCallerDetails = new CallerDetails( + callerId: "tool-caller-456", + callerName: "Tool Caller", + callerUpn: "tool-caller@ztaitest12.onmicrosoft.com", + callerClientIP: IPAddress.Parse("10.0.0.42"), + tenantId: expectedAgentDetails.TenantId); + // Act - using (var scope = ExecuteToolScope.Start(toolCallDetails, expectedAgentDetails, tenantDetails, threatDiagnosticsSummary: expectedThreatDiagnosticsSummary)) + using (var scope = ExecuteToolScope.Start(toolCallDetails, expectedAgentDetails, tenantDetails, threatDiagnosticsSummary: expectedThreatDiagnosticsSummary, callerDetails: expectedToolCallerDetails)) { scope.RecordResponse("Tool response content"); } @@ -196,6 +203,11 @@ public async Task AddTracing_And_ExecuteToolScope_ExporterMakesExpectedRequest() this.GetAttribute(attributes, "server.port").Should().Be(endpoint.Port.ToString()); this.GetAttribute(attributes, "gen_ai.event.content").Should().Be("Tool response content"); this.GetAttribute(attributes, "gen_ai.agent.type").Should().Be(expectedAgentType.ToString()); + this.GetAttribute(attributes, "gen_ai.caller.id").Should().Be(expectedToolCallerDetails.CallerId); + this.GetAttribute(attributes, "gen_ai.caller.upn").Should().Be(expectedToolCallerDetails.CallerUpn); + this.GetAttribute(attributes, "gen_ai.caller.name").Should().Be(expectedToolCallerDetails.CallerName); + this.GetAttribute(attributes, "gen_ai.caller.client.ip").Should().Be(expectedToolCallerDetails.CallerClientIP?.ToString()); + this.GetAttribute(attributes, "gen_ai.caller.tenantid").Should().Be(expectedToolCallerDetails.TenantId); var toolThreatSummaryJson = this.GetAttribute(attributes, "threat.diagnostics.summary"); toolThreatSummaryJson.Should().Contain("\"blockAction\":false"); toolThreatSummaryJson.Should().Contain("\"reasonCode\":200"); @@ -231,8 +243,15 @@ public async Task AddTracing_And_InferenceScope_ExporterMakesExpectedRequest() finishReasons: new[] { "stop", "length" }, responseId: "response-xyz"); + var expectedInferenceCallerDetails = new CallerDetails( + callerId: "inference-caller-789", + callerName: "Inference Caller", + callerUpn: "inference-caller@ztaitest12.onmicrosoft.com", + callerClientIP: IPAddress.Parse("172.16.0.42"), + tenantId: expectedAgentDetails.TenantId); + // Act - using (var scope = InferenceScope.Start(inferenceDetails, expectedAgentDetails, tenantDetails)) + using (var scope = InferenceScope.Start(inferenceDetails, expectedAgentDetails, tenantDetails, callerDetails: expectedInferenceCallerDetails)) { scope.RecordInputMessages(new[] { "Hello", "World" }); scope.RecordOutputMessages(new[] { "Hi there!" }); @@ -277,6 +296,11 @@ public async Task AddTracing_And_InferenceScope_ExporterMakesExpectedRequest() this.GetAttribute(attributes, "gen_ai.input.messages").Should().Be("Hello,World"); this.GetAttribute(attributes, "gen_ai.output.messages").Should().Be("Hi there!"); this.GetAttribute(attributes, "gen_ai.agent.type").Should().Be(expectedAgentType.ToString()); + this.GetAttribute(attributes, "gen_ai.caller.id").Should().Be(expectedInferenceCallerDetails.CallerId); + this.GetAttribute(attributes, "gen_ai.caller.upn").Should().Be(expectedInferenceCallerDetails.CallerUpn); + this.GetAttribute(attributes, "gen_ai.caller.name").Should().Be(expectedInferenceCallerDetails.CallerName); + this.GetAttribute(attributes, "gen_ai.caller.client.ip").Should().Be(expectedInferenceCallerDetails.CallerClientIP?.ToString()); + this.GetAttribute(attributes, "gen_ai.caller.tenantid").Should().Be(expectedInferenceCallerDetails.TenantId); } [TestMethod] diff --git a/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/DTOs/Builders/ExecuteInferenceDataBuilderTests.cs b/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/DTOs/Builders/ExecuteInferenceDataBuilderTests.cs index 38b75aa9..2817f5e7 100644 --- a/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/DTOs/Builders/ExecuteInferenceDataBuilderTests.cs +++ b/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/DTOs/Builders/ExecuteInferenceDataBuilderTests.cs @@ -175,6 +175,7 @@ public void Build_WithAllParameters_SetsAllExpectedAttributes() var parentSpanId = "parent-all-inf"; var thoughtProcess = "First, I analyzed the request. Then, I formulated a response."; var hiringManagerId = "hiring-manager-inf-456"; + var callerDetails = new CallerDetails("caller-inf-123", "Caller Inf Name", "callerinf@example.com", System.Net.IPAddress.Parse("192.168.1.100"), "caller-tenant-inf"); // Act var data = ExecuteInferenceDataBuilder.Build( @@ -189,7 +190,8 @@ public void Build_WithAllParameters_SetsAllExpectedAttributes() spanId: spanId, parentSpanId: parentSpanId, thoughtProcess: thoughtProcess, - hiringManagerId: hiringManagerId); + hiringManagerId: hiringManagerId, + callerDetails: callerDetails); // Assert var attrs = data.Attributes; @@ -202,6 +204,11 @@ public void Build_WithAllParameters_SetsAllExpectedAttributes() attrs.Should().ContainKey(OpenTelemetryConstants.GenAiOutputMessagesKey); attrs.Should().ContainKey(OpenTelemetryConstants.GenAiAgentThoughtProcessKey).WhoseValue.Should().Be(thoughtProcess); attrs.Should().ContainKey(OpenTelemetryConstants.HiringManagerIdKey).WhoseValue.Should().Be("hiring-manager-inf-456"); + attrs.Should().ContainKey(OpenTelemetryConstants.GenAiCallerIdKey).WhoseValue.Should().Be("caller-inf-123"); + attrs.Should().ContainKey(OpenTelemetryConstants.GenAiCallerNameKey).WhoseValue.Should().Be("Caller Inf Name"); + attrs.Should().ContainKey(OpenTelemetryConstants.GenAiCallerUpnKey).WhoseValue.Should().Be("callerinf@example.com"); + attrs.Should().ContainKey(OpenTelemetryConstants.GenAiCallerClientIpKey).WhoseValue.Should().Be("192.168.1.100"); + attrs.Should().ContainKey(OpenTelemetryConstants.GenAiCallerTenantIdKey).WhoseValue.Should().Be("caller-tenant-inf"); data.StartTime.Should().Be(start); data.EndTime.Should().Be(end); data.Duration.Should().BeCloseTo(end - start, TimeSpan.FromMilliseconds(100)); 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 9097f022..d1d8171a 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 @@ -213,6 +213,7 @@ public void Build_WithAllParameters_SetsAllExpectedAttributes() var parentSpanId = "parent-all"; var responseContent = "tool-response"; var hiringManagerId = "hiring-manager-tool-123"; + var callerDetails = new CallerDetails("caller-tool-123", "Caller Tool Name", "callertool@example.com", System.Net.IPAddress.Parse("10.0.0.50"), "caller-tenant-tool"); // Act var data = ExecuteToolDataBuilder.Build( @@ -225,7 +226,8 @@ public void Build_WithAllParameters_SetsAllExpectedAttributes() endTime: end, spanId: spanId, parentSpanId: parentSpanId, - hiringManagerId: hiringManagerId); + hiringManagerId: hiringManagerId, + callerDetails: callerDetails); // Assert var attrs = data.Attributes; @@ -236,6 +238,11 @@ public void Build_WithAllParameters_SetsAllExpectedAttributes() attrs.Should().ContainKey(OpenTelemetryConstants.GenAiConversationIdKey); attrs.Should().ContainKey(OpenTelemetryConstants.GenAiEventContent); attrs.Should().ContainKey(OpenTelemetryConstants.HiringManagerIdKey).WhoseValue.Should().Be("hiring-manager-tool-123"); + attrs.Should().ContainKey(OpenTelemetryConstants.GenAiCallerIdKey).WhoseValue.Should().Be("caller-tool-123"); + attrs.Should().ContainKey(OpenTelemetryConstants.GenAiCallerNameKey).WhoseValue.Should().Be("Caller Tool Name"); + attrs.Should().ContainKey(OpenTelemetryConstants.GenAiCallerUpnKey).WhoseValue.Should().Be("callertool@example.com"); + attrs.Should().ContainKey(OpenTelemetryConstants.GenAiCallerClientIpKey).WhoseValue.Should().Be("10.0.0.50"); + attrs.Should().ContainKey(OpenTelemetryConstants.GenAiCallerTenantIdKey).WhoseValue.Should().Be("caller-tenant-tool"); data.StartTime.Should().Be(start); data.EndTime.Should().Be(end); data.Duration.Should().BeCloseTo(end - start, TimeSpan.FromMilliseconds(100)); diff --git a/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Etw/EtwLoggingBuilderTests.cs b/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Etw/EtwLoggingBuilderTests.cs index 5efe66d1..3e85dd68 100644 --- a/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Etw/EtwLoggingBuilderTests.cs +++ b/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Etw/EtwLoggingBuilderTests.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using System.Diagnostics.Tracing; +using System.Net; using System.Text.Json; namespace Microsoft.Agents.A365.Observability.Runtime.Tests.Etw @@ -37,10 +38,11 @@ public void Build_AddsEtwLogProcessor_AndWritesExpectedAttributes_FromInvokeAgen var tenantDetails = new TenantDetails(Guid.NewGuid()); var agentDetails = new AgentDetails("agent-id", agentName: "agent-name", agentType: AgentType.MicrosoftCopilot); var invokeAgentDetails = new InvokeAgentDetails(endpoint: new Uri("https://example.com/agent"), details: agentDetails, sessionId: "session-1"); + var callerDetails = new CallerDetails(callerId: "caller-id-1", callerName: "Caller Name", callerUpn: "caller@example.com", callerClientIP: IPAddress.Parse("192.168.1.100"), tenantId: "caller-tenant-id"); string conversationId = "conv-123"; // Act - logger.LogInvokeAgent(invokeAgentDetails, tenantDetails, conversationId); + logger.LogInvokeAgent(invokeAgentDetails, tenantDetails, conversationId, callerDetails: callerDetails); // Assert var evt = listener.Events.Find(e => e.EventId == 2000); @@ -65,6 +67,11 @@ public void Build_AddsEtwLogProcessor_AndWritesExpectedAttributes_FromInvokeAgen var tenantIdString = attrsElement.GetProperty(OpenTelemetryConstants.TenantIdKey).GetString(); Assert.IsTrue(Guid.TryParse(tenantIdString, out var parsedTenant)); Assert.AreEqual(tenantDetails.TenantId, parsedTenant); + Assert.AreEqual("caller-id-1", attrsElement.GetProperty(OpenTelemetryConstants.GenAiCallerIdKey).GetString()); + Assert.AreEqual("Caller Name", attrsElement.GetProperty(OpenTelemetryConstants.GenAiCallerNameKey).GetString()); + Assert.AreEqual("caller@example.com", attrsElement.GetProperty(OpenTelemetryConstants.GenAiCallerUpnKey).GetString()); + Assert.AreEqual("192.168.1.100", attrsElement.GetProperty(OpenTelemetryConstants.GenAiCallerClientIpKey).GetString()); + Assert.AreEqual("caller-tenant-id", attrsElement.GetProperty(OpenTelemetryConstants.GenAiCallerTenantIdKey).GetString()); } [TestMethod] @@ -80,9 +87,10 @@ public void Build_AddsEtwLogProcessor_AndWritesExpectedAttributes_FromInferenceC var inferenceDetails = new InferenceCallDetails(InferenceOperationType.Chat, "model-x", "provider-y"); string conversationId = "conv-inf-1"; var source = new SourceMetadata(id: "src-id", name: "ChannelInf", role: Role.Human, description: "https://channel/inf"); + var callerDetails = new CallerDetails(callerId: "inf-caller-id", callerName: "Inference Caller", callerUpn: "infcaller@example.com", callerClientIP: IPAddress.Parse("10.0.0.50")); // Act - logger.LogInferenceCall(inferenceDetails, agentDetails, tenantDetails, conversationId, inputMessages: new[] { "hello" }, outputMessages: new[] { "world" }, sourceMetadata: source); + logger.LogInferenceCall(inferenceDetails, agentDetails, tenantDetails, conversationId, inputMessages: new[] { "hello" }, outputMessages: new[] { "world" }, sourceMetadata: source, callerDetails: callerDetails); // Assert var evt = listener.Events.Find(e => e.EventId == 2000); @@ -110,6 +118,10 @@ public void Build_AddsEtwLogProcessor_AndWritesExpectedAttributes_FromInferenceC var tenantIdString = attrsElement.GetProperty(OpenTelemetryConstants.TenantIdKey).GetString(); Assert.IsTrue(Guid.TryParse(tenantIdString, out var parsedTenant)); Assert.AreEqual(tenantDetails.TenantId, parsedTenant); + Assert.AreEqual("inf-caller-id", attrsElement.GetProperty(OpenTelemetryConstants.GenAiCallerIdKey).GetString()); + Assert.AreEqual("Inference Caller", attrsElement.GetProperty(OpenTelemetryConstants.GenAiCallerNameKey).GetString()); + Assert.AreEqual("infcaller@example.com", attrsElement.GetProperty(OpenTelemetryConstants.GenAiCallerUpnKey).GetString()); + Assert.AreEqual("10.0.0.50", attrsElement.GetProperty(OpenTelemetryConstants.GenAiCallerClientIpKey).GetString()); } [TestMethod] @@ -126,9 +138,10 @@ public void Build_AddsEtwLogProcessor_AndWritesExpectedAttributes_FromToolCall() string conversationId = "conv-tool-1"; string responseContent = @"{ ""value"": ""result"" }"; var source = new SourceMetadata(id: "src-id", name: "ChannelInf", role: Role.Human, description: "https://channel/inf"); + var callerDetails = new CallerDetails(callerId: "tool-caller-id", callerName: "Tool Caller", callerUpn: "toolcaller@example.com", tenantId: "tool-caller-tenant"); // Act - logger.LogToolCall(toolDetails, agentDetails, tenantDetails, conversationId, responseContent: responseContent, sourceMetadata: source); + logger.LogToolCall(toolDetails, agentDetails, tenantDetails, conversationId, responseContent: responseContent, sourceMetadata: source, callerDetails: callerDetails); // Assert var evt = listener.Events.Find(e => e.EventId == 2000); @@ -158,6 +171,10 @@ public void Build_AddsEtwLogProcessor_AndWritesExpectedAttributes_FromToolCall() var tenantIdString = attrsElement.GetProperty(OpenTelemetryConstants.TenantIdKey).GetString(); Assert.IsTrue(Guid.TryParse(tenantIdString, out var parsedTenant)); Assert.AreEqual(tenantDetails.TenantId, parsedTenant); + Assert.AreEqual("tool-caller-id", attrsElement.GetProperty(OpenTelemetryConstants.GenAiCallerIdKey).GetString()); + Assert.AreEqual("Tool Caller", attrsElement.GetProperty(OpenTelemetryConstants.GenAiCallerNameKey).GetString()); + Assert.AreEqual("toolcaller@example.com", attrsElement.GetProperty(OpenTelemetryConstants.GenAiCallerUpnKey).GetString()); + Assert.AreEqual("tool-caller-tenant", attrsElement.GetProperty(OpenTelemetryConstants.GenAiCallerTenantIdKey).GetString()); } [TestMethod] 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 46d106ef..3bcc5f65 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 @@ -229,4 +229,33 @@ public void Start_ToolServerName_IsSetCorrectly() // Assert activity.ShouldHaveTag(OpenTelemetryConstants.GenAiToolServerNameKey, expectedToolServerName); } + + [TestMethod] + public void Start_SetsCallerDetails_WhenProvided() + { + // Arrange + var callerDetails = new CallerDetails( + callerId: "caller-123", + callerName: "Test Caller", + callerUpn: "caller@example.com", + callerClientIP: System.Net.IPAddress.Parse("192.168.1.1"), + tenantId: "tenant-456"); + + // Act + var activity = ListenForActivity(() => + { + using var scope = ExecuteToolScope.Start( + new ToolCallDetails("TestTool", "args"), + Util.GetAgentDetails(), + Util.GetTenantDetails(), + callerDetails: callerDetails); + }); + + // Assert + activity.ShouldHaveTag(OpenTelemetryConstants.GenAiCallerIdKey, callerDetails.CallerId); + activity.ShouldHaveTag(OpenTelemetryConstants.GenAiCallerNameKey, callerDetails.CallerName); + activity.ShouldHaveTag(OpenTelemetryConstants.GenAiCallerUpnKey, callerDetails.CallerUpn); + activity.ShouldHaveTag(OpenTelemetryConstants.GenAiCallerClientIpKey, callerDetails.CallerClientIP!.ToString()); + activity.ShouldHaveTag(OpenTelemetryConstants.GenAiCallerTenantIdKey, callerDetails.TenantId!); + } } \ No newline at end of file diff --git a/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Tracing/Scopes/InferenceScopeTest.cs b/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Tracing/Scopes/InferenceScopeTest.cs index 82896b7f..798f2d2e 100644 --- a/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Tracing/Scopes/InferenceScopeTest.cs +++ b/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Tracing/Scopes/InferenceScopeTest.cs @@ -247,4 +247,37 @@ public void RecordThoughtProcess_SetsTag() activity.ShouldHaveTag(OpenTelemetryConstants.GenAiAgentThoughtProcessKey, thoughtProcess); } + + [TestMethod] + public void Start_SetsCallerDetails_WhenProvided() + { + // Arrange + var callerDetails = new CallerDetails( + callerId: "caller-inf-123", + callerName: "Inference Caller", + callerUpn: "caller-inf@example.com", + callerClientIP: System.Net.IPAddress.Parse("10.0.0.1"), + tenantId: "tenant-inf-456"); + var details = new InferenceCallDetails( + InferenceOperationType.Chat, + "gpt-4o", + "openai"); + + // Act + var activity = ListenForActivity(() => + { + using var scope = InferenceScope.Start( + details, + Util.GetAgentDetails(), + Util.GetTenantDetails(), + callerDetails: callerDetails); + }); + + // Assert + activity.ShouldHaveTag(OpenTelemetryConstants.GenAiCallerIdKey, callerDetails.CallerId); + activity.ShouldHaveTag(OpenTelemetryConstants.GenAiCallerNameKey, callerDetails.CallerName); + activity.ShouldHaveTag(OpenTelemetryConstants.GenAiCallerUpnKey, callerDetails.CallerUpn); + activity.ShouldHaveTag(OpenTelemetryConstants.GenAiCallerClientIpKey, callerDetails.CallerClientIP!.ToString()); + activity.ShouldHaveTag(OpenTelemetryConstants.GenAiCallerTenantIdKey, callerDetails.TenantId!); + } }