Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Diagnostics;
using Microsoft.Agents.A365.Observability.Runtime.Tracing.Contracts;

Expand Down Expand Up @@ -30,6 +31,8 @@ public sealed class ExecuteToolScope : OpenTelemetryScope
/// <param name="sourceMetadata">Optional metadata describing the source of the call (e.g., component, file, line) for observability.</param>
/// <param name="threatDiagnosticsSummary">Optional threat diagnostics summary containing security-related information about blocked actions.</param>
/// <param name="callerDetails">Optional details about the non-agentic caller.</param>
/// <param name="startTime">Optional explicit start time. Useful when recording a tool call after execution has already completed.</param>
/// <param name="endTime">Optional explicit end time. When provided, the span will use this timestamp when disposed instead of the current wall-clock time.</param>
/// <returns>A new ExecuteToolScope instance.</returns>
/// <remarks>
/// <para>
Expand All @@ -44,15 +47,17 @@ public sealed class ExecuteToolScope : OpenTelemetryScope
/// <see href="https://go.microsoft.com/fwlink/?linkid=2344479">Learn more about certification requirements</see>
/// </para>
/// </remarks>
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);
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, DateTimeOffset? startTime = null, DateTimeOffset? endTime = null) => new ExecuteToolScope(details, agentDetails, tenantDetails, parentId, conversationId, sourceMetadata, threatDiagnosticsSummary, callerDetails, startTime, endTime);

private ExecuteToolScope(ToolCallDetails details, AgentDetails agentDetails, TenantDetails tenantDetails, string? parentId = null, string? conversationId = null, SourceMetadata? sourceMetadata = null, ThreatDiagnosticsSummary? threatDiagnosticsSummary = null, CallerDetails? callerDetails = null)
private ExecuteToolScope(ToolCallDetails details, AgentDetails agentDetails, TenantDetails tenantDetails, string? parentId = null, string? conversationId = null, SourceMetadata? sourceMetadata = null, ThreatDiagnosticsSummary? threatDiagnosticsSummary = null, CallerDetails? callerDetails = null, DateTimeOffset? startTime = null, DateTimeOffset? endTime = null)
: base(
kind: ActivityKind.Internal,
agentDetails: agentDetails,
tenantDetails: tenantDetails,
operationName: OperationName,
activityName: $"{OperationName} {details.ToolName}",
startTime: startTime,
endTime: endTime,
parentId: parentId,
conversationId: conversationId,
sourceMetadata: sourceMetadata,
Expand Down
9 changes: 7 additions & 2 deletions src/Observability/Runtime/Tracing/Scopes/InferenceScope.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Diagnostics;
using Microsoft.Agents.A365.Observability.Runtime.Tracing.Contracts;
using static Microsoft.Agents.A365.Observability.Runtime.Tracing.Scopes.OpenTelemetryConstants;
Expand All @@ -25,6 +26,8 @@ public sealed class InferenceScope : OpenTelemetryScope
/// <param name="conversationId">Optional conversation or session correlation ID for the inference.</param>
/// <param name="sourceMetadata">Optional metadata describing the source of the call (e.g., component, file, line) for observability.</param>
/// <param name="callerDetails">Optional details about the non-agentic caller.</param>
/// <param name="startTime">Optional explicit start time. Useful when recording an inference call after execution has already completed.</param>
/// <param name="endTime">Optional explicit end time. When provided, the span will use this timestamp when disposed instead of the current wall-clock time.</param>
/// <returns>A new InferenceScope instance.</returns>
/// <remarks>
/// <para>
Expand All @@ -39,15 +42,17 @@ public sealed class InferenceScope : OpenTelemetryScope
/// <see href="https://go.microsoft.com/fwlink/?linkid=2344479">Learn more about certification requirements</see>
/// </para>
/// </remarks>
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);
public static InferenceScope Start(InferenceCallDetails details, AgentDetails agentDetails, TenantDetails tenantDetails, string? parentId = null, string? conversationId = null, SourceMetadata? sourceMetadata = null, CallerDetails? callerDetails = null, DateTimeOffset? startTime = null, DateTimeOffset? endTime = null) => new InferenceScope(details, agentDetails, tenantDetails, parentId, conversationId, sourceMetadata, callerDetails, startTime, endTime);

