.NET: Sample 06_subWorkflows to showcase the sub workflows (workflow composability) (#1493)

* Sample 06_subWorkflows ready.

* minor fix
This commit is contained in:
Jose Luis Latorre Millas
2025-10-16 20:11:43 +02:00
committed by GitHub
Unverified
parent 7fb8bcf660
commit 85921eda68
3 changed files with 175 additions and 1 deletions
+2 -1
View File
@@ -23,7 +23,7 @@
</Folder>
<Folder Name="/Samples/GettingStarted/A2A/">
<File Path="samples/GettingStarted/A2A/README.md" />
<Project Path="samples/GettingStarted/A2A/A2AAgent_AsFunctionTools/A2AAgent_AsFunctionTools.csproj"/>
<Project Path="samples/GettingStarted/A2A/A2AAgent_AsFunctionTools/A2AAgent_AsFunctionTools.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/AgentProviders/">
<File Path="samples/GettingStarted/AgentProviders/README.md" />
@@ -129,6 +129,7 @@
<Project Path="samples/GettingStarted/Workflows/_Foundational/03_AgentsInWorkflows/03_AgentsInWorkflows.csproj" />
<Project Path="samples/GettingStarted/Workflows/_Foundational/04_AgentWorkflowPatterns/04_AgentWorkflowPatterns.csproj" />
<Project Path="samples/GettingStarted/Workflows/_Foundational/05_MultiModelService/05_MultiModelService.csproj" />
<Project Path="samples/GettingStarted/Workflows/_Foundational/06_SubWorkflows/06_SubWorkflows.csproj" />
</Folder>
<Folder Name="/Samples/SemanticKernelMigration/">
<File Path="samples/SemanticKernelMigration/README.md" />
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<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>
@@ -0,0 +1,156 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows;
namespace WorkflowSubWorkflowsSample;
/// <summary>
/// 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]"
/// </summary>
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
// ====================================
/// <summary>
/// Adds a prefix to the input text.
/// </summary>
internal sealed class PrefixExecutor(string prefix) : Executor<string, string>("PrefixExecutor")
{
public override ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
string result = prefix + message;
Console.WriteLine($"[Prefix] '{message}' → '{result}'");
return ValueTask.FromResult(result);
}
}
/// <summary>
/// Converts input text to uppercase.
/// </summary>
internal sealed class UppercaseExecutor() : Executor<string, string>("UppercaseExecutor")
{
public override ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
string result = message.ToUpperInvariant();
Console.WriteLine($"[Uppercase] '{message}' → '{result}'");
return ValueTask.FromResult(result);
}
}
/// <summary>
/// Reverses the input text.
/// </summary>
internal sealed class ReverseExecutor() : Executor<string, string>("ReverseExecutor")
{
public override ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
string result = string.Concat(message.Reverse());
Console.WriteLine($"[Reverse] '{message}' → '{result}'");
return ValueTask.FromResult(result);
}
}
/// <summary>
/// Appends a suffix to the input text.
/// </summary>
internal sealed class AppendSuffixExecutor(string suffix) : Executor<string, string>("AppendSuffixExecutor")
{
public override ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
string result = message + suffix;
Console.WriteLine($"[AppendSuffix] '{message}' → '{result}'");
return ValueTask.FromResult(result);
}
}
/// <summary>
/// Performs final post-processing by wrapping the text.
/// </summary>
internal sealed class PostProcessExecutor() : Executor<string, string>("PostProcessExecutor")
{
public override ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
string result = $"[FINAL] {message} [END]";
Console.WriteLine($"[PostProcess] '{message}' → '{result}'");
return ValueTask.FromResult(result);
}
}