From 85921eda6860cf0b597b3715c1913e51beae6325 Mon Sep 17 00:00:00 2001 From: Jose Luis Latorre Millas Date: Thu, 16 Oct 2025 20:11:43 +0200 Subject: [PATCH] .NET: Sample 06_subWorkflows to showcase the sub workflows (workflow composability) (#1493) * Sample 06_subWorkflows ready. * minor fix --- dotnet/agent-framework-dotnet.slnx | 3 +- .../06_SubWorkflows/06_SubWorkflows.csproj | 17 ++ .../_Foundational/06_SubWorkflows/Program.cs | 156 ++++++++++++++++++ 3 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 dotnet/samples/GettingStarted/Workflows/_Foundational/06_SubWorkflows/06_SubWorkflows.csproj create mode 100644 dotnet/samples/GettingStarted/Workflows/_Foundational/06_SubWorkflows/Program.cs diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx index 0a594b4237..9d3b86535c 100644 --- a/dotnet/agent-framework-dotnet.slnx +++ b/dotnet/agent-framework-dotnet.slnx @@ -23,7 +23,7 @@ - + @@ -129,6 +129,7 @@ + diff --git a/dotnet/samples/GettingStarted/Workflows/_Foundational/06_SubWorkflows/06_SubWorkflows.csproj b/dotnet/samples/GettingStarted/Workflows/_Foundational/06_SubWorkflows/06_SubWorkflows.csproj new file mode 100644 index 0000000000..1ef94de3da --- /dev/null +++ b/dotnet/samples/GettingStarted/Workflows/_Foundational/06_SubWorkflows/06_SubWorkflows.csproj @@ -0,0 +1,17 @@ + + + + Exe + net9.0 + + enable + enable + + + + + + + + + \ No newline at end of file diff --git a/dotnet/samples/GettingStarted/Workflows/_Foundational/06_SubWorkflows/Program.cs b/dotnet/samples/GettingStarted/Workflows/_Foundational/06_SubWorkflows/Program.cs new file mode 100644 index 0000000000..de00c35ae8 --- /dev/null +++ b/dotnet/samples/GettingStarted/Workflows/_Foundational/06_SubWorkflows/Program.cs @@ -0,0 +1,156 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Agents.AI.Workflows; + +namespace WorkflowSubWorkflowsSample; + +/// +/// This sample demonstrates how to compose workflows hierarchically by using +/// a workflow as an executor within another workflow (sub-workflows). +/// +/// A sub-workflow is a workflow that is embedded as an executor within a parent workflow. +/// This allows you to: +/// 1. Encapsulate and reuse complex workflow logic as modular components +/// 2. Build hierarchical workflow structures +/// 3. Create composable, maintainable workflow architectures +/// +/// In this example, we create: +/// - A text processing sub-workflow (uppercase → reverse → append suffix) +/// - A parent workflow that adds a prefix, processes through the sub-workflow, and post-processes +/// +/// For input "hello", the workflow produces: "INPUT: [FINAL] OLLEH [PROCESSED] [END]" +/// +public static class Program +{ + private static async Task Main() + { + Console.WriteLine("\n=== Sub-Workflow Demonstration ===\n"); + + // Step 1: Build a simple text processing sub-workflow + Console.WriteLine("Building sub-workflow: Uppercase → Reverse → Append Suffix...\n"); + + UppercaseExecutor uppercase = new(); + ReverseExecutor reverse = new(); + AppendSuffixExecutor append = new(" [PROCESSED]"); + + var subWorkflow = new WorkflowBuilder(uppercase) + .AddEdge(uppercase, reverse) + .AddEdge(reverse, append) + .WithOutputFrom(append) + .Build(); + + // Step 2: Configure the sub-workflow as an executor for use in the parent workflow + ExecutorIsh subWorkflowExecutor = subWorkflow.ConfigureSubWorkflow("TextProcessingSubWorkflow"); + + // Step 3: Build a main workflow that uses the sub-workflow as an executor + Console.WriteLine("Building main workflow that uses the sub-workflow as an executor...\n"); + + PrefixExecutor prefix = new("INPUT: "); + PostProcessExecutor postProcess = new(); + + var mainWorkflow = new WorkflowBuilder(prefix) + .AddEdge(prefix, subWorkflowExecutor) + .AddEdge(subWorkflowExecutor, postProcess) + .WithOutputFrom(postProcess) + .Build(); + + // Step 4: Execute the main workflow + Console.WriteLine("Executing main workflow with input: 'hello'\n"); + await using Run run = await InProcessExecution.RunAsync(mainWorkflow, "hello"); + + // Display results + foreach (WorkflowEvent evt in run.NewEvents) + { + if (evt is ExecutorCompletedEvent executorComplete && executorComplete.Data is not null) + { + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($"[{executorComplete.ExecutorId}] {executorComplete.Data}"); + Console.ResetColor(); + } + else if (evt is WorkflowOutputEvent output) + { + Console.ForegroundColor = ConsoleColor.Cyan; + Console.WriteLine("\n=== Main Workflow Completed ==="); + Console.WriteLine($"Final Output: {output.Data}"); + Console.ResetColor(); + } + } + + // Optional: Visualize the workflow structure - Note that sub-workflows are not rendered + Console.ForegroundColor = ConsoleColor.DarkGray; + Console.WriteLine("\n=== Workflow Visualization ===\n"); + Console.WriteLine(mainWorkflow.ToMermaidString()); + Console.ResetColor(); + + Console.WriteLine("\n✅ Sample Complete: Workflows can be composed hierarchically using sub-workflows\n"); + } +} + +// ==================================== +// Text Processing Executors +// ==================================== + +/// +/// Adds a prefix to the input text. +/// +internal sealed class PrefixExecutor(string prefix) : Executor("PrefixExecutor") +{ + public override ValueTask HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default) + { + string result = prefix + message; + Console.WriteLine($"[Prefix] '{message}' → '{result}'"); + return ValueTask.FromResult(result); + } +} + +/// +/// Converts input text to uppercase. +/// +internal sealed class UppercaseExecutor() : Executor("UppercaseExecutor") +{ + public override ValueTask HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default) + { + string result = message.ToUpperInvariant(); + Console.WriteLine($"[Uppercase] '{message}' → '{result}'"); + return ValueTask.FromResult(result); + } +} + +/// +/// Reverses the input text. +/// +internal sealed class ReverseExecutor() : Executor("ReverseExecutor") +{ + public override ValueTask HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default) + { + string result = string.Concat(message.Reverse()); + Console.WriteLine($"[Reverse] '{message}' → '{result}'"); + return ValueTask.FromResult(result); + } +} + +/// +/// Appends a suffix to the input text. +/// +internal sealed class AppendSuffixExecutor(string suffix) : Executor("AppendSuffixExecutor") +{ + public override ValueTask HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default) + { + string result = message + suffix; + Console.WriteLine($"[AppendSuffix] '{message}' → '{result}'"); + return ValueTask.FromResult(result); + } +} + +/// +/// Performs final post-processing by wrapping the text. +/// +internal sealed class PostProcessExecutor() : Executor("PostProcessExecutor") +{ + public override ValueTask HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default) + { + string result = $"[FINAL] {message} [END]"; + Console.WriteLine($"[PostProcess] '{message}' → '{result}'"); + return ValueTask.FromResult(result); + } +}