private InferenceScope(InferenceCallDetails details, AgentDetails agentDetails, TenantDetails tenantDetails, string? parentId = null, string? conversationId = null, SourceMetadata? sourceMetadata = null, CallerDetails? callerDetails = null)
private InferenceScope(InferenceCallDetails details, AgentDetails agentDetails, TenantDetails tenantDetails, string? parentId = null, string? conversationId = null, SourceMetadata? sourceMetadata = null, CallerDetails? callerDetails = null, DateTimeOffset? startTime = null, DateTimeOffset? endTime = null)
: base(
kind: ActivityKind.Client,
agentDetails: agentDetails,
tenantDetails: tenantDetails,
operationName: details.OperationName.ToString(),
activityName: $"{details.OperationName} {details.Model}",
startTime: startTime,
endTime: endTime,
parentId: parentId,
conversationId: conversationId,
sourceMetadata: sourceMetadata,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Diagnostics;
using Microsoft.Agents.A365.Observability.Runtime.Tracing.Contracts;

Expand Down Expand Up @@ -31,6 +32,8 @@ public sealed class InvokeAgentScope : OpenTelemetryScope
/// <param name="callerDetails">The details of the non-agentic caller.</param>
/// <param name="conversationId">The conversation ID for the agent invocation.</param>
/// <param name="threatDiagnosticsSummary">Optional threat diagnostics summary containing security-related information about blocked actions.</param>
/// <param name="startTime">Optional explicit start time. Useful when recording an agent invocation after execution has already completed.</param>
/// <param name="endTime">Optional explicit end time. When provided, the span will use this timestamp when disposed instead of the current wall-clock time.</param>
/// <returns>A new InvokeAgentScope instance.</returns>
/// <remarks>
/// <para>
Expand All @@ -50,9 +53,9 @@ public sealed class InvokeAgentScope : OpenTelemetryScope
/// </para>
/// </remarks>
public static InvokeAgentScope Start(
InvokeAgentDetails invokeAgentDetails, TenantDetails tenantDetails, Request? request = null, AgentDetails? callerAgentDetails = null, CallerDetails? callerDetails = null, string? conversationId = null, ThreatDiagnosticsSummary? threatDiagnosticsSummary = null) => new InvokeAgentScope(invokeAgentDetails, tenantDetails, request, callerAgentDetails, callerDetails, conversationId, threatDiagnosticsSummary);
InvokeAgentDetails invokeAgentDetails, TenantDetails tenantDetails, Request? request = null, AgentDetails? callerAgentDetails = null, CallerDetails? callerDetails = null, string? conversationId = null, ThreatDiagnosticsSummary? threatDiagnosticsSummary = null, DateTimeOffset? startTime = null, DateTimeOffset? endTime = null) => new InvokeAgentScope(invokeAgentDetails, tenantDetails, request, callerAgentDetails, callerDetails, conversationId, threatDiagnosticsSummary, startTime, endTime);

