refactor: [BREAKING] Remove generic Workflow<T> (#1551)

Remove input type checking in favour of explicit `.DescribeProtocolAsync()` flow. Also removes `.AsAgentAsync()` as the validation happens at workflow run time. This makes it easier to use Workflows with DI without resorting to async-over-sync.
This commit is contained in:
Jacob Alber
2025-10-20 20:13:41 -04:00
committed by GitHub
Unverified
parent 7c1e3db846
commit 0ef4e739d5
43 changed files with 284 additions and 461 deletions
@@ -48,7 +48,7 @@ public static class Program
.Build();
// Execute the workflow
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, "Create a slogan for a new electric SUV that is affordable and fun to drive.");
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, input: "Create a slogan for a new electric SUV that is affordable and fun to drive.");
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
{
if (evt is SloganGeneratedEvent or FeedbackEvent)
@@ -35,7 +35,7 @@ public static class Program
var chatClient = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()).GetChatClient(deploymentName).AsIChatClient();
// Create the workflow and turn it into an agent
var workflow = await WorkflowHelper.GetWorkflowAsync(chatClient);
var workflow = WorkflowHelper.GetWorkflow(chatClient);
var agent = workflow.AsAgent("workflow-agent", "Workflow Agent");
var thread = agent.GetNewThread();
@@ -13,7 +13,7 @@ internal static class WorkflowHelper
/// </summary>
/// <param name="chatClient">The chat client to use for the agents</param>
/// <returns>A workflow that processes input using two language agents</returns>
internal static ValueTask<Workflow<List<ChatMessage>>> GetWorkflowAsync(IChatClient chatClient)
internal static Workflow GetWorkflow(IChatClient chatClient)
{
// Create executors
var startExecutor = new ConcurrentStartExecutor();
@@ -26,7 +26,7 @@ internal static class WorkflowHelper
.AddFanOutEdge(startExecutor, targets: [frenchAgent, englishAgent])
.AddFanInEdge(aggregationExecutor, sources: [frenchAgent, englishAgent])
.WithOutputFrom(aggregationExecutor)
.BuildAsync<List<ChatMessage>>();
.Build();
}
/// <summary>
@@ -25,7 +25,7 @@ public static class Program
private static async Task Main()
{
// Create the workflow
var workflow = await WorkflowHelper.GetWorkflowAsync();
var workflow = WorkflowHelper.GetWorkflow();
// Create checkpoint manager
var checkpointManager = CheckpointManager.Default;
@@ -67,7 +67,7 @@ public static class Program
Console.WriteLine($"Number of checkpoints created: {checkpoints.Count}");
// Rehydrate a new workflow instance from a saved checkpoint and continue execution
var newWorkflow = await WorkflowHelper.GetWorkflowAsync();
var newWorkflow = WorkflowHelper.GetWorkflow();
const int CheckpointIndex = 5;
Console.WriteLine($"\n\nHydrating a new workflow instance from the {CheckpointIndex + 1}th checkpoint.");
CheckpointInfo savedCheckpoint = checkpoints[CheckpointIndex];
@@ -13,7 +13,7 @@ internal static class WorkflowHelper
/// 2. JudgeExecutor: Evaluates the guess and provides feedback.
/// The workflow continues until the correct number is guessed.
/// </summary>
internal static ValueTask<Workflow<NumberSignal>> GetWorkflowAsync()
internal static Workflow GetWorkflow()
{
// Create the executors
GuessNumberExecutor guessNumberExecutor = new(1, 100);
@@ -24,7 +24,7 @@ internal static class WorkflowHelper
.AddEdge(guessNumberExecutor, judgeExecutor)
.AddEdge(judgeExecutor, guessNumberExecutor)
.WithOutputFrom(judgeExecutor)
.BuildAsync<NumberSignal>();
.Build();
}
}
@@ -24,7 +24,7 @@ public static class Program
private static async Task Main()
{
// Create the workflow
var workflow = await WorkflowHelper.GetWorkflowAsync();
var workflow = WorkflowHelper.GetWorkflow();
// Create checkpoint manager
var checkpointManager = CheckpointManager.Default;
@@ -13,7 +13,7 @@ internal static class WorkflowHelper
/// 2. JudgeExecutor: Evaluates the guess and provides feedback.
/// The workflow continues until the correct number is guessed.
/// </summary>
internal static ValueTask<Workflow<NumberSignal>> GetWorkflowAsync()
internal static Workflow GetWorkflow()
{
// Create the executors
GuessNumberExecutor guessNumberExecutor = new(1, 100);
@@ -24,7 +24,7 @@ internal static class WorkflowHelper
.AddEdge(guessNumberExecutor, judgeExecutor)
.AddEdge(judgeExecutor, guessNumberExecutor)
.WithOutputFrom(judgeExecutor)
.BuildAsync<NumberSignal>();
.Build();
}
}
@@ -27,7 +27,7 @@ public static class Program
private static async Task Main()
{
// Create the workflow
var workflow = await WorkflowHelper.GetWorkflowAsync();
var workflow = WorkflowHelper.GetWorkflow();
// Create checkpoint manager
var checkpointManager = CheckpointManager.Default;
@@ -10,7 +10,7 @@ internal static class WorkflowHelper
/// Get a workflow that plays a number guessing game with human-in-the-loop interaction.
/// An input port allows the external world to provide inputs to the workflow upon requests.
/// </summary>
internal static ValueTask<Workflow<SignalWithNumber>> GetWorkflowAsync()
internal static Workflow GetWorkflow()
{
// Create the executors
RequestPort numberRequest = RequestPort.Create<SignalWithNumber, int>("GuessNumber");
@@ -21,7 +21,7 @@ internal static class WorkflowHelper
.AddEdge(numberRequest, judgeExecutor)
.AddEdge(judgeExecutor, numberRequest)
.WithOutputFrom(judgeExecutor)
.BuildAsync<SignalWithNumber>();
.Build();
}
}
@@ -58,7 +58,7 @@ public static class Program
.Build();
// Execute the workflow in streaming mode
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, "What is temperature?");
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, input: "What is temperature?");
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
{
if (evt is WorkflowOutputEvent output)
@@ -99,7 +99,7 @@ public static class Program
// Step 2: Run the workflow
Console.WriteLine("\n=== RUNNING WORKFLOW ===\n");
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, rawText);
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, input: rawText);
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
{
Console.WriteLine($"Event: {evt}");
@@ -44,7 +44,7 @@ internal sealed class Program
// Run the workflow, just like any other workflow
string input = this.GetWorkflowInput();
StreamingRun run = await InProcessExecution.StreamAsync(workflow, input);
StreamingRun run = await InProcessExecution.StreamAsync(workflow, input: input);
await this.MonitorAndDisposeWorkflowRunAsync(run);
Notify("\nWORKFLOW: Done!");
@@ -24,7 +24,7 @@ public static class Program
private static async Task Main()
{
// Create the workflow
var workflow = await WorkflowHelper.GetWorkflowAsync();
var workflow = WorkflowHelper.GetWorkflow();
// Execute the workflow
await using StreamingRun handle = await InProcessExecution.StreamAsync(workflow, NumberSignal.Init);
@@ -10,7 +10,7 @@ internal static class WorkflowHelper
/// Get a workflow that plays a number guessing game with human-in-the-loop interaction.
/// An input port allows the external world to provide inputs to the workflow upon requests.
/// </summary>
internal static ValueTask<Workflow<NumberSignal>> GetWorkflowAsync()
internal static Workflow GetWorkflow()
{
// Create the executors
RequestPort numberRequestPort = RequestPort.Create<NumberSignal, int>("GuessNumber");
@@ -21,7 +21,7 @@ internal static class WorkflowHelper
.AddEdge(numberRequestPort, judgeExecutor)
.AddEdge(judgeExecutor, numberRequestPort)
.WithOutputFrom(judgeExecutor)
.BuildAsync<NumberSignal>();
.Build();
}
}
@@ -25,11 +25,11 @@ public static class Program
JudgeExecutor judgeExecutor = new("Judge", 42);
// Build the workflow by connecting executors in a loop
var workflow = await new WorkflowBuilder(guessNumberExecutor)
var workflow = new WorkflowBuilder(guessNumberExecutor)
.AddEdge(guessNumberExecutor, judgeExecutor)
.AddEdge(judgeExecutor, guessNumberExecutor)
.WithOutputFrom(judgeExecutor)
.BuildAsync<NumberSignal>();
.Build();
// Execute the workflow
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, NumberSignal.Init);
@@ -28,7 +28,7 @@ public static class Program
var workflow = builder.Build();
// Execute the workflow in streaming mode
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, "Hello, World!");
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, input: "Hello, World!");
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
{
if (evt is ExecutorCompletedEvent executorCompleted)
@@ -57,8 +57,7 @@ AIAgent reporter = new ChatClientAgent(anthropic,
description: "Summarize the researcher's essay into a single paragraph, focusing only on the fact checker's confirmed facts.");
// Build a sequential workflow: Researcher -> Fact-Checker -> Reporter
AIAgent workflowAgent = await AgentWorkflowBuilder.BuildSequential(researcher, factChecker, reporter)
.AsAgentAsync();
AIAgent workflowAgent = AgentWorkflowBuilder.BuildSequential(researcher, factChecker, reporter).AsAgent();
// Run the workflow, streaming the output as it arrives.
string? lastAuthor = null;