mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
[BREAKING] .NET: Workflow Off-Thread Execution Mode (#1233)
* Updates to async run loop. * fix: Workflow Onwership can be release by nonowner * fix: Incorrect handling of blockOnPending in StreamingRun Depending on whether we are running in streaming on non-streaming mode, we may be using the StreamingRun in different ways. Unfortunately, the only place we can really know what is the actual state of execution is in the RunEventStream implementations. This resulted in blocking where blocking was unneeded and occasionally not-blocking when blocking was needed. The fix is to move the logic of handling this blocking into RunEventStream implementations. * fix: Fix cleanup on error and end run This ensures we clean up the background resources correctly. * fix: Ensure we let the run loop proceed when shutting down * fix: Add timeout for Input Waiting * fix: Make the samples properly clean up `Run`s and `StreamingRun`s * fix: Simplify Declarative Workflow Run disposal pattern * Also fixes missing .Disposal() in Integration tests --------- Co-authored-by: Ben Thomas <ben.thomas@microsoft.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
0113e0466d
commit
7ebe00ec3d
@@ -49,7 +49,7 @@ public static class Program
|
||||
.Build();
|
||||
|
||||
// Execute the workflow
|
||||
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, "Create a slogan for a new electric SUV that is affordable and fun to drive.");
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
|
||||
{
|
||||
if (evt is SloganGeneratedEvent or FeedbackEvent)
|
||||
|
||||
@@ -38,7 +38,7 @@ public static class Program
|
||||
.Build();
|
||||
|
||||
// Execute the workflow
|
||||
StreamingRun run = await InProcessExecution.StreamAsync(workflow, new ChatMessage(ChatRole.User, "Hello World!"));
|
||||
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, new ChatMessage(ChatRole.User, "Hello World!"));
|
||||
// Must send the turn token to trigger the agents.
|
||||
// The agents are wrapped as executors. When they receive messages,
|
||||
// they will cache the messages and only start processing when they receive a TurnToken.
|
||||
|
||||
+2
-2
@@ -32,7 +32,7 @@ public static class Program
|
||||
var checkpoints = new List<CheckpointInfo>();
|
||||
|
||||
// Execute the workflow and save checkpoints
|
||||
Checkpointed<StreamingRun> checkpointedRun = await InProcessExecution
|
||||
await using Checkpointed<StreamingRun> checkpointedRun = await InProcessExecution
|
||||
.StreamAsync(workflow, NumberSignal.Init, checkpointManager)
|
||||
.ConfigureAwait(false);
|
||||
await foreach (WorkflowEvent evt in checkpointedRun.Run.WatchStreamAsync().ConfigureAwait(false))
|
||||
@@ -72,7 +72,7 @@ public static class Program
|
||||
Console.WriteLine($"\n\nHydrating a new workflow instance from the {CheckpointIndex + 1}th checkpoint.");
|
||||
CheckpointInfo savedCheckpoint = checkpoints[CheckpointIndex];
|
||||
|
||||
Checkpointed<StreamingRun> newCheckpointedRun =
|
||||
await using Checkpointed<StreamingRun> newCheckpointedRun =
|
||||
await InProcessExecution.ResumeStreamAsync(newWorkflow, savedCheckpoint, checkpointManager, checkpointedRun.Run.RunId)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ public static class Program
|
||||
var checkpoints = new List<CheckpointInfo>();
|
||||
|
||||
// Execute the workflow and save checkpoints
|
||||
Checkpointed<StreamingRun> checkpointedRun = await InProcessExecution
|
||||
await using Checkpointed<StreamingRun> checkpointedRun = await InProcessExecution
|
||||
.StreamAsync(workflow, NumberSignal.Init, checkpointManager)
|
||||
.ConfigureAwait(false);
|
||||
await foreach (WorkflowEvent evt in checkpointedRun.Run.WatchStreamAsync().ConfigureAwait(false))
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ public static class Program
|
||||
var checkpoints = new List<CheckpointInfo>();
|
||||
|
||||
// Execute the workflow and save checkpoints
|
||||
Checkpointed<StreamingRun> checkpointedRun = await InProcessExecution
|
||||
await using Checkpointed<StreamingRun> checkpointedRun = await InProcessExecution
|
||||
.StreamAsync(workflow, new SignalWithNumber(NumberSignal.Init), checkpointManager)
|
||||
.ConfigureAwait(false);
|
||||
await foreach (WorkflowEvent evt in checkpointedRun.Run.WatchStreamAsync().ConfigureAwait(false))
|
||||
|
||||
@@ -59,7 +59,7 @@ public static class Program
|
||||
.Build();
|
||||
|
||||
// Execute the workflow in streaming mode
|
||||
StreamingRun run = await InProcessExecution.StreamAsync(workflow, "What is temperature?");
|
||||
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, "What is temperature?");
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
|
||||
{
|
||||
if (evt is WorkflowOutputEvent output)
|
||||
|
||||
@@ -99,7 +99,7 @@ public static class Program
|
||||
|
||||
// Step 2: Run the workflow
|
||||
Console.WriteLine("\n=== RUNNING WORKFLOW ===\n");
|
||||
StreamingRun run = await InProcessExecution.StreamAsync(workflow, rawText);
|
||||
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, rawText);
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
|
||||
{
|
||||
Console.WriteLine($"Event: {evt}");
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ public static class Program
|
||||
string email = Resources.Read("spam.txt");
|
||||
|
||||
// Execute the workflow
|
||||
StreamingRun run = await InProcessExecution.StreamAsync(workflow, new ChatMessage(ChatRole.User, email));
|
||||
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, new ChatMessage(ChatRole.User, email));
|
||||
await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
|
||||
{
|
||||
|
||||
@@ -78,7 +78,7 @@ public static class Program
|
||||
string email = Resources.Read("ambiguous_email.txt");
|
||||
|
||||
// Execute the workflow
|
||||
StreamingRun run = await InProcessExecution.StreamAsync(workflow, new ChatMessage(ChatRole.User, email));
|
||||
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, new ChatMessage(ChatRole.User, email));
|
||||
await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
|
||||
{
|
||||
|
||||
+1
-1
@@ -86,7 +86,7 @@ public static class Program
|
||||
string email = Resources.Read("email.txt");
|
||||
|
||||
// Execute the workflow
|
||||
StreamingRun run = await InProcessExecution.StreamAsync(workflow, new ChatMessage(ChatRole.User, email));
|
||||
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, new ChatMessage(ChatRole.User, email));
|
||||
await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
|
||||
{
|
||||
|
||||
@@ -64,7 +64,7 @@ internal sealed class Program
|
||||
InputResponse? response = null;
|
||||
do
|
||||
{
|
||||
ExternalRequest? inputRequest = await this.MonitorWorkflowRunAsync(run, response);
|
||||
ExternalRequest? inputRequest = await this.MonitorAndDisposeWorkflowRunAsync(run, response);
|
||||
if (inputRequest is not null)
|
||||
{
|
||||
Notify("\nWORKFLOW: Yield");
|
||||
@@ -83,6 +83,7 @@ internal sealed class Program
|
||||
// Restore the latest checkpoint.
|
||||
Debug.WriteLine($"RESTORE #{this.LastCheckpoint.CheckpointId}");
|
||||
Notify("\nWORKFLOW: Restore");
|
||||
|
||||
run = await InProcessExecution.ResumeStreamAsync(workflow, this.LastCheckpoint, checkpointManager, run.Run.RunId);
|
||||
}
|
||||
else
|
||||
@@ -132,8 +133,10 @@ internal sealed class Program
|
||||
this.FoundryClient = new PersistentAgentsClient(this.FoundryEndpoint, new AzureCliCredential());
|
||||
}
|
||||
|
||||
private async Task<ExternalRequest?> MonitorWorkflowRunAsync(Checkpointed<StreamingRun> run, InputResponse? response = null)
|
||||
private async Task<ExternalRequest?> MonitorAndDisposeWorkflowRunAsync(Checkpointed<StreamingRun> run, InputResponse? response = null)
|
||||
{
|
||||
await using IAsyncDisposable disposeRun = run;
|
||||
|
||||
string? messageId = null;
|
||||
|
||||
await foreach (WorkflowEvent workflowEvent in run.Run.WatchStreamAsync().ConfigureAwait(false))
|
||||
@@ -175,7 +178,7 @@ internal sealed class Program
|
||||
}
|
||||
else
|
||||
{
|
||||
await run.Run.EndRunAsync().ConfigureAwait(false);
|
||||
await run.Run.DisposeAsync().ConfigureAwait(false);
|
||||
return requestInfo.Request;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -45,7 +45,7 @@ internal sealed class Program
|
||||
// Run the workflow, just like any other workflow
|
||||
string input = this.GetWorkflowInput();
|
||||
StreamingRun run = await InProcessExecution.StreamAsync(workflow, input);
|
||||
await this.MonitorWorkflowRunAsync(run);
|
||||
await this.MonitorAndDisposeWorkflowRunAsync(run);
|
||||
|
||||
Notify("\nWORKFLOW: Done!");
|
||||
}
|
||||
@@ -70,8 +70,10 @@ internal sealed class Program
|
||||
this.FoundryClient = new PersistentAgentsClient(this.FoundryEndpoint, new AzureCliCredential());
|
||||
}
|
||||
|
||||
private async Task MonitorWorkflowRunAsync(StreamingRun run)
|
||||
private async Task MonitorAndDisposeWorkflowRunAsync(StreamingRun run)
|
||||
{
|
||||
await using IAsyncDisposable disposeRun = run;
|
||||
|
||||
string? messageId = null;
|
||||
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ public static class Program
|
||||
var workflow = await WorkflowHelper.GetWorkflowAsync().ConfigureAwait(false);
|
||||
|
||||
// Execute the workflow
|
||||
StreamingRun handle = await InProcessExecution.StreamAsync(workflow, NumberSignal.Init).ConfigureAwait(false);
|
||||
await using StreamingRun handle = await InProcessExecution.StreamAsync(workflow, NumberSignal.Init).ConfigureAwait(false);
|
||||
await foreach (WorkflowEvent evt in handle.WatchStreamAsync().ConfigureAwait(false))
|
||||
{
|
||||
switch (evt)
|
||||
|
||||
@@ -33,7 +33,7 @@ public static class Program
|
||||
.BuildAsync<NumberSignal>();
|
||||
|
||||
// Execute the workflow
|
||||
StreamingRun run = await InProcessExecution.StreamAsync(workflow, NumberSignal.Init).ConfigureAwait(false);
|
||||
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, NumberSignal.Init).ConfigureAwait(false);
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
|
||||
{
|
||||
if (evt is WorkflowOutputEvent outputEvent)
|
||||
|
||||
@@ -57,7 +57,7 @@ public static class Program
|
||||
.Build();
|
||||
|
||||
// Execute the workflow with input data
|
||||
Run run = await InProcessExecution.RunAsync(workflow, "Hello, World!");
|
||||
await using Run run = await InProcessExecution.RunAsync(workflow, "Hello, World!");
|
||||
foreach (WorkflowEvent evt in run.NewEvents)
|
||||
{
|
||||
if (evt is ExecutorCompletedEvent executorComplete)
|
||||
|
||||
@@ -33,7 +33,7 @@ public static class Program
|
||||
.Build();
|
||||
|
||||
// Execute the workflow with input data
|
||||
Run run = await InProcessExecution.RunAsync(workflow, "Lorem_Ipsum.txt");
|
||||
await using Run run = await InProcessExecution.RunAsync(workflow, "Lorem_Ipsum.txt");
|
||||
foreach (WorkflowEvent evt in run.NewEvents)
|
||||
{
|
||||
if (evt is WorkflowOutputEvent outputEvent)
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ public static class Program
|
||||
var workflow = builder.Build();
|
||||
|
||||
// Execute the workflow with input data
|
||||
Run run = await InProcessExecution.RunAsync(workflow, "Hello, World!");
|
||||
await using Run run = await InProcessExecution.RunAsync(workflow, "Hello, World!");
|
||||
foreach (WorkflowEvent evt in run.NewEvents)
|
||||
{
|
||||
if (evt is ExecutorCompletedEvent executorComplete)
|
||||
|
||||
@@ -29,7 +29,7 @@ public static class Program
|
||||
var workflow = builder.Build();
|
||||
|
||||
// Execute the workflow in streaming mode
|
||||
StreamingRun run = await InProcessExecution.StreamAsync(workflow, "Hello, World!");
|
||||
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, "Hello, World!");
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
|
||||
{
|
||||
if (evt is ExecutorCompletedEvent executorCompleted)
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ public static class Program
|
||||
.Build();
|
||||
|
||||
// Execute the workflow
|
||||
StreamingRun run = await InProcessExecution.StreamAsync(workflow, new ChatMessage(ChatRole.User, "Hello World!"));
|
||||
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, new ChatMessage(ChatRole.User, "Hello World!"));
|
||||
|
||||
// Must send the turn token to trigger the agents.
|
||||
// The agents are wrapped as executors. When they receive messages,
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@ public static class Program
|
||||
{
|
||||
string? lastExecutorId = null;
|
||||
|
||||
StreamingRun run = await InProcessExecution.StreamAsync(workflow, messages);
|
||||
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, messages);
|
||||
await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user