private InvokeAgentScope(InvokeAgentDetails invokeAgentDetails, TenantDetails tenantDetails, Request? request, AgentDetails? callerAgentDetails, CallerDetails? callerDetails, string? conversationId, ThreatDiagnosticsSummary? threatDiagnosticsSummary)
private InvokeAgentScope(InvokeAgentDetails invokeAgentDetails, TenantDetails tenantDetails, Request? request, AgentDetails? callerAgentDetails, CallerDetails? callerDetails, string? conversationId, ThreatDiagnosticsSummary? threatDiagnosticsSummary, DateTimeOffset? startTime, DateTimeOffset? endTime)
: base(
kind: ActivityKind.Client,
agentDetails: invokeAgentDetails.Details,
Expand All @@ -61,6 +64,8 @@ private InvokeAgentScope(InvokeAgentDetails invokeAgentDetails, TenantDetails te
activityName: string.IsNullOrWhiteSpace(invokeAgentDetails.Details.AgentName)
? OperationName
: $"invoke_agent {invokeAgentDetails.Details.AgentName}",
startTime: startTime,
endTime: endTime,
conversationId: conversationId,
sourceMetadata: request?.SourceMetadata,
callerDetails: callerDetails)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,15 @@ public abstract class OpenTelemetryScope : IDisposable
/// <param name="operationName">The name of the operation being traced.</param>
/// <param name="activityName">The name of the activity for display purposes.</param>
/// <param name="startTime">Optional custom start time for the scope. If not provided, the current time is used.</param>
/// <param name="endTime">Optional custom end time for the scope. When provided, the span will use this timestamp when disposed instead of the current wall-clock time.</param>
/// <param name="parentId">Optional parent ID for the activity.</param>
/// <param name="conversationId">Optional conversation id.</param>
/// <param name="sourceMetadata">Optional source metadata.</param>
/// <param name="callerDetails">Optional details about the non-agentic caller.</param>
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)
protected OpenTelemetryScope(ActivityKind kind, AgentDetails agentDetails, TenantDetails tenantDetails, string operationName, string activityName, DateTimeOffset? startTime = null, DateTimeOffset? endTime = null, string? parentId = null, string? conversationId = null, SourceMetadata? sourceMetadata = null, CallerDetails? callerDetails = null)
{
customStartTime = startTime;
customEndTime = endTime;
activity = ActivitySource.CreateActivity(activityName, kind, default(ActivityContext));
if (!string.IsNullOrEmpty(parentId))
{
Expand Down
11 changes: 8 additions & 3 deletions src/Observability/Runtime/Tracing/Scopes/OutputScope.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.Agents.A365.Observability.Runtime.Tracing.Contracts;
Expand All @@ -26,17 +27,21 @@ public sealed class OutputScope : OpenTelemetryScope
/// <param name="tenantDetails">Tenant context used for telemetry enrichment and correlation.</param>
/// <param name="response">Response containing output messages.</param>
/// <param name="parentId">Optional parent Activity ID used to link this span to an upstream operation.</param>
/// <param name="startTime">Optional explicit start time. Useful when recording an output operation after execution has already completed.</param>
/// <param name="endTime">Optional explicit end time. When provided, the span will use this timestamp when disposed instead of the current wall-clock time.</param>
/// <returns>A new OutputScope instance.</returns>
public static OutputScope Start(AgentDetails agentDetails, TenantDetails tenantDetails, Response response, string? parentId = null)
=> new OutputScope(agentDetails, tenantDetails, response, parentId);
public static OutputScope Start(AgentDetails agentDetails, TenantDetails tenantDetails, Response response, string? parentId = null, DateTimeOffset? startTime = null, DateTimeOffset? endTime = null)
=> new OutputScope(agentDetails, tenantDetails, response, parentId, startTime, endTime);

private OutputScope(AgentDetails agentDetails, TenantDetails tenantDetails, Response response, string? parentId)
private OutputScope(AgentDetails agentDetails, TenantDetails tenantDetails, Response response, string? parentId, DateTimeOffset? startTime, DateTimeOffset? endTime)
: base(
kind: ActivityKind.Client,
agentDetails: agentDetails,
tenantDetails: tenantDetails,
operationName: OperationName,
activityName: $"{OperationName} {agentDetails?.AgentId}",
startTime: startTime,
endTime: endTime,
parentId: parentId)
{
if (response.Messages.Count > 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,4 +258,73 @@ public void Start_SetsCallerDetails_WhenProvided()
activity.ShouldHaveTag(OpenTelemetryConstants.GenAiCallerClientIpKey, callerDetails.CallerClientIP!.ToString());
activity.ShouldHaveTag(OpenTelemetryConstants.GenAiCallerTenantIdKey, callerDetails.TenantId!);
}

[TestMethod]
public void Start_WithCustomStartTime_SetsActivityStartTime()
{
// Arrange
var customStartTime = new DateTimeOffset(2023, 11, 14, 22, 13, 20, TimeSpan.Zero);

// Act
var activity = ListenForActivity(() =>
{
using var scope = ExecuteToolScope.Start(
new ToolCallDetails("TestTool", "args"),
Util.GetAgentDetails(),
Util.GetTenantDetails(),
startTime: customStartTime);
});

// Assert
var startTime = new DateTimeOffset(activity.StartTimeUtc);
startTime.Should().BeCloseTo(customStartTime, TimeSpan.FromMilliseconds(100));
}

[TestMethod]
public void Start_WithCustomStartAndEndTime_SetsActivityTimes()
{
// Arrange
var customStartTime = new DateTimeOffset(2023, 11, 14, 22, 13, 20, TimeSpan.Zero);
var customEndTime = new DateTimeOffset(2023, 11, 14, 22, 13, 25, TimeSpan.Zero); // 5 seconds later

// Act
var activity = ListenForActivity(() =>
{
using var scope = ExecuteToolScope.Start(
new ToolCallDetails("TestTool", "args"),
Util.GetAgentDetails(),
Util.GetTenantDetails(),
startTime: customStartTime,
endTime: customEndTime);
});

// Assert - Start time should be set to custom time
var startTime = new DateTimeOffset(activity.StartTimeUtc);
startTime.Should().BeCloseTo(customStartTime, TimeSpan.FromMilliseconds(100));
}

[TestMethod]
public void SetEndTime_OverridesEndTime()
{
// Arrange
var customStartTime = new DateTimeOffset(2023, 11, 14, 22, 13, 40, TimeSpan.Zero);
var initialEndTime = new DateTimeOffset(2023, 11, 14, 22, 13, 45, TimeSpan.Zero);
var laterEndTime = new DateTimeOffset(2023, 11, 14, 22, 13, 48, TimeSpan.Zero);

// Act
var activity = ListenForActivity(() =>
{
using var scope = ExecuteToolScope.Start(
new ToolCallDetails("TestTool", "args"),
Util.GetAgentDetails(),
Util.GetTenantDetails(),
startTime: customStartTime,
endTime: initialEndTime);
scope.SetEndTime(laterEndTime);
});

// Assert - The start time should be set
var startTime = new DateTimeOffset(activity.StartTimeUtc);
startTime.Should().BeCloseTo(customStartTime, TimeSpan.FromMilliseconds(100));
}
}
Loading