From 362652b966986d794ea905ac52d77b918f201d8a Mon Sep 17 00:00:00 2001 From: wuweng Date: Tue, 7 Oct 2025 16:16:32 -0700 Subject: [PATCH] .NET: Add Azure Monitor OpenTelemetry Exporter samples (#1231) * Add Azure Monitor OpenTelemetry Exporter to enhance observability in agent samples * add another sample code for application isnights --- dotnet/agent-framework-dotnet.slnx | 1 + .../Agent_Step08_Observability.csproj | 1 + .../Agent_Step08_Observability/Program.cs | 12 ++- .../ApplicationInsights.csproj | 21 ++++ .../ApplicationInsights/Program.cs | 96 +++++++++++++++++++ 5 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 dotnet/samples/GettingStarted/Workflows/Observability/ApplicationInsights/ApplicationInsights.csproj create mode 100644 dotnet/samples/GettingStarted/Workflows/Observability/ApplicationInsights/Program.cs diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx index 81a2da4a49..400b35d298 100644 --- a/dotnet/agent-framework-dotnet.slnx +++ b/dotnet/agent-framework-dotnet.slnx @@ -113,6 +113,7 @@ + diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step08_Observability/Agent_Step08_Observability.csproj b/dotnet/samples/GettingStarted/Agents/Agent_Step08_Observability/Agent_Step08_Observability.csproj index 1f8c39c55f..980e282641 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step08_Observability/Agent_Step08_Observability.csproj +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step08_Observability/Agent_Step08_Observability.csproj @@ -11,6 +11,7 @@ + diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step08_Observability/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step08_Observability/Program.cs index 2826eb06b0..4990f9716a 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step08_Observability/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step08_Observability/Program.cs @@ -4,6 +4,7 @@ using Azure.AI.OpenAI; using Azure.Identity; +using Azure.Monitor.OpenTelemetry.Exporter; using Microsoft.Agents.AI; using OpenAI; using OpenTelemetry; @@ -11,6 +12,7 @@ using OpenTelemetry.Trace; var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; +var applicationInsightsConnectionString = Environment.GetEnvironmentVariable("APPLICATIONINSIGHTS_CONNECTION_STRING"); const string JokerName = "Joker"; const string JokerInstructions = "You are good at telling jokes."; @@ -18,10 +20,14 @@ const string JokerInstructions = "You are good at telling jokes."; // Create TracerProvider with console exporter // This will output the telemetry data to the console. string sourceName = Guid.NewGuid().ToString("N"); -using var tracerProvider = Sdk.CreateTracerProviderBuilder() +var tracerProviderBuilder = Sdk.CreateTracerProviderBuilder() .AddSource(sourceName) - .AddConsoleExporter() - .Build(); + .AddConsoleExporter(); +if (!string.IsNullOrWhiteSpace(applicationInsightsConnectionString)) +{ + tracerProviderBuilder.AddAzureMonitorTraceExporter(options => options.ConnectionString = applicationInsightsConnectionString); +} +using var tracerProvider = tracerProviderBuilder.Build(); // Create the agent, and enable OpenTelemetry instrumentation. AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()) diff --git a/dotnet/samples/GettingStarted/Workflows/Observability/ApplicationInsights/ApplicationInsights.csproj b/dotnet/samples/GettingStarted/Workflows/Observability/ApplicationInsights/ApplicationInsights.csproj new file mode 100644 index 0000000000..f7a5a4424f --- /dev/null +++ b/dotnet/samples/GettingStarted/Workflows/Observability/ApplicationInsights/ApplicationInsights.csproj @@ -0,0 +1,21 @@ + + + + Exe + net9.0 + + enable + enable + + + + + + + + + + + + + \ No newline at end of file diff --git a/dotnet/samples/GettingStarted/Workflows/Observability/ApplicationInsights/Program.cs b/dotnet/samples/GettingStarted/Workflows/Observability/ApplicationInsights/Program.cs new file mode 100644 index 0000000000..0ff7a50e8b --- /dev/null +++ b/dotnet/samples/GettingStarted/Workflows/Observability/ApplicationInsights/Program.cs @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Diagnostics; +using Azure.Monitor.OpenTelemetry.Exporter; +using Microsoft.Agents.AI.Workflows; +using Microsoft.Agents.AI.Workflows.Reflection; +using OpenTelemetry; +using OpenTelemetry.Resources; +using OpenTelemetry.Trace; + +namespace WorkflowObservabilitySample; + +/// +/// This sample shows how to enable observability in a workflow and send the traces +/// to be visualized in Application Insights. +/// +/// In this example, we create a simple text processing pipeline that: +/// 1. Takes input text and converts it to uppercase using an UppercaseExecutor +/// 2. Takes the uppercase text and reverses it using a ReverseTextExecutor +/// +/// The executors are connected sequentially, so data flows from one to the next in order. +/// For input "Hello, World!", the workflow produces "!DLROW ,OLLEH". +/// +public static class Program +{ + private const string SourceName = "Workflow.ApplicationInsightsSample"; + private static readonly ActivitySource s_activitySource = new(SourceName); + + private static async Task Main() + { + var applicationInsightsConnectionString = Environment.GetEnvironmentVariable("APPLICATIONINSIGHTS_CONNECTION_STRING") ?? throw new InvalidOperationException("APPLICATIONINSIGHTS_CONNECTION_STRING is not set."); + + var resourceBuilder = ResourceBuilder + .CreateDefault() + .AddService("WorkflowSample"); + + using var traceProvider = Sdk.CreateTracerProviderBuilder() + .SetResourceBuilder(resourceBuilder) + .AddSource("Microsoft.Agents.AI.Workflows*") + .AddSource(SourceName) + .AddAzureMonitorTraceExporter(options => options.ConnectionString = applicationInsightsConnectionString) + .Build(); + + // Start a root activity for the application + using var activity = s_activitySource.StartActivity("main"); + Console.WriteLine($"Operation/Trace ID: {Activity.Current?.TraceId}"); + + // Create the executors + UppercaseExecutor uppercase = new(); + ReverseTextExecutor reverse = new(); + + // Build the workflow by connecting executors sequentially + var workflow = new WorkflowBuilder(uppercase) + .AddEdge(uppercase, reverse) + .Build(); + + // Execute the workflow with input data + Run run = await InProcessExecution.RunAsync(workflow, "Hello, World!"); + foreach (WorkflowEvent evt in run.NewEvents) + { + if (evt is ExecutorCompletedEvent executorComplete) + { + Console.WriteLine($"{executorComplete.ExecutorId}: {executorComplete.Data}"); + } + } + } +} + +/// +/// First executor: converts input text to uppercase. +/// +internal sealed class UppercaseExecutor() : ReflectingExecutor("UppercaseExecutor"), IMessageHandler +{ + /// + /// Processes the input message by converting it to uppercase. + /// + /// The input text to convert + /// Workflow context for accessing workflow services and adding events + /// The input text converted to uppercase + public async ValueTask HandleAsync(string message, IWorkflowContext context) => + message.ToUpperInvariant(); // The return value will be sent as a message along an edge to subsequent executors +} + +/// +/// Second executor: reverses the input text and completes the workflow. +/// +internal sealed class ReverseTextExecutor() : ReflectingExecutor("ReverseTextExecutor"), IMessageHandler +{ + /// + /// Processes the input message by reversing the text. + /// + /// The input text to reverse + /// Workflow context for accessing workflow services and adding events + /// The input text reversed + public async ValueTask HandleAsync(string message, IWorkflowContext context) => new string(message.Reverse().ToArray()); +}