-
Notifications
You must be signed in to change notification settings - Fork 1.1k
.NET: Add workflow as an agent with observability sample #1612
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
lokitoth
merged 7 commits into
microsoft:main
from
TaoChenOSU:taochen/dotnet-workflow-as-an-agent-observability-sample
Nov 3, 2025
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
56e587a
Add workflow as an agent with observability sample
TaoChenOSU 3615609
Address comment
TaoChenOSU c430b86
Fix formatting
TaoChenOSU 5e1f670
Merge branch 'main' into local-branch-workflow-as-agent-observability…
TaoChenOSU 7d64e9f
enable sensitive data
TaoChenOSU cc71e3c
enable sensitive data for sub agents
TaoChenOSU 5978527
adjust aggregator handlers
TaoChenOSU File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
140 changes: 140 additions & 0 deletions
140
dotnet/samples/GettingStarted/Workflows/Observability/WorkflowAsAnAgent/Program.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,140 @@ | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
|
|
||
| using System.Diagnostics; | ||
| using Azure.AI.OpenAI; | ||
| using Azure.Identity; | ||
| using Azure.Monitor.OpenTelemetry.Exporter; | ||
| using Microsoft.Agents.AI; | ||
| using Microsoft.Agents.AI.Workflows; | ||
| using Microsoft.Extensions.AI; | ||
| using OpenTelemetry; | ||
| using OpenTelemetry.Resources; | ||
| using OpenTelemetry.Trace; | ||
|
|
||
| namespace WorkflowAsAnAgentObservabilitySample; | ||
|
|
||
| /// <summary> | ||
| /// This sample shows how to enable OpenTelemetry observability for workflows when | ||
| /// using them as <see cref="AIAgent"/>s. | ||
| /// | ||
| /// In this example, we create a workflow that uses two language agents to process | ||
| /// input concurrently, one that responds in French and another that responds in English. | ||
| /// | ||
| /// You will interact with the workflow in an interactive loop, sending messages and receiving | ||
| /// streaming responses from the workflow as if it were an agent who responds in both languages. | ||
| /// | ||
| /// OpenTelemetry observability is enabled at multiple levels: | ||
| /// 1. At the chat client level, capturing telemetry for interactions with the Azure OpenAI service. | ||
| /// 2. At the agent level, capturing telemetry for agent operations. | ||
| /// 3. At the workflow level, capturing telemetry for workflow execution. | ||
| /// | ||
| /// Traces will be sent to an Aspire dashboard via an OTLP endpoint, and optionally to | ||
| /// Azure Monitor if an Application Insights connection string is provided. | ||
| /// | ||
| /// Learn how to set up an Aspire dashboard here: | ||
| /// https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/dashboard/standalone?tabs=bash | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Pre-requisites: | ||
| /// - Foundational samples should be completed first. | ||
| /// - This sample uses concurrent processing. | ||
| /// - An Azure OpenAI endpoint and deployment name. | ||
| /// - An Application Insights resource for telemetry (optional). | ||
| /// </remarks> | ||
| public static class Program | ||
| { | ||
| private const string SourceName = "Workflow.ApplicationInsightsSample"; | ||
| private static readonly ActivitySource s_activitySource = new(SourceName); | ||
|
|
||
| private static async Task Main() | ||
| { | ||
| // Set up observability | ||
| var applicationInsightsConnectionString = Environment.GetEnvironmentVariable("APPLICATIONINSIGHTS_CONNECTION_STRING"); | ||
| var otlpEndpoint = Environment.GetEnvironmentVariable("OTLP_ENDPOINT") ?? "http://localhost:4317"; | ||
|
|
||
| var resourceBuilder = ResourceBuilder | ||
| .CreateDefault() | ||
| .AddService("WorkflowSample"); | ||
|
|
||
| var traceProviderBuilder = Sdk.CreateTracerProviderBuilder() | ||
| .SetResourceBuilder(resourceBuilder) | ||
| .AddSource("Microsoft.Agents.AI.*") // Agent Framework telemetry | ||
| .AddSource("Microsoft.Extensions.AI.*") // Extensions AI telemetry | ||
| .AddSource(SourceName); | ||
|
|
||
| traceProviderBuilder.AddOtlpExporter(options => options.Endpoint = new Uri(otlpEndpoint)); | ||
| if (!string.IsNullOrWhiteSpace(applicationInsightsConnectionString)) | ||
| { | ||
| traceProviderBuilder.AddAzureMonitorTraceExporter(options => options.ConnectionString = applicationInsightsConnectionString); | ||
| } | ||
|
|
||
| using var traceProvider = traceProviderBuilder.Build(); | ||
|
|
||
| // Set up the Azure OpenAI client | ||
| 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 chatClient = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()) | ||
| .GetChatClient(deploymentName) | ||
| .AsIChatClient() | ||
| .AsBuilder() | ||
| .UseOpenTelemetry(sourceName: SourceName, configure: (cfg) => cfg.EnableSensitiveData = true) // enable telemetry at the chat client level | ||
| .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 workflow and turn it into an agent with OpenTelemetry instrumentation | ||
| var workflow = WorkflowHelper.GetWorkflow(chatClient, SourceName); | ||
| var agent = new OpenTelemetryAgent(workflow.AsAgent("workflow-agent", "Workflow Agent"), SourceName) | ||
| { | ||
| EnableSensitiveData = true // enable sensitive data at the agent level such as prompts and responses | ||
| }; | ||
| var thread = agent.GetNewThread(); | ||
|
|
||
| // Start an interactive loop to interact with the workflow as if it were an agent | ||
| while (true) | ||
| { | ||
| Console.WriteLine(); | ||
| Console.Write("User (or 'exit' to quit): "); | ||
| string? input = Console.ReadLine(); | ||
| if (string.IsNullOrWhiteSpace(input) || input.Equals("exit", StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| break; | ||
| } | ||
|
|
||
| await ProcessInputAsync(agent, thread, input); | ||
| } | ||
|
|
||
| // Helper method to process user input and display streaming responses. To display | ||
| // multiple interleaved responses correctly, we buffer updates by message ID and | ||
| // re-render all messages on each update. | ||
| static async Task ProcessInputAsync(AIAgent agent, AgentThread thread, string input) | ||
| { | ||
| Dictionary<string, List<AgentRunResponseUpdate>> buffer = []; | ||
| await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync(input, thread)) | ||
| { | ||
| if (update.MessageId is null || string.IsNullOrEmpty(update.Text)) | ||
| { | ||
| // skip updates that don't have a message ID or text | ||
| continue; | ||
| } | ||
| Console.Clear(); | ||
|
|
||
| if (!buffer.TryGetValue(update.MessageId, out List<AgentRunResponseUpdate>? value)) | ||
| { | ||
| value = []; | ||
| buffer[update.MessageId] = value; | ||
| } | ||
| value.Add(update); | ||
|
|
||
| foreach (var (messageId, segments) in buffer) | ||
| { | ||
| string combinedText = string.Concat(segments); | ||
| Console.WriteLine($"{segments[0].AuthorName}: {combinedText}"); | ||
| Console.WriteLine(); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
27 changes: 27 additions & 0 deletions
27
...ngStarted/Workflows/Observability/WorkflowAsAnAgent/WorkflowAsAnAgentObservability.csproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <OutputType>Exe</OutputType> | ||
| <TargetFramework>net9.0</TargetFramework> | ||
|
|
||
| <Nullable>enable</Nullable> | ||
| <ImplicitUsings>enable</ImplicitUsings> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="Azure.AI.OpenAI" /> | ||
| <PackageReference Include="Azure.Identity" /> | ||
| <PackageReference Include="Azure.Monitor.OpenTelemetry.Exporter" /> | ||
| <PackageReference Include="Microsoft.Extensions.AI.OpenAI" /> | ||
| <PackageReference Include="OpenTelemetry" /> | ||
| <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" /> | ||
| <PackageReference Include="System.Diagnostics.DiagnosticSource" /> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Workflows\Microsoft.Agents.AI.Workflows.csproj" /> | ||
| <ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" /> | ||
| <ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.