mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET: Update Workflow Input/Output Redesign (#881)
* feat: Make Executor id field mandatory When checkpointing is involved, it is critical to keep executor ids consistent between runs, even when recreating a new object tree for the workflow. The default id-setting mechanism generated a guid for part of the id, making it not work when restoring from a checkpoint. This change prevents this situation from arising. * feat: Enable running untyped Workflows With the change to enable delay-instantiation of executors and support for async Executor factory methods, we must instantiate the starting executor to know what are the valid input types for the workflow. To avoid forcing instantiation every time, and to better support workflows with multiple input types, we enable support for build and interacting with the base Workflow type without type annotations, and remove the requirement to know a valid input type when initiating a run. * feat: Support Output from any executor and multiple outputs.
This commit is contained in:
committed by
GitHub
Unverified
parent
03ef7f054f
commit
39e071c430
+10
-11
@@ -2,7 +2,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Agents.Workflows;
|
||||
|
||||
@@ -29,7 +28,7 @@ public static class Program
|
||||
private static async Task Main()
|
||||
{
|
||||
// Create the workflow
|
||||
var workflow = WorkflowHelper.GetWorkflow();
|
||||
var workflow = await WorkflowHelper.GetWorkflowAsync().ConfigureAwait(false);
|
||||
|
||||
// Create checkpoint manager
|
||||
var checkpointManager = CheckpointManager.Default;
|
||||
@@ -58,9 +57,9 @@ public static class Program
|
||||
}
|
||||
}
|
||||
|
||||
if (evt is WorkflowCompletedEvent workflowCompletedEvt)
|
||||
if (evt is WorkflowOutputEvent outputEvent)
|
||||
{
|
||||
Console.WriteLine($"Workflow completed with result: {workflowCompletedEvt.Data}");
|
||||
Console.WriteLine($"Workflow completed with result: {outputEvent.Data}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,15 +70,15 @@ 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 = WorkflowHelper.GetWorkflow();
|
||||
var newWorkflow = await WorkflowHelper.GetWorkflowAsync().ConfigureAwait(false);
|
||||
const int CheckpointIndex = 5;
|
||||
Console.WriteLine($"\n\nHydrating a new workflow instance from the {CheckpointIndex + 1}th checkpoint.");
|
||||
CheckpointInfo savedCheckpoint = checkpoints[CheckpointIndex];
|
||||
|
||||
Checkpointed<StreamingRun> newCheckpointedRun = await InProcessExecution
|
||||
.StreamAsync(newWorkflow, NumberSignal.Init, checkpointManager)
|
||||
.ConfigureAwait(false);
|
||||
await newCheckpointedRun.RestoreCheckpointAsync(savedCheckpoint, CancellationToken.None).ConfigureAwait(false);
|
||||
Checkpointed<StreamingRun> newCheckpointedRun =
|
||||
await InProcessExecution.ResumeStreamAsync(newWorkflow, savedCheckpoint, checkpointManager, checkpointedRun.Run.RunId)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await foreach (WorkflowEvent evt in newCheckpointedRun.Run.WatchStreamAsync().ConfigureAwait(false))
|
||||
{
|
||||
if (evt is ExecutorCompletedEvent executorCompletedEvt)
|
||||
@@ -87,9 +86,9 @@ public static class Program
|
||||
Console.WriteLine($"* Executor {executorCompletedEvt.ExecutorId} completed.");
|
||||
}
|
||||
|
||||
if (evt is WorkflowCompletedEvent workflowCompletedEvt)
|
||||
if (evt is WorkflowOutputEvent workflowOutputEvt)
|
||||
{
|
||||
Console.WriteLine($"Workflow completed with result: {workflowCompletedEvt.Data}");
|
||||
Console.WriteLine($"Workflow completed with result: {workflowOutputEvt.Data}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -16,7 +16,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 Workflow<NumberSignal> GetWorkflow()
|
||||
internal static ValueTask<Workflow<NumberSignal>> GetWorkflowAsync()
|
||||
{
|
||||
// Create the executors
|
||||
GuessNumberExecutor guessNumberExecutor = new(1, 100);
|
||||
@@ -26,7 +26,8 @@ internal static class WorkflowHelper
|
||||
return new WorkflowBuilder(guessNumberExecutor)
|
||||
.AddEdge(guessNumberExecutor, judgeExecutor)
|
||||
.AddEdge(judgeExecutor, guessNumberExecutor)
|
||||
.Build<NumberSignal>();
|
||||
.WithOutputFrom(judgeExecutor)
|
||||
.BuildAsync<NumberSignal>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,8 +127,7 @@ internal sealed class JudgeExecutor() : ReflectingExecutor<JudgeExecutor>("Judge
|
||||
this._tries++;
|
||||
if (message == this._targetNumber)
|
||||
{
|
||||
await context.AddEventAsync(new WorkflowCompletedEvent($"{this._targetNumber} found in {this._tries} tries!"))
|
||||
.ConfigureAwait(false);
|
||||
await context.YieldOutputAsync($"{this._targetNumber} found in {this._tries} tries!").ConfigureAwait(false);
|
||||
}
|
||||
else if (message < this._targetNumber)
|
||||
{
|
||||
|
||||
@@ -28,7 +28,7 @@ public static class Program
|
||||
private static async Task Main()
|
||||
{
|
||||
// Create the workflow
|
||||
var workflow = WorkflowHelper.GetWorkflow();
|
||||
var workflow = await WorkflowHelper.GetWorkflowAsync().ConfigureAwait(false);
|
||||
|
||||
// Create checkpoint manager
|
||||
var checkpointManager = CheckpointManager.Default;
|
||||
@@ -57,9 +57,9 @@ public static class Program
|
||||
}
|
||||
}
|
||||
|
||||
if (evt is WorkflowCompletedEvent workflowCompletedEvt)
|
||||
if (evt is WorkflowOutputEvent workflowOutputEvt)
|
||||
{
|
||||
Console.WriteLine($"Workflow completed with result: {workflowCompletedEvt.Data}");
|
||||
Console.WriteLine($"Workflow completed with result: {workflowOutputEvt.Data}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,9 +82,9 @@ public static class Program
|
||||
Console.WriteLine($"* Executor {executorCompletedEvt.ExecutorId} completed.");
|
||||
}
|
||||
|
||||
if (evt is WorkflowCompletedEvent workflowCompletedEvt)
|
||||
if (evt is WorkflowOutputEvent workflowOutputEvt)
|
||||
{
|
||||
Console.WriteLine($"Workflow completed with result: {workflowCompletedEvt.Data}");
|
||||
Console.WriteLine($"Workflow completed with result: {workflowOutputEvt.Data}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -16,7 +16,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 Workflow<NumberSignal> GetWorkflow()
|
||||
internal static ValueTask<Workflow<NumberSignal>> GetWorkflowAsync()
|
||||
{
|
||||
// Create the executors
|
||||
GuessNumberExecutor guessNumberExecutor = new(1, 100);
|
||||
@@ -26,7 +26,8 @@ internal static class WorkflowHelper
|
||||
return new WorkflowBuilder(guessNumberExecutor)
|
||||
.AddEdge(guessNumberExecutor, judgeExecutor)
|
||||
.AddEdge(judgeExecutor, guessNumberExecutor)
|
||||
.Build<NumberSignal>();
|
||||
.WithOutputFrom(judgeExecutor)
|
||||
.BuildAsync<NumberSignal>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,8 +127,7 @@ internal sealed class JudgeExecutor() : ReflectingExecutor<JudgeExecutor>("Judge
|
||||
this._tries++;
|
||||
if (message == this._targetNumber)
|
||||
{
|
||||
await context.AddEventAsync(new WorkflowCompletedEvent($"{this._targetNumber} found in {this._tries} tries!"))
|
||||
.ConfigureAwait(false);
|
||||
await context.YieldOutputAsync($"{this._targetNumber} found in {this._tries} tries!").ConfigureAwait(false);
|
||||
}
|
||||
else if (message < this._targetNumber)
|
||||
{
|
||||
|
||||
+5
-5
@@ -31,7 +31,7 @@ public static class Program
|
||||
private static async Task Main()
|
||||
{
|
||||
// Create the workflow
|
||||
var workflow = WorkflowHelper.GetWorkflow();
|
||||
var workflow = await WorkflowHelper.GetWorkflowAsync().ConfigureAwait(false);
|
||||
|
||||
// Create checkpoint manager
|
||||
var checkpointManager = CheckpointManager.Default;
|
||||
@@ -63,8 +63,8 @@ public static class Program
|
||||
Console.WriteLine($"** Checkpoint created at step {checkpoints.Count}.");
|
||||
}
|
||||
break;
|
||||
case WorkflowCompletedEvent workflowCompletedEvt:
|
||||
Console.WriteLine($"Workflow completed with result: {workflowCompletedEvt.Data}");
|
||||
case WorkflowOutputEvent workflowOutputEvt:
|
||||
Console.WriteLine($"Workflow completed with result: {workflowOutputEvt.Data}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -93,8 +93,8 @@ public static class Program
|
||||
case ExecutorCompletedEvent executorCompletedEvt:
|
||||
Console.WriteLine($"* Executor {executorCompletedEvt.ExecutorId} completed.");
|
||||
break;
|
||||
case WorkflowCompletedEvent workflowCompletedEvt:
|
||||
Console.WriteLine($"Workflow completed with result: {workflowCompletedEvt.Data}");
|
||||
case WorkflowOutputEvent workflowOutputEvt:
|
||||
Console.WriteLine($"Workflow completed with result: {workflowOutputEvt.Data}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
+4
-3
@@ -13,7 +13,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 Workflow<SignalWithNumber> GetWorkflow()
|
||||
internal static ValueTask<Workflow<NumberSignal>> GetWorkflowAsync()
|
||||
{
|
||||
// Create the executors
|
||||
InputPort numberInputPort = InputPort.Create<SignalWithNumber, int>("GuessNumber");
|
||||
@@ -23,7 +23,8 @@ internal static class WorkflowHelper
|
||||
return new WorkflowBuilder(numberInputPort)
|
||||
.AddEdge(numberInputPort, judgeExecutor)
|
||||
.AddEdge(judgeExecutor, numberInputPort)
|
||||
.Build<SignalWithNumber>();
|
||||
.WithOutputFrom(judgeExecutor)
|
||||
.BuildAsync<NumberSignal>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +76,7 @@ internal sealed class JudgeExecutor() : ReflectingExecutor<JudgeExecutor>("Judge
|
||||
this._tries++;
|
||||
if (message == this._targetNumber)
|
||||
{
|
||||
await context.AddEventAsync(new WorkflowCompletedEvent($"{this._targetNumber} found in {this._tries} tries!"))
|
||||
await context.YieldOutputAsync($"{this._targetNumber} found in {this._tries} tries!")
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
else if (message < this._targetNumber)
|
||||
|
||||
Reference in New Issue
Block a user