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
+4
-8
@@ -29,8 +29,8 @@ public static class Program
|
||||
|
||||
// Build the workflow by connecting executors sequentially
|
||||
WorkflowBuilder builder = new(uppercase);
|
||||
builder.AddEdge(uppercase, reverse);
|
||||
var workflow = builder.Build<string>();
|
||||
builder.AddEdge(uppercase, reverse).WithOutputFrom(reverse);
|
||||
var workflow = builder.Build();
|
||||
|
||||
// Execute the workflow with input data
|
||||
Run run = await InProcessExecution.RunAsync(workflow, "Hello, World!");
|
||||
@@ -72,11 +72,7 @@ internal sealed class ReverseTextExecutor() : ReflectingExecutor<ReverseTextExec
|
||||
/// <returns>The input text reversed</returns>
|
||||
public async ValueTask<string> HandleAsync(string message, IWorkflowContext context)
|
||||
{
|
||||
string result = string.Concat(message.Reverse());
|
||||
|
||||
// Signal that the workflow is complete
|
||||
await context.AddEventAsync(new WorkflowCompletedEvent(result)).ConfigureAwait(false);
|
||||
|
||||
return result;
|
||||
// Because we do not suppress it, the returned result will be yielded as an output from this executor.
|
||||
return string.Concat(message.Reverse());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ public static class Program
|
||||
|
||||
// Build the workflow by connecting executors sequentially
|
||||
WorkflowBuilder builder = new(uppercase);
|
||||
builder.AddEdge(uppercase, reverse);
|
||||
var workflow = builder.Build<string>();
|
||||
builder.AddEdge(uppercase, reverse).WithOutputFrom(reverse);
|
||||
var workflow = builder.Build();
|
||||
|
||||
// Execute the workflow in streaming mode
|
||||
StreamingRun run = await InProcessExecution.StreamAsync(workflow, "Hello, World!");
|
||||
@@ -71,11 +71,7 @@ internal sealed class ReverseTextExecutor() : ReflectingExecutor<ReverseTextExec
|
||||
/// <returns>The input text reversed</returns>
|
||||
public async ValueTask<string> HandleAsync(string message, IWorkflowContext context)
|
||||
{
|
||||
string result = string.Concat(message.Reverse());
|
||||
|
||||
// Signal that the workflow is complete
|
||||
await context.AddEventAsync(new WorkflowCompletedEvent(result)).ConfigureAwait(false);
|
||||
|
||||
return result;
|
||||
// Because we do not suppress it, the returned result will be yielded as an output from this executor.
|
||||
return string.Concat(message.Reverse());
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ public static class Program
|
||||
var workflow = new WorkflowBuilder(frenchAgent)
|
||||
.AddEdge(frenchAgent, spanishAgent)
|
||||
.AddEdge(spanishAgent, englishAgent)
|
||||
.Build<ChatMessage>();
|
||||
.Build();
|
||||
|
||||
// Execute the workflow
|
||||
StreamingRun run = await InProcessExecution.StreamAsync(workflow, new ChatMessage(ChatRole.User, "Hello World!"));
|
||||
|
||||
+3
-3
@@ -84,7 +84,7 @@ public static class Program
|
||||
throw new InvalidOperationException("Invalid workflow type.");
|
||||
}
|
||||
|
||||
static async Task<List<ChatMessage>> RunWorkflowAsync(Workflow<List<ChatMessage>> workflow, List<ChatMessage> messages)
|
||||
static async Task<List<ChatMessage>> RunWorkflowAsync(Workflow workflow, List<ChatMessage> messages)
|
||||
{
|
||||
string? lastExecutorId = null;
|
||||
|
||||
@@ -108,10 +108,10 @@ public static class Program
|
||||
Console.WriteLine($" [Calling function '{call.Name}' with arguments: {JsonSerializer.Serialize(call.Arguments)}]");
|
||||
}
|
||||
}
|
||||
else if (evt is WorkflowCompletedEvent completed)
|
||||
else if (evt is WorkflowOutputEvent output)
|
||||
{
|
||||
Console.WriteLine();
|
||||
return (List<ChatMessage>)completed.Data!;
|
||||
return output.As<List<ChatMessage>>()!;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -58,7 +58,9 @@ 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 = AgentWorkflowBuilder.BuildSequential(researcher, factChecker, reporter).AsAgent();
|
||||
AIAgent workflowAgent = await AgentWorkflowBuilder.BuildSequential(researcher, factChecker, reporter)
|
||||
.AsAgentAsync()
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// Run the workflow, streaming the output as it arrives.
|
||||
string? lastAuthor = null;
|
||||
|
||||
Reference in New Issue
Block a user