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
+3
-3
@@ -9,7 +9,7 @@ namespace Microsoft.Agents.Workflows.Sample;
|
||||
|
||||
internal static class Step1EntryPoint
|
||||
{
|
||||
public static Workflow<string> WorkflowInstance
|
||||
public static Workflow WorkflowInstance
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -19,7 +19,7 @@ internal static class Step1EntryPoint
|
||||
WorkflowBuilder builder = new(uppercase);
|
||||
builder.AddEdge(uppercase, reverse);
|
||||
|
||||
return builder.Build<string>();
|
||||
return builder.Build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ internal sealed class ReverseTextExecutor() : ReflectingExecutor<ReverseTextExec
|
||||
{
|
||||
string result = string.Concat(message.Reverse());
|
||||
|
||||
await context.AddEventAsync(new WorkflowCompletedEvent(result)).ConfigureAwait(false);
|
||||
await context.AddEventAsync(new RequestHaltEvent(result)).ConfigureAwait(false);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ internal static class Step1aEntryPoint
|
||||
{
|
||||
Run run = await InProcessExecution.RunAsync(WorkflowInstance, "Hello, World!").ConfigureAwait(false);
|
||||
|
||||
Assert.Equal(RunStatus.Completed, run.Status);
|
||||
Assert.Equal(RunStatus.Idle, run.Status);
|
||||
|
||||
foreach (WorkflowEvent evt in run.NewEvents)
|
||||
{
|
||||
|
||||
+14
-13
@@ -10,20 +10,21 @@ namespace Microsoft.Agents.Workflows.Sample;
|
||||
|
||||
internal static class Step2EntryPoint
|
||||
{
|
||||
public static Workflow<string> WorkflowInstance
|
||||
public static Workflow WorkflowInstance
|
||||
{
|
||||
get
|
||||
{
|
||||
string[] spamKeywords = ["spam", "advertisement", "offer"];
|
||||
|
||||
DetectSpamExecutor detectSpam = new(spamKeywords);
|
||||
RespondToMessageExecutor respondToMessage = new();
|
||||
RemoveSpamExecutor removeSpam = new();
|
||||
DetectSpamExecutor detectSpam = new("DetectSpam", spamKeywords);
|
||||
RespondToMessageExecutor respondToMessage = new("RespondToMessage");
|
||||
RemoveSpamExecutor removeSpam = new("RemoveSpam");
|
||||
|
||||
return new WorkflowBuilder(detectSpam)
|
||||
.AddEdge(detectSpam, respondToMessage, (bool isSpam) => !isSpam) // If not spam, respond
|
||||
.AddEdge(detectSpam, removeSpam, (bool isSpam) => isSpam) // If spam, remove
|
||||
.Build<string>();
|
||||
.WithOutputFrom(respondToMessage, removeSpam)
|
||||
.Build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,9 +35,9 @@ internal static class Step2EntryPoint
|
||||
{
|
||||
switch (evt)
|
||||
{
|
||||
case WorkflowCompletedEvent workflowCompleteEvt:
|
||||
case WorkflowOutputEvent workflowOutputEvt:
|
||||
// The workflow has completed successfully, return the result
|
||||
string workflowResult = workflowCompleteEvt.Data!.ToString()!;
|
||||
string workflowResult = workflowOutputEvt.As<string>()!;
|
||||
writer.WriteLine($"Result: {workflowResult}");
|
||||
return workflowResult;
|
||||
case ExecutorCompletedEvent executorCompletedEvt:
|
||||
@@ -45,7 +46,7 @@ internal static class Step2EntryPoint
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Workflow failed to yield the completion event.");
|
||||
throw new InvalidOperationException("Workflow failed to yield an output.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +54,7 @@ internal sealed class DetectSpamExecutor : ReflectingExecutor<DetectSpamExecutor
|
||||
{
|
||||
public string[] SpamKeywords { get; }
|
||||
|
||||
public DetectSpamExecutor(params string[] spamKeywords)
|
||||
public DetectSpamExecutor(string id, params string[] spamKeywords) : base(id)
|
||||
{
|
||||
this.SpamKeywords = spamKeywords;
|
||||
}
|
||||
@@ -70,7 +71,7 @@ internal sealed class DetectSpamExecutor : ReflectingExecutor<DetectSpamExecutor
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class RespondToMessageExecutor : ReflectingExecutor<RespondToMessageExecutor>, IMessageHandler<bool>
|
||||
internal sealed class RespondToMessageExecutor(string id) : ReflectingExecutor<RespondToMessageExecutor>(id), IMessageHandler<bool>
|
||||
{
|
||||
public const string ActionResult = "Message processed successfully.";
|
||||
|
||||
@@ -84,12 +85,12 @@ internal sealed class RespondToMessageExecutor : ReflectingExecutor<RespondToMes
|
||||
|
||||
await Task.Delay(1000).ConfigureAwait(false); // Simulate some processing delay
|
||||
|
||||
await context.AddEventAsync(new WorkflowCompletedEvent(ActionResult))
|
||||
await context.YieldOutputAsync(ActionResult)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class RemoveSpamExecutor : ReflectingExecutor<RemoveSpamExecutor>, IMessageHandler<bool>
|
||||
internal sealed class RemoveSpamExecutor(string id) : ReflectingExecutor<RemoveSpamExecutor>(id), IMessageHandler<bool>
|
||||
{
|
||||
public const string ActionResult = "Spam message removed.";
|
||||
|
||||
@@ -103,7 +104,7 @@ internal sealed class RemoveSpamExecutor : ReflectingExecutor<RemoveSpamExecutor
|
||||
|
||||
await Task.Delay(1000).ConfigureAwait(false); // Simulate some processing delay
|
||||
|
||||
await context.AddEventAsync(new WorkflowCompletedEvent(ActionResult))
|
||||
await context.YieldOutputAsync(ActionResult)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
+11
-10
@@ -10,17 +10,18 @@ namespace Microsoft.Agents.Workflows.Sample;
|
||||
|
||||
internal static class Step3EntryPoint
|
||||
{
|
||||
public static Workflow<NumberSignal> WorkflowInstance
|
||||
public static Workflow WorkflowInstance
|
||||
{
|
||||
get
|
||||
{
|
||||
GuessNumberExecutor guessNumber = new(1, 100);
|
||||
JudgeExecutor judge = new(42); // Let's say the target number is 42
|
||||
GuessNumberExecutor guessNumber = new("GuessNumber", 1, 100);
|
||||
JudgeExecutor judge = new("Judge", 42); // Let's say the target number is 42
|
||||
|
||||
return new WorkflowBuilder(guessNumber)
|
||||
.AddEdge(guessNumber, judge)
|
||||
.AddEdge(judge, guessNumber)
|
||||
.Build<NumberSignal>();
|
||||
.WithOutputFrom(guessNumber)
|
||||
.Build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,9 +33,9 @@ internal static class Step3EntryPoint
|
||||
{
|
||||
switch (evt)
|
||||
{
|
||||
case WorkflowCompletedEvent workflowCompleteEvt:
|
||||
case WorkflowOutputEvent workflowOutputEvt:
|
||||
// The workflow has completed successfully, return the result
|
||||
string workflowResult = workflowCompleteEvt.Data!.ToString()!;
|
||||
string workflowResult = workflowOutputEvt.As<string>()!;
|
||||
writer.WriteLine($"Result: {workflowResult}");
|
||||
return workflowResult;
|
||||
case ExecutorCompletedEvent executorCompletedEvt:
|
||||
@@ -43,7 +44,7 @@ internal static class Step3EntryPoint
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Workflow failed to yield the completion event.");
|
||||
throw new InvalidOperationException("Workflow failed to yield an output.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +61,7 @@ internal sealed class GuessNumberExecutor : ReflectingExecutor<GuessNumberExecut
|
||||
public int LowerBound { get; private set; }
|
||||
public int UpperBound { get; private set; }
|
||||
|
||||
public GuessNumberExecutor(int lowerBound, int upperBound)
|
||||
public GuessNumberExecutor(string id, int lowerBound, int upperBound) : base(id, new ExecutorOptions { AutoYieldOutputHandlerResultObject = false })
|
||||
{
|
||||
this.LowerBound = lowerBound;
|
||||
this.UpperBound = upperBound;
|
||||
@@ -74,7 +75,7 @@ internal sealed class GuessNumberExecutor : ReflectingExecutor<GuessNumberExecut
|
||||
switch (message)
|
||||
{
|
||||
case NumberSignal.Matched:
|
||||
await context.AddEventAsync(new WorkflowCompletedEvent($"Guessed the number: {this._currGuess}"))
|
||||
await context.YieldOutputAsync($"Guessed the number: {this._currGuess}")
|
||||
.ConfigureAwait(false);
|
||||
break;
|
||||
|
||||
@@ -97,7 +98,7 @@ internal sealed class JudgeExecutor : ReflectingExecutor<JudgeExecutor>, IMessag
|
||||
|
||||
internal int? Tries { get; private set; }
|
||||
|
||||
public JudgeExecutor(int targetNumber)
|
||||
public JudgeExecutor(string id, int targetNumber) : base(id)
|
||||
{
|
||||
this._targetNumber = targetNumber;
|
||||
}
|
||||
|
||||
+50
-23
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -8,18 +9,27 @@ namespace Microsoft.Agents.Workflows.Sample;
|
||||
|
||||
internal static class Step4EntryPoint
|
||||
{
|
||||
public static Workflow<NumberSignal, string> CreateWorkflowInstance(out JudgeExecutor judge)
|
||||
internal const string JudgeId = "Judge";
|
||||
|
||||
public static Workflow CreateWorkflowInstance(out JudgeExecutor judge)
|
||||
{
|
||||
InputPort guessNumber = InputPort.Create<NumberSignal, int>("GuessNumber");
|
||||
judge = new(42); // Let's say the target number is 42
|
||||
judge = new(JudgeId, 42); // Let's say the target number is 42
|
||||
|
||||
return new WorkflowBuilder(guessNumber)
|
||||
.AddEdge(guessNumber, judge)
|
||||
.AddEdge(judge, guessNumber, (NumberSignal signal) => signal != NumberSignal.Matched)
|
||||
.BuildWithOutput<NumberSignal, NumberSignal, string>(judge, ComputeStreamingOutput, (s, _) => s is NumberSignal.Matched);
|
||||
.WithOutputFrom(judge)
|
||||
.Build();
|
||||
}
|
||||
|
||||
public static Workflow<NumberSignal, string> WorkflowInstance
|
||||
public static ValueTask<Workflow<NumberSignal>?> GetPromotedWorklowInstanceAsync()
|
||||
{
|
||||
Workflow workflow = CreateWorkflowInstance(out _);
|
||||
return workflow.TryPromoteAsync<NumberSignal>();
|
||||
}
|
||||
|
||||
public static Workflow WorkflowInstance
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -29,30 +39,53 @@ internal static class Step4EntryPoint
|
||||
|
||||
public static async ValueTask<string> RunAsync(TextWriter writer, Func<string, int> userGuessCallback)
|
||||
{
|
||||
Workflow<NumberSignal, string> workflow = WorkflowInstance;
|
||||
StreamingRun<string> handle = await InProcessExecution.StreamAsync(workflow, NumberSignal.Init).ConfigureAwait(false);
|
||||
NumberSignal signal = NumberSignal.Init;
|
||||
string? prompt = UpdatePrompt(null, signal);
|
||||
|
||||
Workflow workflow = WorkflowInstance;
|
||||
StreamingRun handle = await InProcessExecution.StreamAsync(workflow, NumberSignal.Init).ConfigureAwait(false);
|
||||
|
||||
List<ExternalRequest> requests = [];
|
||||
await foreach (WorkflowEvent evt in handle.WatchStreamAsync().ConfigureAwait(false))
|
||||
{
|
||||
switch (evt)
|
||||
{
|
||||
case WorkflowOutputEvent outputEvent:
|
||||
switch (outputEvent.SourceId)
|
||||
{
|
||||
case JudgeId:
|
||||
if (!outputEvent.Is<NumberSignal>())
|
||||
{
|
||||
throw new InvalidOperationException($"Unexpected output type {outputEvent.Data!.GetType()}");
|
||||
}
|
||||
|
||||
signal = outputEvent.As<NumberSignal?>()!.Value;
|
||||
prompt = UpdatePrompt(prompt, signal);
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case RequestInfoEvent requestInputEvt:
|
||||
ExternalResponse response = ExecuteExternalRequest(requestInputEvt.Request, userGuessCallback, workflow.RunningOutput);
|
||||
await handle.SendResponseAsync(response).ConfigureAwait(false);
|
||||
requests.Add(requestInputEvt.Request);
|
||||
break;
|
||||
|
||||
case SuperStepCompletedEvent stepCompletedEvent:
|
||||
foreach (ExternalRequest request in requests)
|
||||
{
|
||||
ExternalResponse response = ExecuteExternalRequest(request, userGuessCallback, prompt);
|
||||
await handle.SendResponseAsync(response).ConfigureAwait(false);
|
||||
}
|
||||
requests.Clear();
|
||||
break;
|
||||
|
||||
case WorkflowCompletedEvent workflowCompleteEvt:
|
||||
// The workflow has completed successfully, return the result
|
||||
string workflowResult = workflowCompleteEvt.Data!.ToString()!;
|
||||
writer.WriteLine($"Result: {workflowResult}");
|
||||
return workflowResult;
|
||||
case ExecutorCompletedEvent executorCompletedEvt:
|
||||
writer.WriteLine($"'{executorCompletedEvt.ExecutorId}: {executorCompletedEvt.Data}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Workflow failed to yield the completion event.");
|
||||
writer.WriteLine($"Result: {prompt}");
|
||||
return prompt!;
|
||||
}
|
||||
|
||||
private static ExternalResponse ExecuteExternalRequest(
|
||||
@@ -73,16 +106,10 @@ internal static class Step4EntryPoint
|
||||
/// This converts the incoming <see cref="NumberSignal"/> from the judge to a status text that can be displayed
|
||||
/// to the user.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This works correctly timing-wise because both the <see cref="StreamingAggregator{TInput, TOutput}"/> and the
|
||||
/// <see cref="InputPort"/> are one edge from the <see cref="JudgeExecutor"/> (see the workflow definition in the
|
||||
/// <see cref="RunAsync"/> method). That means they will get the <see cref="NumberSignal"/> at the same time (one
|
||||
/// SuperStep after the Judge has generated it.)
|
||||
/// </remarks>
|
||||
/// <param name="signal"></param>
|
||||
/// <param name="runningResult"></param>
|
||||
/// <param name="signal"></param>
|
||||
/// <returns></returns>
|
||||
private static string ComputeStreamingOutput(NumberSignal signal, string? runningResult)
|
||||
internal static string? UpdatePrompt(string? runningResult, NumberSignal signal)
|
||||
{
|
||||
return signal switch
|
||||
{
|
||||
@@ -90,7 +117,7 @@ internal static class Step4EntryPoint
|
||||
NumberSignal.Above => "Your guess was too high. Try again.",
|
||||
NumberSignal.Below => "Your guess was too low. Try again.",
|
||||
|
||||
_ => runningResult ?? string.Empty
|
||||
_ => runningResult
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+49
-16
@@ -13,18 +13,23 @@ internal static class Step5EntryPoint
|
||||
{
|
||||
public static async ValueTask<string> RunAsync(TextWriter writer, Func<string, int> userGuessCallback, bool rehydrateToRestore = false, CheckpointManager? checkpointManager = null)
|
||||
{
|
||||
Dictionary<CheckpointInfo, (NumberSignal signal, string? prompt)> checkpointedOutputs = new();
|
||||
|
||||
NumberSignal signal = NumberSignal.Init;
|
||||
string? prompt = Step4EntryPoint.UpdatePrompt(null, signal);
|
||||
|
||||
checkpointManager ??= CheckpointManager.Default;
|
||||
|
||||
Workflow<NumberSignal, string> workflow = Step4EntryPoint.CreateWorkflowInstance(out JudgeExecutor judge);
|
||||
Checkpointed<StreamingRun<string>> checkpointed =
|
||||
Workflow workflow = Step4EntryPoint.CreateWorkflowInstance(out JudgeExecutor judge);
|
||||
Checkpointed<StreamingRun> checkpointed =
|
||||
await InProcessExecution.StreamAsync(workflow, NumberSignal.Init, checkpointManager)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
List<CheckpointInfo> checkpoints = [];
|
||||
CancellationTokenSource cancellationSource = new();
|
||||
|
||||
StreamingRun<string> handle = checkpointed.Run;
|
||||
string? result = await RunStreamToHaltOrMaxStepAsync(6).ConfigureAwait(false);
|
||||
StreamingRun handle = checkpointed.Run;
|
||||
string? result = await RunStreamToHaltOrMaxStepAsync(maxStep: 6).ConfigureAwait(false);
|
||||
|
||||
result.Should().BeNull();
|
||||
checkpoints.Should().HaveCount(6, "we should have two checkpoints, one for each step");
|
||||
@@ -34,7 +39,7 @@ internal static class Step5EntryPoint
|
||||
|
||||
if (rehydrateToRestore)
|
||||
{
|
||||
checkpointed = await InProcessExecution.ResumeStreamAsync(workflow, targetCheckpoint, checkpointManager, CancellationToken.None)
|
||||
checkpointed = await InProcessExecution.ResumeStreamAsync(workflow, targetCheckpoint, checkpointManager, runId: handle.RunId, cancellation: CancellationToken.None)
|
||||
.ConfigureAwait(false);
|
||||
handle = checkpointed.Run;
|
||||
}
|
||||
@@ -43,6 +48,8 @@ internal static class Step5EntryPoint
|
||||
await checkpointed.RestoreCheckpointAsync(checkpoints[2], CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
(signal, prompt) = checkpointedOutputs[targetCheckpoint];
|
||||
|
||||
judge.Tries.Should().Be(1);
|
||||
|
||||
cancellationSource.Dispose();
|
||||
@@ -52,7 +59,7 @@ internal static class Step5EntryPoint
|
||||
result = await RunStreamToHaltOrMaxStepAsync().ConfigureAwait(false);
|
||||
|
||||
result.Should().NotBeNull();
|
||||
checkpoints.Should().HaveCount(6);
|
||||
checkpoints.Should().HaveCount(7);
|
||||
|
||||
cancellationSource.Dispose();
|
||||
|
||||
@@ -60,31 +67,56 @@ internal static class Step5EntryPoint
|
||||
|
||||
async ValueTask<string?> RunStreamToHaltOrMaxStepAsync(int? maxStep = null)
|
||||
{
|
||||
List<ExternalRequest> requests = [];
|
||||
await foreach (WorkflowEvent evt in handle.WatchStreamAsync(cancellationSource.Token).ConfigureAwait(false))
|
||||
{
|
||||
switch (evt)
|
||||
{
|
||||
case WorkflowOutputEvent outputEvent:
|
||||
switch (outputEvent.SourceId)
|
||||
{
|
||||
case Step4EntryPoint.JudgeId:
|
||||
if (!outputEvent.Is<NumberSignal>())
|
||||
{
|
||||
throw new InvalidOperationException($"Unexpected output type {outputEvent.Data!.GetType()}");
|
||||
}
|
||||
|
||||
signal = outputEvent.As<NumberSignal?>()!.Value;
|
||||
prompt = Step4EntryPoint.UpdatePrompt(null, signal);
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case RequestInfoEvent requestInputEvt:
|
||||
requests.Add(requestInputEvt.Request);
|
||||
break;
|
||||
|
||||
case SuperStepCompletedEvent stepCompletedEvt:
|
||||
CheckpointInfo? checkpoint = stepCompletedEvt.CompletionInfo!.Checkpoint;
|
||||
if (checkpoint is not null)
|
||||
{
|
||||
checkpoints.Add(checkpoint);
|
||||
|
||||
checkpointedOutputs[checkpoint] = (signal, prompt);
|
||||
}
|
||||
|
||||
if (maxStep.HasValue && stepCompletedEvt.StepNumber >= maxStep.Value - 1)
|
||||
{
|
||||
cancellationSource.Cancel();
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (ExternalRequest request in requests)
|
||||
{
|
||||
ExternalResponse response = ExecuteExternalRequest(request, userGuessCallback, prompt);
|
||||
await handle.SendResponseAsync(response).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
requests.Clear();
|
||||
}
|
||||
break;
|
||||
case RequestInfoEvent requestInputEvt:
|
||||
ExternalResponse response = ExecuteExternalRequest(requestInputEvt.Request, userGuessCallback, workflow.RunningOutput);
|
||||
await handle.SendResponseAsync(response).ConfigureAwait(false);
|
||||
break;
|
||||
case WorkflowCompletedEvent workflowCompleteEvt:
|
||||
// The workflow has completed successfully, return the result
|
||||
string workflowResult = workflowCompleteEvt.Data!.ToString()!;
|
||||
writer.WriteLine($"Result: {workflowResult}");
|
||||
return workflowResult;
|
||||
|
||||
case ExecutorCompletedEvent executorCompleteEvt:
|
||||
writer.WriteLine($"'{executorCompleteEvt.ExecutorId}: {executorCompleteEvt.Data}");
|
||||
break;
|
||||
@@ -96,7 +128,8 @@ internal static class Step5EntryPoint
|
||||
return null;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Workflow failed to yield the completion event.");
|
||||
writer.WriteLine($"Result: {prompt}");
|
||||
return prompt!;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Microsoft.Agents.Workflows.Sample;
|
||||
|
||||
internal static class Step6EntryPoint
|
||||
{
|
||||
public static Workflow<List<ChatMessage>> CreateWorkflow(int maxTurns) =>
|
||||
public static Workflow CreateWorkflow(int maxTurns) =>
|
||||
AgentWorkflowBuilder
|
||||
.CreateGroupChatBuilderWith(agents => new AgentWorkflowBuilder.RoundRobinGroupChatManager(agents) { MaximumIterationCount = maxTurns })
|
||||
.AddParticipants(new HelloAgent(), new EchoAgent())
|
||||
@@ -25,9 +25,9 @@ internal static class Step6EntryPoint
|
||||
|
||||
public static async ValueTask RunAsync(TextWriter writer, int maxSteps = 2)
|
||||
{
|
||||
Workflow<List<ChatMessage>> workflow = CreateWorkflow(maxSteps);
|
||||
Workflow workflow = CreateWorkflow(maxSteps);
|
||||
|
||||
StreamingRun run = await InProcessExecution.StreamAsync(workflow, [])
|
||||
StreamingRun run = await InProcessExecution.StreamAsync(workflow, Array.Empty<ChatMessage>())
|
||||
.ConfigureAwait(false);
|
||||
await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
|
||||
|
||||
|
||||
+4
-1
@@ -13,7 +13,10 @@ internal static class Step7EntryPoint
|
||||
{
|
||||
public static async ValueTask RunAsync(TextWriter writer, int maxSteps = 2)
|
||||
{
|
||||
Workflow<List<ChatMessage>> workflow = Step6EntryPoint.CreateWorkflow(maxSteps);
|
||||
Workflow<List<ChatMessage>> workflow = (await Step6EntryPoint.CreateWorkflow(maxSteps)
|
||||
.TryPromoteAsync<List<ChatMessage>>()
|
||||
.ConfigureAwait(false))!;
|
||||
|
||||
AIAgent agent = workflow.AsAgent("group-chat-agent", "Group Chat Agent");
|
||||
|
||||
AgentThread thread = agent.GetNewThread();
|
||||
|
||||
Reference in New Issue
Block a user