Skip to content

Commit 85921ed

Browse files
authored
.NET: Sample 06_subWorkflows to showcase the sub workflows (workflow composability) (#1493)
* Sample 06_subWorkflows ready. * minor fix
1 parent 7fb8bcf commit 85921ed

File tree

3 files changed

+175
-1
lines changed

3 files changed

+175
-1
lines changed

dotnet/agent-framework-dotnet.slnx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
</Folder>
2424
<Folder Name="/Samples/GettingStarted/A2A/">
2525
<File Path="samples/GettingStarted/A2A/README.md" />
26-
<Project Path="samples/GettingStarted/A2A/A2AAgent_AsFunctionTools/A2AAgent_AsFunctionTools.csproj"/>
26+
<Project Path="samples/GettingStarted/A2A/A2AAgent_AsFunctionTools/A2AAgent_AsFunctionTools.csproj" />
2727
</Folder>
2828
<Folder Name="/Samples/GettingStarted/AgentProviders/">
2929
<File Path="samples/GettingStarted/AgentProviders/README.md" />
@@ -129,6 +129,7 @@
129129
<Project Path="samples/GettingStarted/Workflows/_Foundational/03_AgentsInWorkflows/03_AgentsInWorkflows.csproj" />
130130
<Project Path="samples/GettingStarted/Workflows/_Foundational/04_AgentWorkflowPatterns/04_AgentWorkflowPatterns.csproj" />
131131
<Project Path="samples/GettingStarted/Workflows/_Foundational/05_MultiModelService/05_MultiModelService.csproj" />
132+
<Project Path="samples/GettingStarted/Workflows/_Foundational/06_SubWorkflows/06_SubWorkflows.csproj" />
132133
</Folder>
133134
<Folder Name="/Samples/SemanticKernelMigration/">
134135
<File Path="samples/SemanticKernelMigration/README.md" />
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net9.0</TargetFramework>
6+
7+
<Nullable>enable</Nullable>
8+
<ImplicitUsings>enable</ImplicitUsings>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Workflows\Microsoft.Agents.AI.Workflows.csproj" />
13+
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
14+
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
15+
</ItemGroup>
16+
17+
</Project>
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using Microsoft.Agents.AI.Workflows;
4+
5+
namespace WorkflowSubWorkflowsSample;
6+
7+
/// <summary>
8+
/// This sample demonstrates how to compose workflows hierarchically by using
9+
/// a workflow as an executor within another workflow (sub-workflows).
10+
///
11+
/// A sub-workflow is a workflow that is embedded as an executor within a parent workflow.
12+
/// This allows you to:
13+
/// 1. Encapsulate and reuse complex workflow logic as modular components
14+
/// 2. Build hierarchical workflow structures
15+
/// 3. Create composable, maintainable workflow architectures
16+
///
17+
/// In this example, we create:
18+
/// - A text processing sub-workflow (uppercase → reverse → append suffix)
19+
/// - A parent workflow that adds a prefix, processes through the sub-workflow, and post-processes
20+
///
21+
/// For input "hello", the workflow produces: "INPUT: [FINAL] OLLEH [PROCESSED] [END]"
22+
/// </summary>
23+
public static class Program
24+
{
25+
private static async Task Main()
26+
{
27+
Console.WriteLine("\n=== Sub-Workflow Demonstration ===\n");
28+
29+
// Step 1: Build a simple text processing sub-workflow
30+
Console.WriteLine("Building sub-workflow: Uppercase → Reverse → Append Suffix...\n");
31+
32+
UppercaseExecutor uppercase = new();
33+
ReverseExecutor reverse = new();
34+
AppendSuffixExecutor append = new(" [PROCESSED]");
35+
36+
var subWorkflow = new WorkflowBuilder(uppercase)
37+
.AddEdge(uppercase, reverse)
38+
.AddEdge(reverse, append)
39+
.WithOutputFrom(append)
40+
.Build();
41+
42+
// Step 2: Configure the sub-workflow as an executor for use in the parent workflow
43+
ExecutorIsh subWorkflowExecutor = subWorkflow.ConfigureSubWorkflow("TextProcessingSubWorkflow");
44+
45+
// Step 3: Build a main workflow that uses the sub-workflow as an executor
46+
Console.WriteLine("Building main workflow that uses the sub-workflow as an executor...\n");
47+
48+
PrefixExecutor prefix = new("INPUT: ");
49+
PostProcessExecutor postProcess = new();
50+
51+
var mainWorkflow = new WorkflowBuilder(prefix)
52+
.AddEdge(prefix, subWorkflowExecutor)
53+
.AddEdge(subWorkflowExecutor, postProcess)
54+
.WithOutputFrom(postProcess)
55+
.Build();
56+
57+
// Step 4: Execute the main workflow
58+
Console.WriteLine("Executing main workflow with input: 'hello'\n");
59+
await using Run run = await InProcessExecution.RunAsync(mainWorkflow, "hello");
60+
61+
// Display results
62+
foreach (WorkflowEvent evt in run.NewEvents)
63+
{
64+
if (evt is ExecutorCompletedEvent executorComplete && executorComplete.Data is not null)
65+
{
66+
Console.ForegroundColor = ConsoleColor.Green;
67+
Console.WriteLine($"[{executorComplete.ExecutorId}] {executorComplete.Data}");
68+
Console.ResetColor();
69+
}
70+
else if (evt is WorkflowOutputEvent output)
71+
{
72+
Console.ForegroundColor = ConsoleColor.Cyan;
73+
Console.WriteLine("\n=== Main Workflow Completed ===");
74+
Console.WriteLine($"Final Output: {output.Data}");
75+
Console.ResetColor();
76+
}
77+
}
78+
79+
// Optional: Visualize the workflow structure - Note that sub-workflows are not rendered
80+
Console.ForegroundColor = ConsoleColor.DarkGray;
81+
Console.WriteLine("\n=== Workflow Visualization ===\n");
82+
Console.WriteLine(mainWorkflow.ToMermaidString());
83+
Console.ResetColor();
84+
85+
Console.WriteLine("\n✅ Sample Complete: Workflows can be composed hierarchically using sub-workflows\n");
86+
}
87+
}
88+
89+
// ====================================
90+
// Text Processing Executors
91+
// ====================================
92+
93+
/// <summary>
94+
/// Adds a prefix to the input text.
95+
/// </summary>
96+
internal sealed class PrefixExecutor(string prefix) : Executor<string, string>("PrefixExecutor")
97+
{
98+
public override ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
99+
{
100+
string result = prefix + message;
101+
Console.WriteLine($"[Prefix] '{message}' → '{result}'");
102+
return ValueTask.FromResult(result);
103+
}
104+
}
105+
106+
/// <summary>
107+
/// Converts input text to uppercase.
108+
/// </summary>
109+
internal sealed class UppercaseExecutor() : Executor<string, string>("UppercaseExecutor")
110+
{
111+
public override ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
112+
{
113+
string result = message.ToUpperInvariant();
114+
Console.WriteLine($"[Uppercase] '{message}' → '{result}'");
115+
return ValueTask.FromResult(result);
116+
}
117+
}
118+
119+
/// <summary>
120+
/// Reverses the input text.
121+
/// </summary>
122+
internal sealed class ReverseExecutor() : Executor<string, string>("ReverseExecutor")
123+
{
124+
public override ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
125+
{
126+
string result = string.Concat(message.Reverse());
127+
Console.WriteLine($"[Reverse] '{message}' → '{result}'");
128+
return ValueTask.FromResult(result);
129+
}
130+
}
131+
132+
/// <summary>
133+
/// Appends a suffix to the input text.
134+
/// </summary>
135+
internal sealed class AppendSuffixExecutor(string suffix) : Executor<string, string>("AppendSuffixExecutor")
136+
{
137+
public override ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
138+
{
139+
string result = message + suffix;
140+
Console.WriteLine($"[AppendSuffix] '{message}' → '{result}'");
141+
return ValueTask.FromResult(result);
142+
}
143+
}
144+
145+
/// <summary>
146+
/// Performs final post-processing by wrapping the text.
147+
/// </summary>
148+
internal sealed class PostProcessExecutor() : Executor<string, string>("PostProcessExecutor")
149+
{
150+
public override ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
151+
{
152+
string result = $"[FINAL] {message} [END]";
153+
Console.WriteLine($"[PostProcess] '{message}' → '{result}'");
154+
return ValueTask.FromResult(result);
155+
}
156+
}

0 commit comments

Comments
 (0)