diff --git a/docs/decisions/0003-agent-opentelemetry-instrumentation.md b/docs/decisions/0003-agent-opentelemetry-instrumentation.md index 863387b5cbd..527f364792a 100644 --- a/docs/decisions/0003-agent-opentelemetry-instrumentation.md +++ b/docs/decisions/0003-agent-opentelemetry-instrumentation.md @@ -124,21 +124,43 @@ The implementation is validated through: ### Usage Example +The name passed to `TracerProviderBuilder.AddSource` must match the source name the agent emits under, otherwise the +provider silently receives no agent spans. When no source name is supplied to `UseOpenTelemetry`, the agent emits under +`OpenTelemetryAgent.DefaultSourceName`, so that is the value to register. That property is marked +`[Experimental("MAAI001")]`; suppress that diagnostic in your csproj to use it. + ```csharp // Create TracerProvider using var tracerProvider = Sdk.CreateTracerProviderBuilder() - .AddSource(AgentOpenTelemetryConsts.DefaultSourceName) + .AddSource(OpenTelemetryAgent.DefaultSourceName) .AddConsoleExporter() .Build(); // Create and wrap agent with telemetry var baseAgent = new ChatClientAgent(chatClient, options); -using var telemetryAgent = baseAgent.WithOpenTelemetry(); +using var telemetryAgent = baseAgent.AsBuilder() + .UseOpenTelemetry() + .Build(); // Use agent normally - telemetry is captured automatically var response = await telemetryAgent.RunAsync(messages); ``` +To emit under a custom source name, pass the same value to both calls: + +```csharp +const string SourceName = "MyCompany.MyAgent"; + +using var tracerProvider = Sdk.CreateTracerProviderBuilder() + .AddSource(SourceName) + .AddConsoleExporter() + .Build(); + +using var telemetryAgent = baseAgent.AsBuilder() + .UseOpenTelemetry(sourceName: SourceName) + .Build(); +``` + ### Relationship to Microsoft.Extensions.AI This implementation follows the exact patterns established by Microsoft.Extensions.AI's OpenTelemetry instrumentation, ensuring consistency across the AI ecosystem and leveraging proven patterns for telemetry integration. diff --git a/dotnet/src/Microsoft.Agents.AI/OpenTelemetryAgent.cs b/dotnet/src/Microsoft.Agents.AI/OpenTelemetryAgent.cs index 90c0ec59ebb..bb94f4674a3 100644 --- a/dotnet/src/Microsoft.Agents.AI/OpenTelemetryAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI/OpenTelemetryAgent.cs @@ -41,6 +41,19 @@ public sealed class OpenTelemetryAgent : DelegatingAIAgent, IDisposable // inner agent not directly but rather via OpenTelemetryChatClient, which wraps a ForwardingChatClient that in turn // calls back into the inner agent. + /// + /// Gets the default name used by when no source + /// name is supplied to the constructor. + /// + /// + /// Pass this value to the tracing pipeline (for example, TracerProviderBuilder.AddSource) to subscribe to + /// the spans emitted by agents that use the default source name, instead of hardcoding the literal name. This is + /// a property rather than a constant so that the value is read at run time: a consumer that upgrades the package + /// picks up the current source name without recompiling. + /// + [Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)] + public static string DefaultSourceName => OpenTelemetryConsts.DefaultSourceName; + /// The providing the bulk of the telemetry. private readonly OpenTelemetryChatClient _otelClient; /// The provider name extracted from . @@ -62,8 +75,9 @@ public sealed class OpenTelemetryAgent : DelegatingAIAgent, IDisposable /// Initializes a new instance of the class. /// The underlying to be augmented with telemetry capabilities. /// - /// An optional source name that will be used to identify telemetry data from this agent. - /// If not provided, a default source name will be used for telemetry identification. + /// An optional source name used to identify telemetry data from this agent. + /// When specified, register the same value with TracerProviderBuilder.AddSource so the tracing pipeline + /// subscribes to these spans. When omitted, is used. /// /// is . /// @@ -80,8 +94,9 @@ public OpenTelemetryAgent(AIAgent innerAgent, string? sourceName = null) /// Initializes a new instance of the class. /// The underlying to be augmented with telemetry capabilities. /// - /// An optional source name that will be used to identify telemetry data from this agent. - /// If not provided, a default source name will be used for telemetry identification. + /// An optional source name used to identify telemetry data from this agent. + /// When specified, register the same value with TracerProviderBuilder.AddSource so the tracing pipeline + /// subscribes to these spans. When omitted, is used. /// /// /// When and the inner agent is a , the underlying diff --git a/dotnet/src/Microsoft.Agents.AI/OpenTelemetryAgentBuilderExtensions.cs b/dotnet/src/Microsoft.Agents.AI/OpenTelemetryAgentBuilderExtensions.cs index 8f83a8dda1e..0d27ef8d178 100644 --- a/dotnet/src/Microsoft.Agents.AI/OpenTelemetryAgentBuilderExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI/OpenTelemetryAgentBuilderExtensions.cs @@ -15,8 +15,9 @@ public static class OpenTelemetryAgentBuilderExtensions /// /// The to which OpenTelemetry support will be added. /// - /// An optional source name that will be used to identify telemetry data from this agent. - /// If not specified, a default source name will be used. + /// An optional source name used to identify telemetry data from this agent. + /// When specified, register the same value with TracerProviderBuilder.AddSource so the tracing pipeline + /// subscribes to these spans. When omitted, is used. /// /// /// An optional callback that provides additional configuration of the instance. diff --git a/dotnet/src/Microsoft.Agents.AI/PublicAPI/net10.0/PublicAPI.Unshipped.txt b/dotnet/src/Microsoft.Agents.AI/PublicAPI/net10.0/PublicAPI.Unshipped.txt index 31d026ce287..b473d4bd163 100644 --- a/dotnet/src/Microsoft.Agents.AI/PublicAPI/net10.0/PublicAPI.Unshipped.txt +++ b/dotnet/src/Microsoft.Agents.AI/PublicAPI/net10.0/PublicAPI.Unshipped.txt @@ -7,5 +7,6 @@ [MAAI001]const Microsoft.Agents.AI.FileAccessProvider.ReadLinesToolName = "file_access_read_lines" -> string! [MAAI001]static Microsoft.Agents.AI.AgentFileStore.ScanContent(string! fileName, string! content, System.Text.RegularExpressions.Regex! regex) -> Microsoft.Agents.AI.FileSearchResult? [MAAI001]static Microsoft.Agents.AI.AgentFileStore.SplitLines(string! content) -> System.Collections.Generic.IReadOnlyList! +[MAAI001]static Microsoft.Agents.AI.OpenTelemetryAgent.DefaultSourceName.get -> string! [MAAI001]virtual Microsoft.Agents.AI.AgentFileStore.FindMatchingFilesAsync(string! directory, string! regexPattern, string? globPattern = null, bool recursive = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! [MAAI001]virtual Microsoft.Agents.AI.AgentFileStore.SearchAsync(string! directory, string! regexPattern, string? globPattern = null, bool recursive = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! diff --git a/dotnet/src/Microsoft.Agents.AI/PublicAPI/net472/PublicAPI.Unshipped.txt b/dotnet/src/Microsoft.Agents.AI/PublicAPI/net472/PublicAPI.Unshipped.txt index 31d026ce287..b473d4bd163 100644 --- a/dotnet/src/Microsoft.Agents.AI/PublicAPI/net472/PublicAPI.Unshipped.txt +++ b/dotnet/src/Microsoft.Agents.AI/PublicAPI/net472/PublicAPI.Unshipped.txt @@ -7,5 +7,6 @@ [MAAI001]const Microsoft.Agents.AI.FileAccessProvider.ReadLinesToolName = "file_access_read_lines" -> string! [MAAI001]static Microsoft.Agents.AI.AgentFileStore.ScanContent(string! fileName, string! content, System.Text.RegularExpressions.Regex! regex) -> Microsoft.Agents.AI.FileSearchResult? [MAAI001]static Microsoft.Agents.AI.AgentFileStore.SplitLines(string! content) -> System.Collections.Generic.IReadOnlyList! +[MAAI001]static Microsoft.Agents.AI.OpenTelemetryAgent.DefaultSourceName.get -> string! [MAAI001]virtual Microsoft.Agents.AI.AgentFileStore.FindMatchingFilesAsync(string! directory, string! regexPattern, string? globPattern = null, bool recursive = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! [MAAI001]virtual Microsoft.Agents.AI.AgentFileStore.SearchAsync(string! directory, string! regexPattern, string? globPattern = null, bool recursive = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! diff --git a/dotnet/src/Microsoft.Agents.AI/PublicAPI/net8.0/PublicAPI.Unshipped.txt b/dotnet/src/Microsoft.Agents.AI/PublicAPI/net8.0/PublicAPI.Unshipped.txt index 31d026ce287..b473d4bd163 100644 --- a/dotnet/src/Microsoft.Agents.AI/PublicAPI/net8.0/PublicAPI.Unshipped.txt +++ b/dotnet/src/Microsoft.Agents.AI/PublicAPI/net8.0/PublicAPI.Unshipped.txt @@ -7,5 +7,6 @@ [MAAI001]const Microsoft.Agents.AI.FileAccessProvider.ReadLinesToolName = "file_access_read_lines" -> string! [MAAI001]static Microsoft.Agents.AI.AgentFileStore.ScanContent(string! fileName, string! content, System.Text.RegularExpressions.Regex! regex) -> Microsoft.Agents.AI.FileSearchResult? [MAAI001]static Microsoft.Agents.AI.AgentFileStore.SplitLines(string! content) -> System.Collections.Generic.IReadOnlyList! +[MAAI001]static Microsoft.Agents.AI.OpenTelemetryAgent.DefaultSourceName.get -> string! [MAAI001]virtual Microsoft.Agents.AI.AgentFileStore.FindMatchingFilesAsync(string! directory, string! regexPattern, string? globPattern = null, bool recursive = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! [MAAI001]virtual Microsoft.Agents.AI.AgentFileStore.SearchAsync(string! directory, string! regexPattern, string? globPattern = null, bool recursive = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! diff --git a/dotnet/src/Microsoft.Agents.AI/PublicAPI/net9.0/PublicAPI.Unshipped.txt b/dotnet/src/Microsoft.Agents.AI/PublicAPI/net9.0/PublicAPI.Unshipped.txt index 31d026ce287..b473d4bd163 100644 --- a/dotnet/src/Microsoft.Agents.AI/PublicAPI/net9.0/PublicAPI.Unshipped.txt +++ b/dotnet/src/Microsoft.Agents.AI/PublicAPI/net9.0/PublicAPI.Unshipped.txt @@ -7,5 +7,6 @@ [MAAI001]const Microsoft.Agents.AI.FileAccessProvider.ReadLinesToolName = "file_access_read_lines" -> string! [MAAI001]static Microsoft.Agents.AI.AgentFileStore.ScanContent(string! fileName, string! content, System.Text.RegularExpressions.Regex! regex) -> Microsoft.Agents.AI.FileSearchResult? [MAAI001]static Microsoft.Agents.AI.AgentFileStore.SplitLines(string! content) -> System.Collections.Generic.IReadOnlyList! +[MAAI001]static Microsoft.Agents.AI.OpenTelemetryAgent.DefaultSourceName.get -> string! [MAAI001]virtual Microsoft.Agents.AI.AgentFileStore.FindMatchingFilesAsync(string! directory, string! regexPattern, string? globPattern = null, bool recursive = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! [MAAI001]virtual Microsoft.Agents.AI.AgentFileStore.SearchAsync(string! directory, string! regexPattern, string? globPattern = null, bool recursive = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! diff --git a/dotnet/src/Microsoft.Agents.AI/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/dotnet/src/Microsoft.Agents.AI/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt index 31d026ce287..b473d4bd163 100644 --- a/dotnet/src/Microsoft.Agents.AI/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt +++ b/dotnet/src/Microsoft.Agents.AI/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt @@ -7,5 +7,6 @@ [MAAI001]const Microsoft.Agents.AI.FileAccessProvider.ReadLinesToolName = "file_access_read_lines" -> string! [MAAI001]static Microsoft.Agents.AI.AgentFileStore.ScanContent(string! fileName, string! content, System.Text.RegularExpressions.Regex! regex) -> Microsoft.Agents.AI.FileSearchResult? [MAAI001]static Microsoft.Agents.AI.AgentFileStore.SplitLines(string! content) -> System.Collections.Generic.IReadOnlyList! +[MAAI001]static Microsoft.Agents.AI.OpenTelemetryAgent.DefaultSourceName.get -> string! [MAAI001]virtual Microsoft.Agents.AI.AgentFileStore.FindMatchingFilesAsync(string! directory, string! regexPattern, string? globPattern = null, bool recursive = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! [MAAI001]virtual Microsoft.Agents.AI.AgentFileStore.SearchAsync(string! directory, string! regexPattern, string? globPattern = null, bool recursive = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/OpenTelemetryAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/OpenTelemetryAgentTests.cs index a6b609e971e..9b0370fe958 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/OpenTelemetryAgentTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/OpenTelemetryAgentTests.cs @@ -873,6 +873,17 @@ public async Task AutoWireChatClient_UserFactoryAddsOwnOTel_CoexistsWithBelowFic Assert.Equal(2, activities.Count(a => string.Equals(a.GetTagItem("gen_ai.operation.name") as string, "chat", StringComparison.Ordinal))); } + [Fact] + public void DefaultSourceName_ReturnsDocumentedSourceName() + { + // Callers pass this to TracerProviderBuilder.AddSource, so it must stay in sync with the source name the + // agent emits spans under, which Ctor_NullOrWhitespaceSourceName_AutoWiredChatClientUsesDefaultSource_Async + // pins to the same literal. Comparing against the literal here guards a rename of the internal constant. + + // Arrange & Act & Assert + Assert.Equal("Experimental.Microsoft.Agents.AI", OpenTelemetryAgent.DefaultSourceName); + } + [Theory] [InlineData(null)] [InlineData("")]