mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
ca580a8316
* Initial plan * Add WorkflowErrorEvent and ExecutorFailedEvent error checking to all workflow samples Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/c5d77400-d7ed-4fbe-9103-f5d74aabcf2b Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com> * Fix if/else if consistency for error event handlers per code review feedback Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/c5d77400-d7ed-4fbe-9103-f5d74aabcf2b Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com> * Address PR comments * fixup: PR comments --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com> Co-authored-by: Jacob Alber <jaalber@microsoft.com>
121 lines
5.4 KiB
C#
121 lines
5.4 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
using System.Diagnostics;
|
|
using Microsoft.Agents.AI.Workflows;
|
|
using OpenTelemetry;
|
|
using OpenTelemetry.Logs;
|
|
using OpenTelemetry.Metrics;
|
|
using OpenTelemetry.Resources;
|
|
using OpenTelemetry.Trace;
|
|
|
|
namespace WorkflowObservabilitySample;
|
|
|
|
/// <summary>
|
|
/// This sample shows how to enable observability in a workflow and send the traces
|
|
/// to be visualized in Aspire Dashboard.
|
|
///
|
|
/// 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".
|
|
/// </summary>
|
|
public static class Program
|
|
{
|
|
private const string SourceName = "Workflow.Sample";
|
|
private static readonly ActivitySource s_activitySource = new(SourceName);
|
|
|
|
private static async Task Main()
|
|
{
|
|
// Configure OpenTelemetry for Aspire dashboard
|
|
var otlpEndpoint = Environment.GetEnvironmentVariable("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4317";
|
|
|
|
var resourceBuilder = ResourceBuilder
|
|
.CreateDefault()
|
|
.AddService("WorkflowSample");
|
|
|
|
using var traceProvider = Sdk.CreateTracerProviderBuilder()
|
|
.SetResourceBuilder(resourceBuilder)
|
|
.AddSource(SourceName)
|
|
// The following source is only required if not specifying
|
|
// the `activitySource` in the WithOpenTelemetry call below
|
|
.AddSource("Microsoft.Agents.AI.Workflows*")
|
|
.AddOtlpExporter(options => options.Endpoint = new Uri(otlpEndpoint))
|
|
.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)
|
|
.WithOpenTelemetry(
|
|
// Set `EnableSensitiveData` to true to include message content in traces
|
|
configure: cfg => cfg.EnableSensitiveData = true,
|
|
activitySource: s_activitySource)
|
|
.Build();
|
|
|
|
// Execute the workflow with input data
|
|
await using 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}");
|
|
}
|
|
else if (evt is WorkflowErrorEvent workflowError)
|
|
{
|
|
Console.ForegroundColor = ConsoleColor.Red;
|
|
Console.Error.WriteLine(workflowError.Exception?.ToString() ?? "Unknown workflow error occurred.");
|
|
Console.ResetColor();
|
|
}
|
|
else if (evt is ExecutorFailedEvent executorFailed)
|
|
{
|
|
Console.ForegroundColor = ConsoleColor.Red;
|
|
Console.Error.WriteLine($"Executor '{executorFailed.ExecutorId}' failed with {(executorFailed.Data == null ? "unknown error" : $"exception {executorFailed.Data}")}.");
|
|
Console.ResetColor();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// First executor: converts input text to uppercase.
|
|
/// </summary>
|
|
internal sealed class UppercaseExecutor() : Executor<string, string>("UppercaseExecutor")
|
|
{
|
|
/// <summary>
|
|
/// Processes the input message by converting it to uppercase.
|
|
/// </summary>
|
|
/// <param name="message">The input text to convert</param>
|
|
/// <param name="context">Workflow context for accessing workflow services and adding events</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
|
|
/// The default is <see cref="CancellationToken.None"/>.</param>
|
|
/// <returns>The input text converted to uppercase</returns>
|
|
public override async ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default) =>
|
|
message.ToUpperInvariant(); // The return value will be sent as a message along an edge to subsequent executors
|
|
}
|
|
|
|
/// <summary>
|
|
/// Second executor: reverses the input text and completes the workflow.
|
|
/// </summary>
|
|
internal sealed class ReverseTextExecutor() : Executor<string, string>("ReverseTextExecutor")
|
|
{
|
|
/// <summary>
|
|
/// Processes the input message by reversing the text.
|
|
/// </summary>
|
|
/// <param name="message">The input text to reverse</param>
|
|
/// <param name="context">Workflow context for accessing workflow services and adding events</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
|
|
/// The default is <see cref="CancellationToken.None"/>.</param>
|
|
/// <returns>The input text reversed</returns>
|
|
public override async ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
|
=> new(message.Reverse().ToArray());
|
|
}
|