.NET: Remove reflection samples (#1460)

* Removing usage of ReflectingExecutor<T> from workflow samples.

* Removing uneeded changes.

* Clean up.

* Update dotnet/samples/GettingStarted/Workflows/_Foundational/01_ExecutorsAndEdges/Program.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update dotnet/samples/GettingStarted/Workflows/_Foundational/01_ExecutorsAndEdges/Program.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update dotnet/samples/GettingStarted/Workflows/_Foundational/02_Streaming/Program.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update dotnet/samples/GettingStarted/Workflows/_Foundational/02_Streaming/Program.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Updates per PR feedback.

* Undo changes to generated file.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Ben Thomas
2025-10-14 13:34:04 -07:00
committed by GitHub
Unverified
parent 38e10eab81
commit a4039134de
29 changed files with 278 additions and 306 deletions
@@ -6,7 +6,6 @@ using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Agents.AI.Workflows.Reflection;
using Microsoft.Extensions.AI;
namespace WorkflowCustomAgentExecutorsSample;
@@ -50,7 +49,7 @@ public static class Program
// 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 foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
{
if (evt is SloganGeneratedEvent or FeedbackEvent)
{
@@ -107,10 +106,7 @@ internal sealed class SloganGeneratedEvent(SloganResult sloganResult) : Workflow
/// 1. HandleAsync(string message): Handles the initial task to create a slogan.
/// 2. HandleAsync(Feedback message): Handles feedback to improve the slogan.
/// </summary>
internal sealed class SloganWriterExecutor
: ReflectingExecutor<SloganWriterExecutor>,
IMessageHandler<string, SloganResult>,
IMessageHandler<FeedbackResult, SloganResult>
internal sealed class SloganWriterExecutor : Executor
{
private readonly AIAgent _agent;
private readonly AgentThread _thread;
@@ -134,6 +130,10 @@ internal sealed class SloganWriterExecutor
this._thread = this._agent.GetNewThread();
}
protected override RouteBuilder ConfigureRoutes(RouteBuilder routeBuilder) =>
routeBuilder.AddHandler<string, SloganResult>(this.HandleAsync)
.AddHandler<FeedbackResult, SloganResult>(this.HandleAsync);
public async ValueTask<SloganResult> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
var result = await this._agent.RunAsync(message, this._thread, cancellationToken: cancellationToken);
@@ -175,7 +175,7 @@ internal sealed class FeedbackEvent(FeedbackResult feedbackResult) : WorkflowEve
/// <summary>
/// A custom executor that uses an AI agent to provide feedback on a slogan.
/// </summary>
internal sealed class FeedbackExecutor : ReflectingExecutor<FeedbackExecutor>, IMessageHandler<SloganResult>
internal sealed class FeedbackExecutor : Executor<SloganResult>
{
private readonly AIAgent _agent;
private readonly AgentThread _thread;
@@ -205,7 +205,7 @@ internal sealed class FeedbackExecutor : ReflectingExecutor<FeedbackExecutor>, I
this._thread = this._agent.GetNewThread();
}
public async ValueTask HandleAsync(SloganResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask HandleAsync(SloganResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
var sloganMessage = $"""
Here is a slogan for the task '{message.Task}':
@@ -43,7 +43,7 @@ public static class Program
// The agents are wrapped as executors. When they receive messages,
// they will cache the messages and only start processing when they receive a TurnToken.
await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
{
if (evt is AgentRunUpdateEvent executorComplete)
{
@@ -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).ConfigureAwait(false);
var workflow = await WorkflowHelper.GetWorkflowAsync(chatClient);
var agent = workflow.AsAgent("workflow-agent", "Workflow Agent");
var thread = agent.GetNewThread();
@@ -59,7 +59,7 @@ public static class Program
static async Task ProcessInputAsync(AIAgent agent, AgentThread thread, string input)
{
Dictionary<string, List<AgentRunResponseUpdate>> buffer = [];
await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync(input, thread).ConfigureAwait(false))
await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync(input, thread))
{
if (update.MessageId is null)
{
@@ -2,7 +2,6 @@
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Agents.AI.Workflows.Reflection;
using Microsoft.Extensions.AI;
namespace WorkflowAsAnAgentsSample;
@@ -43,8 +42,7 @@ internal static class WorkflowHelper
/// Executor that starts the concurrent processing by sending messages to the agents.
/// </summary>
private sealed class ConcurrentStartExecutor() :
ReflectingExecutor<ConcurrentStartExecutor>("ConcurrentStartExecutor"),
IMessageHandler<List<ChatMessage>>
Executor<List<ChatMessage>>("ConcurrentStartExecutor")
{
/// <summary>
/// Starts the concurrent processing by sending messages to the agents.
@@ -53,7 +51,7 @@ internal static class WorkflowHelper
/// <param name="context">Workflow context for accessing workflow services and adding events</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
/// The default is <see cref="CancellationToken.None"/>.</param>
public async ValueTask HandleAsync(List<ChatMessage> message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask HandleAsync(List<ChatMessage> message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
// Broadcast the message to all connected agents. Receiving agents will queue
// the message but will not start processing until they receive a turn token.
@@ -67,8 +65,7 @@ internal static class WorkflowHelper
/// Executor that aggregates the results from the concurrent agents.
/// </summary>
private sealed class ConcurrentAggregationExecutor() :
ReflectingExecutor<ConcurrentAggregationExecutor>("ConcurrentAggregationExecutor"),
IMessageHandler<ChatMessage>
Executor<ChatMessage>("ConcurrentAggregationExecutor")
{
private readonly List<ChatMessage> _messages = [];
@@ -79,7 +76,7 @@ internal static class WorkflowHelper
/// <param name="context">Workflow context for accessing workflow services and adding events</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
/// The default is <see cref="CancellationToken.None"/>.</param>
public async ValueTask HandleAsync(ChatMessage message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask HandleAsync(ChatMessage message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
this._messages.Add(message);
@@ -25,7 +25,7 @@ public static class Program
private static async Task Main()
{
// Create the workflow
var workflow = await WorkflowHelper.GetWorkflowAsync().ConfigureAwait(false);
var workflow = await WorkflowHelper.GetWorkflowAsync();
// Create checkpoint manager
var checkpointManager = CheckpointManager.Default;
@@ -33,9 +33,9 @@ public static class Program
// Execute the workflow and save checkpoints
await using Checkpointed<StreamingRun> checkpointedRun = await InProcessExecution
.StreamAsync(workflow, NumberSignal.Init, checkpointManager)
.ConfigureAwait(false);
await foreach (WorkflowEvent evt in checkpointedRun.Run.WatchStreamAsync().ConfigureAwait(false))
.StreamAsync(workflow, NumberSignal.Init, checkpointManager);
await foreach (WorkflowEvent evt in checkpointedRun.Run.WatchStreamAsync())
{
if (evt is ExecutorCompletedEvent executorCompletedEvt)
{
@@ -67,16 +67,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 = await WorkflowHelper.GetWorkflowAsync().ConfigureAwait(false);
var newWorkflow = await WorkflowHelper.GetWorkflowAsync();
const int CheckpointIndex = 5;
Console.WriteLine($"\n\nHydrating a new workflow instance from the {CheckpointIndex + 1}th checkpoint.");
CheckpointInfo savedCheckpoint = checkpoints[CheckpointIndex];
await using Checkpointed<StreamingRun> newCheckpointedRun =
await InProcessExecution.ResumeStreamAsync(newWorkflow, savedCheckpoint, checkpointManager, checkpointedRun.Run.RunId)
.ConfigureAwait(false);
await InProcessExecution.ResumeStreamAsync(newWorkflow, savedCheckpoint, checkpointManager, checkpointedRun.Run.RunId);
await foreach (WorkflowEvent evt in newCheckpointedRun.Run.WatchStreamAsync().ConfigureAwait(false))
await foreach (WorkflowEvent evt in newCheckpointedRun.Run.WatchStreamAsync())
{
if (evt is ExecutorCompletedEvent executorCompletedEvt)
{
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows;
using Microsoft.Agents.AI.Workflows.Reflection;
namespace WorkflowCheckpointAndRehydrateSample;
@@ -42,7 +41,7 @@ internal enum NumberSignal
/// <summary>
/// Executor that makes a guess based on the current bounds.
/// </summary>
internal sealed class GuessNumberExecutor() : ReflectingExecutor<GuessNumberExecutor>("Guess"), IMessageHandler<NumberSignal>
internal sealed class GuessNumberExecutor() : Executor<NumberSignal>("Guess")
{
/// <summary>
/// The lower bound of the guessing range.
@@ -69,20 +68,20 @@ internal sealed class GuessNumberExecutor() : ReflectingExecutor<GuessNumberExec
private int NextGuess => (this.LowerBound + this.UpperBound) / 2;
public async ValueTask HandleAsync(NumberSignal message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask HandleAsync(NumberSignal message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
switch (message)
{
case NumberSignal.Init:
await context.SendMessageAsync(this.NextGuess, cancellationToken: cancellationToken).ConfigureAwait(false);
await context.SendMessageAsync(this.NextGuess, cancellationToken: cancellationToken);
break;
case NumberSignal.Above:
this.UpperBound = this.NextGuess - 1;
await context.SendMessageAsync(this.NextGuess, cancellationToken: cancellationToken).ConfigureAwait(false);
await context.SendMessageAsync(this.NextGuess, cancellationToken: cancellationToken);
break;
case NumberSignal.Below:
this.LowerBound = this.NextGuess + 1;
await context.SendMessageAsync(this.NextGuess, cancellationToken: cancellationToken).ConfigureAwait(false);
await context.SendMessageAsync(this.NextGuess, cancellationToken: cancellationToken);
break;
}
}
@@ -99,13 +98,13 @@ internal sealed class GuessNumberExecutor() : ReflectingExecutor<GuessNumberExec
/// This must be overridden to restore any state that was saved during checkpointing.
/// </summary>
protected override async ValueTask OnCheckpointRestoredAsync(IWorkflowContext context, CancellationToken cancellationToken = default) =>
(this.LowerBound, this.UpperBound) = await context.ReadStateAsync<(int, int)>(StateKey, cancellationToken: cancellationToken).ConfigureAwait(false);
(this.LowerBound, this.UpperBound) = await context.ReadStateAsync<(int, int)>(StateKey, cancellationToken: cancellationToken);
}
/// <summary>
/// Executor that judges the guess and provides feedback.
/// </summary>
internal sealed class JudgeExecutor() : ReflectingExecutor<JudgeExecutor>("Judge"), IMessageHandler<int>
internal sealed class JudgeExecutor() : Executor<int>("Judge")
{
private readonly int _targetNumber;
private int _tries;
@@ -120,20 +119,20 @@ internal sealed class JudgeExecutor() : ReflectingExecutor<JudgeExecutor>("Judge
this._targetNumber = targetNumber;
}
public async ValueTask HandleAsync(int message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask HandleAsync(int message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
this._tries++;
if (message == this._targetNumber)
{
await context.YieldOutputAsync($"{this._targetNumber} found in {this._tries} tries!", cancellationToken: cancellationToken).ConfigureAwait(false);
await context.YieldOutputAsync($"{this._targetNumber} found in {this._tries} tries!", cancellationToken: cancellationToken);
}
else if (message < this._targetNumber)
{
await context.SendMessageAsync(NumberSignal.Below, cancellationToken: cancellationToken).ConfigureAwait(false);
await context.SendMessageAsync(NumberSignal.Below, cancellationToken: cancellationToken);
}
else
{
await context.SendMessageAsync(NumberSignal.Above, cancellationToken: cancellationToken).ConfigureAwait(false);
await context.SendMessageAsync(NumberSignal.Above, cancellationToken: cancellationToken);
}
}
@@ -149,5 +148,5 @@ internal sealed class JudgeExecutor() : ReflectingExecutor<JudgeExecutor>("Judge
/// This must be overridden to restore any state that was saved during checkpointing.
/// </summary>
protected override async ValueTask OnCheckpointRestoredAsync(IWorkflowContext context, CancellationToken cancellationToken = default) =>
this._tries = await context.ReadStateAsync<int>(StateKey, cancellationToken: cancellationToken).ConfigureAwait(false);
this._tries = await context.ReadStateAsync<int>(StateKey, cancellationToken: cancellationToken);
}
@@ -24,7 +24,7 @@ public static class Program
private static async Task Main()
{
// Create the workflow
var workflow = await WorkflowHelper.GetWorkflowAsync().ConfigureAwait(false);
var workflow = await WorkflowHelper.GetWorkflowAsync();
// Create checkpoint manager
var checkpointManager = CheckpointManager.Default;
@@ -33,8 +33,8 @@ public static class Program
// Execute the workflow and save checkpoints
await using Checkpointed<StreamingRun> checkpointedRun = await InProcessExecution
.StreamAsync(workflow, NumberSignal.Init, checkpointManager)
.ConfigureAwait(false);
await foreach (WorkflowEvent evt in checkpointedRun.Run.WatchStreamAsync().ConfigureAwait(false))
;
await foreach (WorkflowEvent evt in checkpointedRun.Run.WatchStreamAsync())
{
if (evt is ExecutorCompletedEvent executorCompletedEvt)
{
@@ -70,8 +70,8 @@ public static class Program
Console.WriteLine($"\n\nRestoring from the {CheckpointIndex + 1}th checkpoint.");
CheckpointInfo savedCheckpoint = checkpoints[CheckpointIndex];
// Note that we are restoring the state directly to the same run instance.
await checkpointedRun.RestoreCheckpointAsync(savedCheckpoint, CancellationToken.None).ConfigureAwait(false);
await foreach (WorkflowEvent evt in checkpointedRun.Run.WatchStreamAsync().ConfigureAwait(false))
await checkpointedRun.RestoreCheckpointAsync(savedCheckpoint, CancellationToken.None);
await foreach (WorkflowEvent evt in checkpointedRun.Run.WatchStreamAsync())
{
if (evt is ExecutorCompletedEvent executorCompletedEvt)
{
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows;
using Microsoft.Agents.AI.Workflows.Reflection;
namespace WorkflowCheckpointAndResumeSample;
@@ -42,7 +41,7 @@ internal enum NumberSignal
/// <summary>
/// Executor that makes a guess based on the current bounds.
/// </summary>
internal sealed class GuessNumberExecutor() : ReflectingExecutor<GuessNumberExecutor>("Guess"), IMessageHandler<NumberSignal>
internal sealed class GuessNumberExecutor() : Executor<NumberSignal>("Guess")
{
/// <summary>
/// The lower bound of the guessing range.
@@ -69,20 +68,20 @@ internal sealed class GuessNumberExecutor() : ReflectingExecutor<GuessNumberExec
private int NextGuess => (this.LowerBound + this.UpperBound) / 2;
public async ValueTask HandleAsync(NumberSignal message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask HandleAsync(NumberSignal message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
switch (message)
{
case NumberSignal.Init:
await context.SendMessageAsync(this.NextGuess, cancellationToken: cancellationToken).ConfigureAwait(false);
await context.SendMessageAsync(this.NextGuess, cancellationToken: cancellationToken);
break;
case NumberSignal.Above:
this.UpperBound = this.NextGuess - 1;
await context.SendMessageAsync(this.NextGuess, cancellationToken: cancellationToken).ConfigureAwait(false);
await context.SendMessageAsync(this.NextGuess, cancellationToken: cancellationToken);
break;
case NumberSignal.Below:
this.LowerBound = this.NextGuess + 1;
await context.SendMessageAsync(this.NextGuess, cancellationToken: cancellationToken).ConfigureAwait(false);
await context.SendMessageAsync(this.NextGuess, cancellationToken: cancellationToken);
break;
}
}
@@ -99,13 +98,13 @@ internal sealed class GuessNumberExecutor() : ReflectingExecutor<GuessNumberExec
/// This must be overridden to restore any state that was saved during checkpointing.
/// </summary>
protected override async ValueTask OnCheckpointRestoredAsync(IWorkflowContext context, CancellationToken cancellationToken = default) =>
(this.LowerBound, this.UpperBound) = await context.ReadStateAsync<(int, int)>(StateKey, cancellationToken: cancellationToken).ConfigureAwait(false);
(this.LowerBound, this.UpperBound) = await context.ReadStateAsync<(int, int)>(StateKey, cancellationToken: cancellationToken);
}
/// <summary>
/// Executor that judges the guess and provides feedback.
/// </summary>
internal sealed class JudgeExecutor() : ReflectingExecutor<JudgeExecutor>("Judge"), IMessageHandler<int>
internal sealed class JudgeExecutor() : Executor<int>("Judge")
{
private readonly int _targetNumber;
private int _tries;
@@ -120,20 +119,20 @@ internal sealed class JudgeExecutor() : ReflectingExecutor<JudgeExecutor>("Judge
this._targetNumber = targetNumber;
}
public async ValueTask HandleAsync(int message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask HandleAsync(int message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
this._tries++;
if (message == this._targetNumber)
{
await context.YieldOutputAsync($"{this._targetNumber} found in {this._tries} tries!", cancellationToken).ConfigureAwait(false);
await context.YieldOutputAsync($"{this._targetNumber} found in {this._tries} tries!", cancellationToken);
}
else if (message < this._targetNumber)
{
await context.SendMessageAsync(NumberSignal.Below, cancellationToken: cancellationToken).ConfigureAwait(false);
await context.SendMessageAsync(NumberSignal.Below, cancellationToken: cancellationToken);
}
else
{
await context.SendMessageAsync(NumberSignal.Above, cancellationToken: cancellationToken).ConfigureAwait(false);
await context.SendMessageAsync(NumberSignal.Above, cancellationToken: cancellationToken);
}
}
@@ -149,5 +148,5 @@ internal sealed class JudgeExecutor() : ReflectingExecutor<JudgeExecutor>("Judge
/// This must be overridden to restore any state that was saved during checkpointing.
/// </summary>
protected override async ValueTask OnCheckpointRestoredAsync(IWorkflowContext context, CancellationToken cancellationToken = default) =>
this._tries = await context.ReadStateAsync<int>(StateKey, cancellationToken: cancellationToken).ConfigureAwait(false);
this._tries = await context.ReadStateAsync<int>(StateKey, cancellationToken: cancellationToken);
}
@@ -27,7 +27,7 @@ public static class Program
private static async Task Main()
{
// Create the workflow
var workflow = await WorkflowHelper.GetWorkflowAsync().ConfigureAwait(false);
var workflow = await WorkflowHelper.GetWorkflowAsync();
// Create checkpoint manager
var checkpointManager = CheckpointManager.Default;
@@ -36,15 +36,15 @@ public static class Program
// Execute the workflow and save checkpoints
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))
;
await foreach (WorkflowEvent evt in checkpointedRun.Run.WatchStreamAsync())
{
switch (evt)
{
case RequestInfoEvent requestInputEvt:
// Handle `RequestInfoEvent` from the workflow
ExternalResponse response = HandleExternalRequest(requestInputEvt.Request);
await checkpointedRun.Run.SendResponseAsync(response).ConfigureAwait(false);
await checkpointedRun.Run.SendResponseAsync(response);
break;
case ExecutorCompletedEvent executorCompletedEvt:
Console.WriteLine($"* Executor {executorCompletedEvt.ExecutorId} completed.");
@@ -76,15 +76,15 @@ public static class Program
Console.WriteLine($"\n\nRestoring from the {CheckpointIndex + 1}th checkpoint.");
CheckpointInfo savedCheckpoint = checkpoints[CheckpointIndex];
// Note that we are restoring the state directly to the same run instance.
await checkpointedRun.RestoreCheckpointAsync(savedCheckpoint, CancellationToken.None).ConfigureAwait(false);
await foreach (WorkflowEvent evt in checkpointedRun.Run.WatchStreamAsync().ConfigureAwait(false))
await checkpointedRun.RestoreCheckpointAsync(savedCheckpoint, CancellationToken.None);
await foreach (WorkflowEvent evt in checkpointedRun.Run.WatchStreamAsync())
{
switch (evt)
{
case RequestInfoEvent requestInputEvt:
// Handle `RequestInfoEvent` from the workflow
ExternalResponse response = HandleExternalRequest(requestInputEvt.Request);
await checkpointedRun.Run.SendResponseAsync(response).ConfigureAwait(false);
await checkpointedRun.Run.SendResponseAsync(response);
break;
case ExecutorCompletedEvent executorCompletedEvt:
Console.WriteLine($"* Executor {executorCompletedEvt.ExecutorId} completed.");
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows;
using Microsoft.Agents.AI.Workflows.Reflection;
namespace WorkflowCheckpointWithHumanInTheLoopSample;
@@ -54,7 +53,7 @@ internal sealed class SignalWithNumber
/// <summary>
/// Executor that judges the guess and provides feedback.
/// </summary>
internal sealed class JudgeExecutor() : ReflectingExecutor<JudgeExecutor>("Judge"), IMessageHandler<int>
internal sealed class JudgeExecutor() : Executor<int>("Judge")
{
private readonly int _targetNumber;
private int _tries;
@@ -69,21 +68,20 @@ internal sealed class JudgeExecutor() : ReflectingExecutor<JudgeExecutor>("Judge
this._targetNumber = targetNumber;
}
public async ValueTask HandleAsync(int message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask HandleAsync(int message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
this._tries++;
if (message == this._targetNumber)
{
await context.YieldOutputAsync($"{this._targetNumber} found in {this._tries} tries!", cancellationToken)
.ConfigureAwait(false);
await context.YieldOutputAsync($"{this._targetNumber} found in {this._tries} tries!", cancellationToken);
}
else if (message < this._targetNumber)
{
await context.SendMessageAsync(new SignalWithNumber(NumberSignal.Below, message), cancellationToken: cancellationToken).ConfigureAwait(false);
await context.SendMessageAsync(new SignalWithNumber(NumberSignal.Below, message), cancellationToken: cancellationToken);
}
else
{
await context.SendMessageAsync(new SignalWithNumber(NumberSignal.Above, message), cancellationToken: cancellationToken).ConfigureAwait(false);
await context.SendMessageAsync(new SignalWithNumber(NumberSignal.Above, message), cancellationToken: cancellationToken);
}
}
@@ -99,5 +97,5 @@ internal sealed class JudgeExecutor() : ReflectingExecutor<JudgeExecutor>("Judge
/// This must be overridden to restore any state that was saved during checkpointing.
/// </summary>
protected override async ValueTask OnCheckpointRestoredAsync(IWorkflowContext context, CancellationToken cancellationToken = default) =>
this._tries = await context.ReadStateAsync<int>(StateKey, cancellationToken: cancellationToken).ConfigureAwait(false);
this._tries = await context.ReadStateAsync<int>(StateKey, cancellationToken: cancellationToken);
}
@@ -4,7 +4,6 @@ using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Agents.AI.Workflows.Reflection;
using Microsoft.Extensions.AI;
namespace WorkflowConcurrentSample;
@@ -60,7 +59,7 @@ public static class Program
// Execute the workflow in streaming mode
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, "What is temperature?");
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
{
if (evt is WorkflowOutputEvent output)
{
@@ -74,8 +73,7 @@ public static class Program
/// Executor that starts the concurrent processing by sending messages to the agents.
/// </summary>
internal sealed class ConcurrentStartExecutor() :
ReflectingExecutor<ConcurrentStartExecutor>("ConcurrentStartExecutor"),
IMessageHandler<string>
Executor<string>("ConcurrentStartExecutor")
{
/// <summary>
/// Starts the concurrent processing by sending messages to the agents.
@@ -85,7 +83,7 @@ internal sealed class ConcurrentStartExecutor() :
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
/// The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>A task representing the asynchronous operation</returns>
public async ValueTask HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
// Broadcast the message to all connected agents. Receiving agents will queue
// the message but will not start processing until they receive a turn token.
@@ -99,8 +97,7 @@ internal sealed class ConcurrentStartExecutor() :
/// Executor that aggregates the results from the concurrent agents.
/// </summary>
internal sealed class ConcurrentAggregationExecutor() :
ReflectingExecutor<ConcurrentAggregationExecutor>("ConcurrentAggregationExecutor"),
IMessageHandler<ChatMessage>
Executor<ChatMessage>("ConcurrentAggregationExecutor")
{
private readonly List<ChatMessage> _messages = [];
@@ -112,7 +109,7 @@ internal sealed class ConcurrentAggregationExecutor() :
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
/// The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>A task representing the asynchronous operation</returns>
public async ValueTask HandleAsync(ChatMessage message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask HandleAsync(ChatMessage message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
this._messages.Add(message);
@@ -8,7 +8,6 @@ using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Agents.AI.Workflows.Reflection;
namespace WorkflowMapReduceSample;
@@ -130,8 +129,7 @@ public static class Program
/// Splits data into roughly equal chunks based on the number of mapper nodes.
/// </summary>
internal sealed class Split(string[] mapperIds, string id) :
ReflectingExecutor<Split>(id),
IMessageHandler<string>
Executor<string>(id)
{
private readonly string[] _mapperIds = mapperIds;
private static readonly string[] s_lineSeparators = ["\r\n", "\r", "\n"];
@@ -139,7 +137,7 @@ internal sealed class Split(string[] mapperIds, string id) :
/// <summary>
/// Tokenize input and assign contiguous index ranges to each mapper via shared state.
/// </summary>
public async ValueTask HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
// Ensure temp directory exists
Directory.CreateDirectory(MapReduceConstants.TempDir);
@@ -188,12 +186,12 @@ internal sealed class Split(string[] mapperIds, string id) :
/// <summary>
/// Maps each token to a count of 1 and writes pairs to a per-mapper file.
/// </summary>
internal sealed class Mapper(string id) : ReflectingExecutor<Mapper>(id), IMessageHandler<SplitComplete>
internal sealed class Mapper(string id) : Executor<SplitComplete>(id)
{
/// <summary>
/// Read the assigned slice, emit (word, 1) pairs, and persist to disk.
/// </summary>
public async ValueTask HandleAsync(SplitComplete message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask HandleAsync(SplitComplete message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
var dataToProcess = await context.ReadStateAsync<string[]>(MapReduceConstants.DataToProcessKey, scopeName: MapReduceConstants.StateScope, cancellationToken);
var chunk = await context.ReadStateAsync<(int start, int end)>(this.Id, scopeName: MapReduceConstants.StateScope, cancellationToken);
@@ -215,8 +213,7 @@ internal sealed class Mapper(string id) : ReflectingExecutor<Mapper>(id), IMessa
/// Groups intermediate pairs by key and partitions them across reducers.
/// </summary>
internal sealed class Shuffler(string[] reducerIds, string[] mapperIds, string id) :
ReflectingExecutor<Shuffler>(id),
IMessageHandler<MapComplete>
Executor<MapComplete>(id)
{
private readonly string[] _reducerIds = reducerIds;
private readonly string[] _mapperIds = mapperIds;
@@ -225,7 +222,7 @@ internal sealed class Shuffler(string[] reducerIds, string[] mapperIds, string i
/// <summary>
/// Aggregate mapper outputs and write one partition file per reducer.
/// </summary>
public async ValueTask HandleAsync(MapComplete message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask HandleAsync(MapComplete message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
this._mapResults.Add(message);
@@ -314,12 +311,12 @@ internal sealed class Shuffler(string[] reducerIds, string[] mapperIds, string i
/// <summary>
/// Sums grouped counts per key for its assigned partition.
/// </summary>
internal sealed class Reducer(string id) : ReflectingExecutor<Reducer>(id), IMessageHandler<ShuffleComplete>
internal sealed class Reducer(string id) : Executor<ShuffleComplete>(id)
{
/// <summary>
/// Read one shuffle partition and reduce it to totals.
/// </summary>
public async ValueTask HandleAsync(ShuffleComplete message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask HandleAsync(ShuffleComplete message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
if (message.ReducerId != this.Id)
{
@@ -356,13 +353,12 @@ internal sealed class Reducer(string id) : ReflectingExecutor<Reducer>(id), IMes
/// Joins all reducer outputs and yields the final output.
/// </summary>
internal sealed class CompletionExecutor(string id) :
ReflectingExecutor<CompletionExecutor>(id),
IMessageHandler<List<ReduceComplete>>
Executor<List<ReduceComplete>>(id)
{
/// <summary>
/// Collect reducer output file paths and yield final output.
/// </summary>
public async ValueTask HandleAsync(List<ReduceComplete> message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask HandleAsync(List<ReduceComplete> message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
var filePaths = message.ConvertAll(r => r.FilePath);
await context.YieldOutputAsync(filePaths, cancellationToken);
@@ -6,7 +6,6 @@ using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Agents.AI.Workflows.Reflection;
using Microsoft.Extensions.AI;
namespace WorkflowEdgeConditionSample;
@@ -64,7 +63,7 @@ public static class Program
// Execute the workflow
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))
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
{
if (evt is WorkflowOutputEvent outputEvent)
{
@@ -147,7 +146,7 @@ internal sealed class Email
/// <summary>
/// Executor that detects spam using an AI agent.
/// </summary>
internal sealed class SpamDetectionExecutor : ReflectingExecutor<SpamDetectionExecutor>, IMessageHandler<ChatMessage, DetectionResult>
internal sealed class SpamDetectionExecutor : Executor<ChatMessage, DetectionResult>
{
private readonly AIAgent _spamDetectionAgent;
@@ -160,7 +159,7 @@ internal sealed class SpamDetectionExecutor : ReflectingExecutor<SpamDetectionEx
this._spamDetectionAgent = spamDetectionAgent;
}
public async ValueTask<DetectionResult> HandleAsync(ChatMessage message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask<DetectionResult> HandleAsync(ChatMessage message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
// Generate a random email ID and store the email content to the shared state
var newEmail = new Email
@@ -192,7 +191,7 @@ public sealed class EmailResponse
/// <summary>
/// Executor that assists with email responses using an AI agent.
/// </summary>
internal sealed class EmailAssistantExecutor : ReflectingExecutor<EmailAssistantExecutor>, IMessageHandler<DetectionResult, EmailResponse>
internal sealed class EmailAssistantExecutor : Executor<DetectionResult, EmailResponse>
{
private readonly AIAgent _emailAssistantAgent;
@@ -205,7 +204,7 @@ internal sealed class EmailAssistantExecutor : ReflectingExecutor<EmailAssistant
this._emailAssistantAgent = emailAssistantAgent;
}
public async ValueTask<EmailResponse> HandleAsync(DetectionResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask<EmailResponse> HandleAsync(DetectionResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
if (message.IsSpam)
{
@@ -227,24 +226,24 @@ internal sealed class EmailAssistantExecutor : ReflectingExecutor<EmailAssistant
/// <summary>
/// Executor that sends emails.
/// </summary>
internal sealed class SendEmailExecutor() : ReflectingExecutor<SendEmailExecutor>("SendEmailExecutor"), IMessageHandler<EmailResponse>
internal sealed class SendEmailExecutor() : Executor<EmailResponse>("SendEmailExecutor")
{
/// <summary>
/// Simulate the sending of an email.
/// </summary>
public async ValueTask HandleAsync(EmailResponse message, IWorkflowContext context, CancellationToken cancellationToken = default) =>
public override async ValueTask HandleAsync(EmailResponse message, IWorkflowContext context, CancellationToken cancellationToken = default) =>
await context.YieldOutputAsync($"Email sent: {message.Response}", cancellationToken);
}
/// <summary>
/// Executor that handles spam messages.
/// </summary>
internal sealed class HandleSpamExecutor() : ReflectingExecutor<HandleSpamExecutor>("HandleSpamExecutor"), IMessageHandler<DetectionResult>
internal sealed class HandleSpamExecutor() : Executor<DetectionResult>("HandleSpamExecutor")
{
/// <summary>
/// Simulate the handling of a spam message.
/// </summary>
public async ValueTask HandleAsync(DetectionResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask HandleAsync(DetectionResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
if (message.IsSpam)
{
@@ -6,7 +6,6 @@ using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Agents.AI.Workflows.Reflection;
using Microsoft.Extensions.AI;
namespace WorkflowSwitchCaseSample;
@@ -80,7 +79,7 @@ public static class Program
// Execute the workflow
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))
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
{
if (evt is WorkflowOutputEvent outputEvent)
{
@@ -172,7 +171,7 @@ internal sealed class Email
/// <summary>
/// Executor that detects spam using an AI agent.
/// </summary>
internal sealed class SpamDetectionExecutor : ReflectingExecutor<SpamDetectionExecutor>, IMessageHandler<ChatMessage, DetectionResult>
internal sealed class SpamDetectionExecutor : Executor<ChatMessage, DetectionResult>
{
private readonly AIAgent _spamDetectionAgent;
@@ -185,7 +184,7 @@ internal sealed class SpamDetectionExecutor : ReflectingExecutor<SpamDetectionEx
this._spamDetectionAgent = spamDetectionAgent;
}
public async ValueTask<DetectionResult> HandleAsync(ChatMessage message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask<DetectionResult> HandleAsync(ChatMessage message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
// Generate a random email ID and store the email content
var newEmail = new Email
@@ -217,7 +216,7 @@ public sealed class EmailResponse
/// <summary>
/// Executor that assists with email responses using an AI agent.
/// </summary>
internal sealed class EmailAssistantExecutor : ReflectingExecutor<EmailAssistantExecutor>, IMessageHandler<DetectionResult, EmailResponse>
internal sealed class EmailAssistantExecutor : Executor<DetectionResult, EmailResponse>
{
private readonly AIAgent _emailAssistantAgent;
@@ -230,7 +229,7 @@ internal sealed class EmailAssistantExecutor : ReflectingExecutor<EmailAssistant
this._emailAssistantAgent = emailAssistantAgent;
}
public async ValueTask<EmailResponse> HandleAsync(DetectionResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask<EmailResponse> HandleAsync(DetectionResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
if (message.spamDecision == SpamDecision.Spam)
{
@@ -251,28 +250,28 @@ internal sealed class EmailAssistantExecutor : ReflectingExecutor<EmailAssistant
/// <summary>
/// Executor that sends emails.
/// </summary>
internal sealed class SendEmailExecutor() : ReflectingExecutor<SendEmailExecutor>("SendEmailExecutor"), IMessageHandler<EmailResponse>
internal sealed class SendEmailExecutor() : Executor<EmailResponse>("SendEmailExecutor")
{
/// <summary>
/// Simulate the sending of an email.
/// </summary>
public async ValueTask HandleAsync(EmailResponse message, IWorkflowContext context, CancellationToken cancellationToken = default) =>
await context.YieldOutputAsync($"Email sent: {message.Response}", cancellationToken).ConfigureAwait(false);
public override async ValueTask HandleAsync(EmailResponse message, IWorkflowContext context, CancellationToken cancellationToken = default) =>
await context.YieldOutputAsync($"Email sent: {message.Response}", cancellationToken);
}
/// <summary>
/// Executor that handles spam messages.
/// </summary>
internal sealed class HandleSpamExecutor() : ReflectingExecutor<HandleSpamExecutor>("HandleSpamExecutor"), IMessageHandler<DetectionResult>
internal sealed class HandleSpamExecutor() : Executor<DetectionResult>("HandleSpamExecutor")
{
/// <summary>
/// Simulate the handling of a spam message.
/// </summary>
public async ValueTask HandleAsync(DetectionResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask HandleAsync(DetectionResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
if (message.spamDecision == SpamDecision.Spam)
{
await context.YieldOutputAsync($"Email marked as spam: {message.Reason}", cancellationToken).ConfigureAwait(false);
await context.YieldOutputAsync($"Email marked as spam: {message.Reason}", cancellationToken);
}
else
{
@@ -284,12 +283,12 @@ internal sealed class HandleSpamExecutor() : ReflectingExecutor<HandleSpamExecut
/// <summary>
/// Executor that handles uncertain emails.
/// </summary>
internal sealed class HandleUncertainExecutor() : ReflectingExecutor<HandleUncertainExecutor>("HandleUncertainExecutor"), IMessageHandler<DetectionResult>
internal sealed class HandleUncertainExecutor() : Executor<DetectionResult>("HandleUncertainExecutor")
{
/// <summary>
/// Simulate the handling of an uncertain spam decision.
/// </summary>
public async ValueTask HandleAsync(DetectionResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask HandleAsync(DetectionResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
if (message.spamDecision == SpamDecision.Uncertain)
{
@@ -6,7 +6,6 @@ using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Agents.AI.Workflows.Reflection;
using Microsoft.Extensions.AI;
namespace WorkflowMultiSelectionSample;
@@ -88,7 +87,7 @@ public static class Program
// Execute the workflow
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))
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
{
if (evt is WorkflowOutputEvent outputEvent)
{
@@ -228,7 +227,7 @@ internal sealed class Email
/// <summary>
/// Executor that analyzes emails using an AI agent.
/// </summary>
internal sealed class EmailAnalysisExecutor : ReflectingExecutor<EmailAnalysisExecutor>, IMessageHandler<ChatMessage, AnalysisResult>
internal sealed class EmailAnalysisExecutor : Executor<ChatMessage, AnalysisResult>
{
private readonly AIAgent _emailAnalysisAgent;
@@ -241,7 +240,7 @@ internal sealed class EmailAnalysisExecutor : ReflectingExecutor<EmailAnalysisEx
this._emailAnalysisAgent = emailAnalysisAgent;
}
public async ValueTask<AnalysisResult> HandleAsync(ChatMessage message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask<AnalysisResult> HandleAsync(ChatMessage message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
// Generate a random email ID and store the email content
var newEmail = new Email
@@ -274,7 +273,7 @@ public sealed class EmailResponse
/// <summary>
/// Executor that assists with email responses using an AI agent.
/// </summary>
internal sealed class EmailAssistantExecutor : ReflectingExecutor<EmailAssistantExecutor>, IMessageHandler<AnalysisResult, EmailResponse>
internal sealed class EmailAssistantExecutor : Executor<AnalysisResult, EmailResponse>
{
private readonly AIAgent _emailAssistantAgent;
@@ -287,7 +286,7 @@ internal sealed class EmailAssistantExecutor : ReflectingExecutor<EmailAssistant
this._emailAssistantAgent = emailAssistantAgent;
}
public async ValueTask<EmailResponse> HandleAsync(AnalysisResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask<EmailResponse> HandleAsync(AnalysisResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
if (message.spamDecision == SpamDecision.Spam)
{
@@ -308,24 +307,24 @@ internal sealed class EmailAssistantExecutor : ReflectingExecutor<EmailAssistant
/// <summary>
/// Executor that sends emails.
/// </summary>
internal sealed class SendEmailExecutor() : ReflectingExecutor<SendEmailExecutor>("SendEmailExecutor"), IMessageHandler<EmailResponse>
internal sealed class SendEmailExecutor() : Executor<EmailResponse>("SendEmailExecutor")
{
/// <summary>
/// Simulate the sending of an email.
/// </summary>
public async ValueTask HandleAsync(EmailResponse message, IWorkflowContext context, CancellationToken cancellationToken = default) =>
public override async ValueTask HandleAsync(EmailResponse message, IWorkflowContext context, CancellationToken cancellationToken = default) =>
await context.YieldOutputAsync($"Email sent: {message.Response}", cancellationToken);
}
/// <summary>
/// Executor that handles spam messages.
/// </summary>
internal sealed class HandleSpamExecutor() : ReflectingExecutor<HandleSpamExecutor>("HandleSpamExecutor"), IMessageHandler<AnalysisResult>
internal sealed class HandleSpamExecutor() : Executor<AnalysisResult>("HandleSpamExecutor")
{
/// <summary>
/// Simulate the handling of a spam message.
/// </summary>
public async ValueTask HandleAsync(AnalysisResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask HandleAsync(AnalysisResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
if (message.spamDecision == SpamDecision.Spam)
{
@@ -341,12 +340,12 @@ internal sealed class HandleSpamExecutor() : ReflectingExecutor<HandleSpamExecut
/// <summary>
/// Executor that handles uncertain messages.
/// </summary>
internal sealed class HandleUncertainExecutor() : ReflectingExecutor<HandleUncertainExecutor>("HandleUncertainExecutor"), IMessageHandler<AnalysisResult>
internal sealed class HandleUncertainExecutor() : Executor<AnalysisResult>("HandleUncertainExecutor")
{
/// <summary>
/// Simulate the handling of an uncertain spam decision.
/// </summary>
public async ValueTask HandleAsync(AnalysisResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask HandleAsync(AnalysisResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
if (message.spamDecision == SpamDecision.Uncertain)
{
@@ -372,7 +371,7 @@ public sealed class EmailSummary
/// <summary>
/// Executor that summarizes emails using an AI agent.
/// </summary>
internal sealed class EmailSummaryExecutor : ReflectingExecutor<EmailSummaryExecutor>, IMessageHandler<AnalysisResult, AnalysisResult>
internal sealed class EmailSummaryExecutor : Executor<AnalysisResult, AnalysisResult>
{
private readonly AIAgent _emailSummaryAgent;
@@ -385,7 +384,7 @@ internal sealed class EmailSummaryExecutor : ReflectingExecutor<EmailSummaryExec
this._emailSummaryAgent = emailSummaryAgent;
}
public async ValueTask<AnalysisResult> HandleAsync(AnalysisResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask<AnalysisResult> HandleAsync(AnalysisResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
// Read the email content from the shared states
var email = await context.ReadStateAsync<Email>(message.EmailId, scopeName: EmailStateConstants.EmailStateScope, cancellationToken);
@@ -408,9 +407,9 @@ internal sealed class DatabaseEvent(string message) : WorkflowEvent(message) { }
/// <summary>
/// Executor that handles database access.
/// </summary>
internal sealed class DatabaseAccessExecutor() : ReflectingExecutor<DatabaseAccessExecutor>("DatabaseAccessExecutor"), IMessageHandler<AnalysisResult>
internal sealed class DatabaseAccessExecutor() : Executor<AnalysisResult>("DatabaseAccessExecutor")
{
public async ValueTask HandleAsync(AnalysisResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask HandleAsync(AnalysisResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
// 1. Save the email content
await context.ReadStateAsync<Email>(message.EmailId, scopeName: EmailStateConstants.EmailStateScope, cancellationToken);
@@ -52,22 +52,22 @@ public static class TestWorkflowProvider
"FOUNDRY_AGENT_RESEARCHWEATHER").ConfigureAwait(false);
// Initialize variables
await context.QueueStateUpdateAsync("AgentResponse", UnassignedValue.Instance, "Local").ConfigureAwait(false);
await context.QueueStateUpdateAsync("AgentResponseText", UnassignedValue.Instance, "Local").ConfigureAwait(false);
await context.QueueStateUpdateAsync("AvailableAgents", UnassignedValue.Instance, "Local").ConfigureAwait(false);
await context.QueueStateUpdateAsync("FinalResponse", UnassignedValue.Instance, "Local").ConfigureAwait(false);
await context.QueueStateUpdateAsync("InputTask", UnassignedValue.Instance, "Local").ConfigureAwait(false);
await context.QueueStateUpdateAsync("InternalConversationId", UnassignedValue.Instance, "Local").ConfigureAwait(false);
await context.QueueStateUpdateAsync("NextSpeaker", UnassignedValue.Instance, "Local").ConfigureAwait(false);
await context.QueueStateUpdateAsync("Plan", UnassignedValue.Instance, "Local").ConfigureAwait(false);
await context.QueueStateUpdateAsync("ProgressLedgerUpdate", UnassignedValue.Instance, "Local").ConfigureAwait(false);
await context.QueueStateUpdateAsync("RestartCount", UnassignedValue.Instance, "Local").ConfigureAwait(false);
await context.QueueStateUpdateAsync("SeedTask", UnassignedValue.Instance, "Local").ConfigureAwait(false);
await context.QueueStateUpdateAsync("StallCount", UnassignedValue.Instance, "Local").ConfigureAwait(false);
await context.QueueStateUpdateAsync("TaskFacts", UnassignedValue.Instance, "Local").ConfigureAwait(false);
await context.QueueStateUpdateAsync("TaskInstructions", UnassignedValue.Instance, "Local").ConfigureAwait(false);
await context.QueueStateUpdateAsync("TeamDescription", UnassignedValue.Instance, "Local").ConfigureAwait(false);
await context.QueueStateUpdateAsync("TypedProgressLedger", UnassignedValue.Instance, "Local").ConfigureAwait(false);
await context.QueueStateUpdateAsync("AgentResponse", UnassignedValue.Instance, "Local");
await context.QueueStateUpdateAsync("AgentResponseText", UnassignedValue.Instance, "Local");
await context.QueueStateUpdateAsync("AvailableAgents", UnassignedValue.Instance, "Local");
await context.QueueStateUpdateAsync("FinalResponse", UnassignedValue.Instance, "Local");
await context.QueueStateUpdateAsync("InputTask", UnassignedValue.Instance, "Local");
await context.QueueStateUpdateAsync("InternalConversationId", UnassignedValue.Instance, "Local");
await context.QueueStateUpdateAsync("NextSpeaker", UnassignedValue.Instance, "Local");
await context.QueueStateUpdateAsync("Plan", UnassignedValue.Instance, "Local");
await context.QueueStateUpdateAsync("ProgressLedgerUpdate", UnassignedValue.Instance, "Local");
await context.QueueStateUpdateAsync("RestartCount", UnassignedValue.Instance, "Local");
await context.QueueStateUpdateAsync("SeedTask", UnassignedValue.Instance, "Local");
await context.QueueStateUpdateAsync("StallCount", UnassignedValue.Instance, "Local");
await context.QueueStateUpdateAsync("TaskFacts", UnassignedValue.Instance, "Local");
await context.QueueStateUpdateAsync("TaskInstructions", UnassignedValue.Instance, "Local");
await context.QueueStateUpdateAsync("TeamDescription", UnassignedValue.Instance, "Local");
await context.QueueStateUpdateAsync("TypedProgressLedger", UnassignedValue.Instance, "Local");
}
}
@@ -97,8 +97,8 @@ public static class TestWorkflowProvider
agentid: Env.FOUNDRY_AGENT_RESEARCHWEB
}
]
""").ConfigureAwait(false);
await context.QueueStateUpdateAsync(key: "AvailableAgents", value: evaluatedValue, scopeName: "Local").ConfigureAwait(false);
""");
await context.QueueStateUpdateAsync(key: "AvailableAgents", value: evaluatedValue, scopeName: "Local");
return default;
}
@@ -115,8 +115,8 @@ public static class TestWorkflowProvider
object? evaluatedValue = await context.EvaluateValueAsync<object>("""
Concat(ForAll(Local.AvailableAgents, $"- " & name & $": " & description), Value, "
")
""").ConfigureAwait(false);
await context.QueueStateUpdateAsync(key: "TeamDescription", value: evaluatedValue, scopeName: "Local").ConfigureAwait(false);
""");
await context.QueueStateUpdateAsync(key: "TeamDescription", value: evaluatedValue, scopeName: "Local");
return default;
}
@@ -130,8 +130,8 @@ public static class TestWorkflowProvider
// <inheritdoc />
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
object? evaluatedValue = await context.EvaluateValueAsync<object>("System.LastMessage.Text").ConfigureAwait(false);
await context.QueueStateUpdateAsync(key: "InputTask", value: evaluatedValue, scopeName: "Local").ConfigureAwait(false);
object? evaluatedValue = await context.EvaluateValueAsync<object>("System.LastMessage.Text");
await context.QueueStateUpdateAsync(key: "InputTask", value: evaluatedValue, scopeName: "Local");
return default;
}
@@ -145,8 +145,8 @@ public static class TestWorkflowProvider
// <inheritdoc />
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
object? evaluatedValue = await context.EvaluateValueAsync<object>("UserMessage(Local.InputTask)").ConfigureAwait(false);
await context.QueueStateUpdateAsync(key: "SeedTask", value: evaluatedValue, scopeName: "Local").ConfigureAwait(false);
object? evaluatedValue = await context.EvaluateValueAsync<object>("UserMessage(Local.InputTask)");
await context.QueueStateUpdateAsync(key: "SeedTask", value: evaluatedValue, scopeName: "Local");
return default;
}
@@ -167,7 +167,7 @@ public static class TestWorkflowProvider
"""
);
AgentRunResponse response = new([new ChatMessage(ChatRole.Assistant, activityText)]);
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response)).ConfigureAwait(false);
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response));
return default;
}
@@ -180,8 +180,8 @@ public static class TestWorkflowProvider
{
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
string conversationId = await agentProvider.CreateConversationAsync(cancellationToken).ConfigureAwait(false);
await context.QueueStateUpdateAsync(key: "InternalConversationId", value: conversationId, scopeName: "Local").ConfigureAwait(false);
string conversationId = await agentProvider.CreateConversationAsync(cancellationToken);
await context.QueueStateUpdateAsync(key: "InternalConversationId", value: conversationId, scopeName: "Local");
return default;
}
@@ -195,14 +195,14 @@ public static class TestWorkflowProvider
// <inheritdoc />
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
string? agentName = await context.ReadStateAsync<string>(key: "FOUNDRY_AGENT_RESEARCHANALYST", scopeName: "Env").ConfigureAwait(false);
string? agentName = await context.ReadStateAsync<string>(key: "FOUNDRY_AGENT_RESEARCHANALYST", scopeName: "Env");
if (string.IsNullOrWhiteSpace(agentName))
{
throw new InvalidOperationException($"Agent name must be defined: {this.Id}");
}
string? conversationId = await context.ReadStateAsync<string>(key: "InternalConversationId", scopeName: "Local").ConfigureAwait(false);
string? conversationId = await context.ReadStateAsync<string>(key: "InternalConversationId", scopeName: "Local");
bool autoSend = true;
string additionalInstructions =
await context.FormatTemplateAsync(
@@ -226,7 +226,7 @@ public static class TestWorkflowProvider
DO NOT include any other headings or sections in your response. DO NOT list next steps or plans until asked to do so.
""");
IList<ChatMessage>? inputMessages = await context.EvaluateListAsync<ChatMessage>("UserMessage(Local.InputTask)").ConfigureAwait(false);
IList<ChatMessage>? inputMessages = await context.EvaluateListAsync<ChatMessage>("UserMessage(Local.InputTask)");
AgentRunResponse agentResponse =
await InvokeAgentAsync(
@@ -236,14 +236,14 @@ public static class TestWorkflowProvider
autoSend,
additionalInstructions,
inputMessages,
cancellationToken).ConfigureAwait(false);
cancellationToken);
if (autoSend)
{
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, agentResponse)).ConfigureAwait(false);
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, agentResponse));
}
await context.QueueStateUpdateAsync(key: "TaskFacts", value: agentResponse.Messages, scopeName: "Local").ConfigureAwait(false);
await context.QueueStateUpdateAsync(key: "TaskFacts", value: agentResponse.Messages, scopeName: "Local");
return default;
}
@@ -264,7 +264,7 @@ public static class TestWorkflowProvider
"""
);
AgentRunResponse response = new([new ChatMessage(ChatRole.Assistant, activityText)]);
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response)).ConfigureAwait(false);
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response));
return default;
}
@@ -278,14 +278,14 @@ public static class TestWorkflowProvider
// <inheritdoc />
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
string? agentName = await context.ReadStateAsync<string>(key: "FOUNDRY_AGENT_RESEARCHMANAGER", scopeName: "Env").ConfigureAwait(false);
string? agentName = await context.ReadStateAsync<string>(key: "FOUNDRY_AGENT_RESEARCHMANAGER", scopeName: "Env");
if (string.IsNullOrWhiteSpace(agentName))
{
throw new InvalidOperationException($"Agent name must be defined: {this.Id}");
}
string? conversationId = await context.ReadStateAsync<string>(key: "InternalConversationId", scopeName: "Local").ConfigureAwait(false);
string? conversationId = await context.ReadStateAsync<string>(key: "InternalConversationId", scopeName: "Local");
bool autoSend = true;
string additionalInstructions =
await context.FormatTemplateAsync(
@@ -300,7 +300,7 @@ public static class TestWorkflowProvider
Remember, there is no requirement to involve the entire team -- only select team member's whose particular expertise is required for this task.
""");
IList<ChatMessage>? inputMessages = await context.EvaluateListAsync<ChatMessage>("UserMessage(Local.InputTask)").ConfigureAwait(false);
IList<ChatMessage>? inputMessages = await context.EvaluateListAsync<ChatMessage>("UserMessage(Local.InputTask)");
AgentRunResponse agentResponse =
await InvokeAgentAsync(
@@ -310,14 +310,14 @@ public static class TestWorkflowProvider
autoSend,
additionalInstructions,
inputMessages,
cancellationToken).ConfigureAwait(false);
cancellationToken);
if (autoSend)
{
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, agentResponse)).ConfigureAwait(false);
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, agentResponse));
}
await context.QueueStateUpdateAsync(key: "Plan", value: agentResponse.Messages, scopeName: "Local").ConfigureAwait(false);
await context.QueueStateUpdateAsync(key: "Plan", value: agentResponse.Messages, scopeName: "Local");
return default;
}
@@ -354,8 +354,8 @@ public static class TestWorkflowProvider
Here is the plan to follow as best as possible:
" & Last(Local.Plan).Text
""").ConfigureAwait(false);
await context.QueueStateUpdateAsync(key: "TaskInstructions", value: evaluatedValue, scopeName: "Local").ConfigureAwait(false);
""");
await context.QueueStateUpdateAsync(key: "TaskInstructions", value: evaluatedValue, scopeName: "Local");
return default;
}
@@ -376,7 +376,7 @@ public static class TestWorkflowProvider
"""
);
AgentRunResponse response = new([new ChatMessage(ChatRole.Assistant, activityText)]);
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response)).ConfigureAwait(false);
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response));
return default;
}
@@ -390,14 +390,14 @@ public static class TestWorkflowProvider
// <inheritdoc />
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
string? agentName = await context.ReadStateAsync<string>(key: "FOUNDRY_AGENT_RESEARCHMANAGER", scopeName: "Env").ConfigureAwait(false);
string? agentName = await context.ReadStateAsync<string>(key: "FOUNDRY_AGENT_RESEARCHMANAGER", scopeName: "Env");
if (string.IsNullOrWhiteSpace(agentName))
{
throw new InvalidOperationException($"Agent name must be defined: {this.Id}");
}
string? conversationId = await context.ReadStateAsync<string>(key: "InternalConversationId", scopeName: "Local").ConfigureAwait(false);
string? conversationId = await context.ReadStateAsync<string>(key: "InternalConversationId", scopeName: "Local");
bool autoSend = true;
string additionalInstructions =
await context.FormatTemplateAsync(
@@ -443,7 +443,7 @@ public static class TestWorkflowProvider
}}
}}
""");
IList<ChatMessage>? inputMessages = await context.EvaluateListAsync<ChatMessage>("UserMessage(Local.AgentResponseText)").ConfigureAwait(false);
IList<ChatMessage>? inputMessages = await context.EvaluateListAsync<ChatMessage>("UserMessage(Local.AgentResponseText)");
AgentRunResponse agentResponse =
await InvokeAgentAsync(
@@ -453,14 +453,14 @@ public static class TestWorkflowProvider
autoSend,
additionalInstructions,
inputMessages,
cancellationToken).ConfigureAwait(false);
cancellationToken);
if (autoSend)
{
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, agentResponse)).ConfigureAwait(false);
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, agentResponse));
}
await context.QueueStateUpdateAsync(key: "ProgressLedgerUpdate", value: agentResponse.Messages, scopeName: "Local").ConfigureAwait(false);
await context.QueueStateUpdateAsync(key: "ProgressLedgerUpdate", value: agentResponse.Messages, scopeName: "Local");
return default;
}
@@ -496,8 +496,8 @@ public static class TestWorkflowProvider
VariableType.Record(
("reason", typeof(string)),
("answer", typeof(string)))));
object? parsedValue = await context.ConvertValueAsync(targetType, "Last(Local.ProgressLedgerUpdate).Text", cancellationToken).ConfigureAwait(false);
await context.QueueStateUpdateAsync(key: "TypedProgressLedger", value: parsedValue, scopeName: "Local").ConfigureAwait(false);
object? parsedValue = await context.ConvertValueAsync(targetType, "Last(Local.ProgressLedgerUpdate).Text", cancellationToken);
await context.QueueStateUpdateAsync(key: "TypedProgressLedger", value: parsedValue, scopeName: "Local");
return default;
}
@@ -511,13 +511,13 @@ public static class TestWorkflowProvider
// <inheritdoc />
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
bool condition0 = await context.EvaluateValueAsync<bool>("Local.TypedProgressLedger.is_request_satisfied.answer").ConfigureAwait(false);
bool condition0 = await context.EvaluateValueAsync<bool>("Local.TypedProgressLedger.is_request_satisfied.answer");
if (condition0)
{
return "conditionItem_fj432c";
}
bool condition1 = await context.EvaluateValueAsync<bool>("Local.TypedProgressLedger.is_in_loop.answer || Not(Local.TypedProgressLedger.is_progress_being_made.answer)").ConfigureAwait(false);
bool condition1 = await context.EvaluateValueAsync<bool>("Local.TypedProgressLedger.is_in_loop.answer || Not(Local.TypedProgressLedger.is_progress_being_made.answer)");
if (condition1)
{
return "conditionItem_yiqund";
@@ -542,7 +542,7 @@ public static class TestWorkflowProvider
"""
);
AgentRunResponse response = new([new ChatMessage(ChatRole.Assistant, activityText)]);
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response)).ConfigureAwait(false);
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response));
return default;
}
@@ -556,14 +556,14 @@ public static class TestWorkflowProvider
// <inheritdoc />
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
string? agentName = await context.ReadStateAsync<string>(key: "FOUNDRY_AGENT_RESEARCHMANAGER", scopeName: "Env").ConfigureAwait(false);
string? agentName = await context.ReadStateAsync<string>(key: "FOUNDRY_AGENT_RESEARCHMANAGER", scopeName: "Env");
if (string.IsNullOrWhiteSpace(agentName))
{
throw new InvalidOperationException($"Agent name must be defined: {this.Id}");
}
string? conversationId = await context.ReadStateAsync<string>(key: "ConversationId", scopeName: "System").ConfigureAwait(false);
string? conversationId = await context.ReadStateAsync<string>(key: "ConversationId", scopeName: "System");
bool autoSend = true;
string additionalInstructions =
await context.FormatTemplateAsync(
@@ -572,7 +572,7 @@ public static class TestWorkflowProvider
Based only on the conversation and without adding any new information, synthesize the result of the conversation as a complete response to the user task.
The user will only every see this last response and not the entire conversation, so please ensure it is complete and self-contained.
""");
IList<ChatMessage>? inputMessages = await context.ReadListAsync<ChatMessage>(key: "SeedTask", scopeName: "Local").ConfigureAwait(false);
IList<ChatMessage>? inputMessages = await context.ReadListAsync<ChatMessage>(key: "SeedTask", scopeName: "Local");
AgentRunResponse agentResponse =
await InvokeAgentAsync(
@@ -582,14 +582,14 @@ public static class TestWorkflowProvider
autoSend,
additionalInstructions,
inputMessages,
cancellationToken).ConfigureAwait(false);
cancellationToken);
if (autoSend)
{
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, agentResponse)).ConfigureAwait(false);
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, agentResponse));
}
await context.QueueStateUpdateAsync(key: "FinalResponse", value: agentResponse.Messages, scopeName: "Local").ConfigureAwait(false);
await context.QueueStateUpdateAsync(key: "FinalResponse", value: agentResponse.Messages, scopeName: "Local");
return default;
}
@@ -603,8 +603,8 @@ public static class TestWorkflowProvider
// <inheritdoc />
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
object? evaluatedValue = await context.EvaluateValueAsync<object>("Local.StallCount + 1").ConfigureAwait(false);
await context.QueueStateUpdateAsync(key: "StallCount", value: evaluatedValue, scopeName: "Local").ConfigureAwait(false);
object? evaluatedValue = await context.EvaluateValueAsync<object>("Local.StallCount + 1");
await context.QueueStateUpdateAsync(key: "StallCount", value: evaluatedValue, scopeName: "Local");
return default;
}
@@ -618,13 +618,13 @@ public static class TestWorkflowProvider
// <inheritdoc />
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
bool condition0 = await context.EvaluateValueAsync<bool>(".TypedProgressLedger.is_in_loop.answer").ConfigureAwait(false);
bool condition0 = await context.EvaluateValueAsync<bool>(".TypedProgressLedger.is_in_loop.answer");
if (condition0)
{
return "conditionItem_fpaNL9";
}
bool condition1 = await context.EvaluateValueAsync<bool>("Not(Local.TypedProgressLedger.is_progress_being_made.answer)").ConfigureAwait(false);
bool condition1 = await context.EvaluateValueAsync<bool>("Not(Local.TypedProgressLedger.is_progress_being_made.answer)");
if (condition1)
{
return "conditionItem_NnqvXh";
@@ -649,7 +649,7 @@ public static class TestWorkflowProvider
"""
);
AgentRunResponse response = new([new ChatMessage(ChatRole.Assistant, activityText)]);
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response)).ConfigureAwait(false);
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response));
return default;
}
@@ -670,7 +670,7 @@ public static class TestWorkflowProvider
"""
);
AgentRunResponse response = new([new ChatMessage(ChatRole.Assistant, activityText)]);
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response)).ConfigureAwait(false);
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response));
return default;
}
@@ -684,7 +684,7 @@ public static class TestWorkflowProvider
// <inheritdoc />
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
bool condition0 = await context.EvaluateValueAsync<bool>("Local.StallCount > 2").ConfigureAwait(false);
bool condition0 = await context.EvaluateValueAsync<bool>("Local.StallCount > 2");
if (condition0)
{
return "conditionItem_NlQTBv";
@@ -709,7 +709,7 @@ public static class TestWorkflowProvider
"""
);
AgentRunResponse response = new([new ChatMessage(ChatRole.Assistant, activityText)]);
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response)).ConfigureAwait(false);
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response));
return default;
}
@@ -723,7 +723,7 @@ public static class TestWorkflowProvider
// <inheritdoc />
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
bool condition0 = await context.EvaluateValueAsync<bool>("Local.RestartCount > 2").ConfigureAwait(false);
bool condition0 = await context.EvaluateValueAsync<bool>("Local.RestartCount > 2");
if (condition0)
{
return "conditionItem_EXAlhZ";
@@ -748,7 +748,7 @@ public static class TestWorkflowProvider
"""
);
AgentRunResponse response = new([new ChatMessage(ChatRole.Assistant, activityText)]);
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response)).ConfigureAwait(false);
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response));
return default;
}
@@ -769,7 +769,7 @@ public static class TestWorkflowProvider
"""
);
AgentRunResponse response = new([new ChatMessage(ChatRole.Assistant, activityText)]);
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response)).ConfigureAwait(false);
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response));
return default;
}
@@ -783,14 +783,14 @@ public static class TestWorkflowProvider
// <inheritdoc />
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
string? agentName = await context.ReadStateAsync<string>(key: "FOUNDRY_AGENT_RESEARCHANALYST", scopeName: "Env").ConfigureAwait(false);
string? agentName = await context.ReadStateAsync<string>(key: "FOUNDRY_AGENT_RESEARCHANALYST", scopeName: "Env");
if (string.IsNullOrWhiteSpace(agentName))
{
throw new InvalidOperationException($"Agent name must be defined: {this.Id}");
}
string? conversationId = await context.ReadStateAsync<string>(key: "InternalConversationId", scopeName: "Local").ConfigureAwait(false);
string? conversationId = await context.ReadStateAsync<string>(key: "InternalConversationId", scopeName: "Local");
bool autoSend = true;
string additionalInstructions =
await context.FormatTemplateAsync(
@@ -810,7 +810,7 @@ public static class TestWorkflowProvider
"As a reminder, we are working to solve the following task:
" & Local.InputTask)
""").ConfigureAwait(false);
""");
AgentRunResponse agentResponse =
await InvokeAgentAsync(
@@ -820,14 +820,14 @@ public static class TestWorkflowProvider
autoSend,
additionalInstructions,
inputMessages,
cancellationToken).ConfigureAwait(false);
cancellationToken);
if (autoSend)
{
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, agentResponse)).ConfigureAwait(false);
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, agentResponse));
}
await context.QueueStateUpdateAsync(key: "TaskFacts", value: agentResponse.Messages, scopeName: "Local").ConfigureAwait(false);
await context.QueueStateUpdateAsync(key: "TaskFacts", value: agentResponse.Messages, scopeName: "Local");
return default;
}
@@ -848,7 +848,7 @@ public static class TestWorkflowProvider
"""
);
AgentRunResponse response = new([new ChatMessage(ChatRole.Assistant, activityText)]);
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response)).ConfigureAwait(false);
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response));
return default;
}
@@ -862,14 +862,14 @@ public static class TestWorkflowProvider
// <inheritdoc />
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
string? agentName = await context.ReadStateAsync<string>(key: "FOUNDRY_AGENT_RESEARCHMANAGER", scopeName: "Env").ConfigureAwait(false);
string? agentName = await context.ReadStateAsync<string>(key: "FOUNDRY_AGENT_RESEARCHMANAGER", scopeName: "Env");
if (string.IsNullOrWhiteSpace(agentName))
{
throw new InvalidOperationException($"Agent name must be defined: {this.Id}");
}
string? conversationId = await context.ReadStateAsync<string>(key: "InternalConversationId", scopeName: "Local").ConfigureAwait(false);
string? conversationId = await context.ReadStateAsync<string>(key: "InternalConversationId", scopeName: "Local");
bool autoSend = true;
string additionalInstructions =
await context.FormatTemplateAsync(
@@ -891,14 +891,14 @@ public static class TestWorkflowProvider
autoSend,
additionalInstructions,
inputMessages,
cancellationToken).ConfigureAwait(false);
cancellationToken);
if (autoSend)
{
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, agentResponse)).ConfigureAwait(false);
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, agentResponse));
}
await context.QueueStateUpdateAsync(key: "Plan", value: agentResponse.Messages, scopeName: "Local").ConfigureAwait(false);
await context.QueueStateUpdateAsync(key: "Plan", value: agentResponse.Messages, scopeName: "Local");
return default;
}
@@ -935,8 +935,8 @@ public static class TestWorkflowProvider
Here is the plan to follow as best as possible:
" & Local.Plan.Text
""").ConfigureAwait(false);
await context.QueueStateUpdateAsync(key: "TaskInstructions", value: evaluatedValue, scopeName: "Local").ConfigureAwait(false);
""");
await context.QueueStateUpdateAsync(key: "TaskInstructions", value: evaluatedValue, scopeName: "Local");
return default;
}
@@ -951,7 +951,7 @@ public static class TestWorkflowProvider
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
object? evaluatedValue = 0;
await context.QueueStateUpdateAsync(key: "StallCount", value: evaluatedValue, scopeName: "Local").ConfigureAwait(false);
await context.QueueStateUpdateAsync(key: "StallCount", value: evaluatedValue, scopeName: "Local");
return default;
}
@@ -965,8 +965,8 @@ public static class TestWorkflowProvider
// <inheritdoc />
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
object? evaluatedValue = await context.EvaluateValueAsync<object>("Local.RestartCount + 1").ConfigureAwait(false);
await context.QueueStateUpdateAsync(key: "RestartCount", value: evaluatedValue, scopeName: "Local").ConfigureAwait(false);
object? evaluatedValue = await context.EvaluateValueAsync<object>("Local.RestartCount + 1");
await context.QueueStateUpdateAsync(key: "RestartCount", value: evaluatedValue, scopeName: "Local");
return default;
}
@@ -989,7 +989,7 @@ public static class TestWorkflowProvider
"""
);
AgentRunResponse response = new([new ChatMessage(ChatRole.Assistant, activityText)]);
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response)).ConfigureAwait(false);
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response));
return default;
}
@@ -1004,7 +1004,7 @@ public static class TestWorkflowProvider
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
object? evaluatedValue = 0;
await context.QueueStateUpdateAsync(key: "StallCount", value: evaluatedValue, scopeName: "Local").ConfigureAwait(false);
await context.QueueStateUpdateAsync(key: "StallCount", value: evaluatedValue, scopeName: "Local");
return default;
}
@@ -1018,8 +1018,8 @@ public static class TestWorkflowProvider
// <inheritdoc />
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
object? evaluatedValue = await context.EvaluateValueAsync<object>("Search(Local.AvailableAgents, Local.TypedProgressLedger.next_speaker.answer, name)").ConfigureAwait(false);
await context.QueueStateUpdateAsync(key: "NextSpeaker", value: evaluatedValue, scopeName: "Local").ConfigureAwait(false);
object? evaluatedValue = await context.EvaluateValueAsync<object>("Search(Local.AvailableAgents, Local.TypedProgressLedger.next_speaker.answer, name)");
await context.QueueStateUpdateAsync(key: "NextSpeaker", value: evaluatedValue, scopeName: "Local");
return default;
}
@@ -1033,7 +1033,7 @@ public static class TestWorkflowProvider
// <inheritdoc />
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
bool condition0 = await context.EvaluateValueAsync<bool>("CountRows(Local.NextSpeaker) = 1").ConfigureAwait(false);
bool condition0 = await context.EvaluateValueAsync<bool>("CountRows(Local.NextSpeaker) = 1");
if (condition0)
{
return "conditionItem_GmigcU";
@@ -1051,21 +1051,21 @@ public static class TestWorkflowProvider
// <inheritdoc />
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
string? agentName = await context.EvaluateValueAsync<string>("First(Local.NextSpeaker).agentid").ConfigureAwait(false);
string? agentName = await context.EvaluateValueAsync<string>("First(Local.NextSpeaker).agentid");
if (string.IsNullOrWhiteSpace(agentName))
{
throw new InvalidOperationException($"Agent name must be defined: {this.Id}");
}
string? conversationId = await context.ReadStateAsync<string>(key: "ConversationId", scopeName: "System").ConfigureAwait(false);
string? conversationId = await context.ReadStateAsync<string>(key: "ConversationId", scopeName: "System");
bool autoSend = true;
string additionalInstructions =
await context.FormatTemplateAsync(
"""
{Local.TypedProgressLedger.instruction_or_question.answer}
""");
IList<ChatMessage>? inputMessages = await context.ReadListAsync<ChatMessage>(key: "SeedTask", scopeName: "Local").ConfigureAwait(false);
IList<ChatMessage>? inputMessages = await context.ReadListAsync<ChatMessage>(key: "SeedTask", scopeName: "Local");
AgentRunResponse agentResponse =
await InvokeAgentAsync(
@@ -1075,14 +1075,14 @@ public static class TestWorkflowProvider
autoSend,
additionalInstructions,
inputMessages,
cancellationToken).ConfigureAwait(false);
cancellationToken);
if (autoSend)
{
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, agentResponse)).ConfigureAwait(false);
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, agentResponse));
}
await context.QueueStateUpdateAsync(key: "AgentResponse", value: agentResponse.Messages, scopeName: "Local").ConfigureAwait(false);
await context.QueueStateUpdateAsync(key: "AgentResponse", value: agentResponse.Messages, scopeName: "Local");
return default;
}
@@ -1096,8 +1096,8 @@ public static class TestWorkflowProvider
// <inheritdoc />
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
object? evaluatedValue = await context.EvaluateValueAsync<object>("Last(Local.AgentResponse).Text").ConfigureAwait(false);
await context.QueueStateUpdateAsync(key: "AgentResponseText", value: evaluatedValue, scopeName: "Local").ConfigureAwait(false);
object? evaluatedValue = await context.EvaluateValueAsync<object>("Last(Local.AgentResponse).Text");
await context.QueueStateUpdateAsync(key: "AgentResponseText", value: evaluatedValue, scopeName: "Local");
return default;
}
@@ -1111,7 +1111,7 @@ public static class TestWorkflowProvider
{
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
await context.QueueStateUpdateAsync(key: "SeedTask", value: UnassignedValue.Instance, scopeName: "Local").ConfigureAwait(false);
await context.QueueStateUpdateAsync(key: "SeedTask", value: UnassignedValue.Instance, scopeName: "Local");
return default;
}
@@ -1132,7 +1132,7 @@ public static class TestWorkflowProvider
"""
);
AgentRunResponse response = new([new ChatMessage(ChatRole.Assistant, activityText)]);
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response)).ConfigureAwait(false);
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response));
return default;
}
@@ -1146,8 +1146,8 @@ public static class TestWorkflowProvider
// <inheritdoc />
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
object? evaluatedValue = await context.EvaluateValueAsync<object>("Local.StallCount + 1").ConfigureAwait(false);
await context.QueueStateUpdateAsync(key: "StallCount", value: evaluatedValue, scopeName: "Local").ConfigureAwait(false);
object? evaluatedValue = await context.EvaluateValueAsync<object>("Local.StallCount + 1");
await context.QueueStateUpdateAsync(key: "StallCount", value: evaluatedValue, scopeName: "Local");
return default;
}
@@ -76,7 +76,7 @@ internal sealed class Program
string? messageId = null;
await foreach (WorkflowEvent workflowEvent in run.WatchStreamAsync().ConfigureAwait(false))
await foreach (WorkflowEvent workflowEvent in run.WatchStreamAsync())
{
switch (workflowEvent)
{
@@ -150,7 +150,7 @@ internal sealed class Program
string? messageId = null;
await foreach (WorkflowEvent workflowEvent in run.Run.WatchStreamAsync().ConfigureAwait(false))
await foreach (WorkflowEvent workflowEvent in run.Run.WatchStreamAsync())
{
switch (workflowEvent)
{
@@ -187,12 +187,12 @@ internal sealed class Program
if (response is not null)
{
ExternalResponse requestResponse = requestInfo.Request.CreateResponse(response);
await run.Run.SendResponseAsync(requestResponse).ConfigureAwait(false);
await run.Run.SendResponseAsync(requestResponse);
response = null;
}
else
{
await run.Run.DisposeAsync().ConfigureAwait(false);
await run.Run.DisposeAsync();
return requestInfo.Request;
}
break;
@@ -24,18 +24,18 @@ public static class Program
private static async Task Main()
{
// Create the workflow
var workflow = await WorkflowHelper.GetWorkflowAsync().ConfigureAwait(false);
var workflow = await WorkflowHelper.GetWorkflowAsync();
// Execute the workflow
await using StreamingRun handle = await InProcessExecution.StreamAsync(workflow, NumberSignal.Init).ConfigureAwait(false);
await foreach (WorkflowEvent evt in handle.WatchStreamAsync().ConfigureAwait(false))
await using StreamingRun handle = await InProcessExecution.StreamAsync(workflow, NumberSignal.Init);
await foreach (WorkflowEvent evt in handle.WatchStreamAsync())
{
switch (evt)
{
case RequestInfoEvent requestInputEvt:
// Handle `RequestInfoEvent` from the workflow
ExternalResponse response = HandleExternalRequest(requestInputEvt.Request);
await handle.SendResponseAsync(response).ConfigureAwait(false);
await handle.SendResponseAsync(response);
break;
case WorkflowOutputEvent outputEvt:
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows;
using Microsoft.Agents.AI.Workflows.Reflection;
namespace WorkflowHumanInTheLoopBasicSample;
@@ -39,7 +38,7 @@ internal enum NumberSignal
/// <summary>
/// Executor that judges the guess and provides feedback.
/// </summary>
internal sealed class JudgeExecutor() : ReflectingExecutor<JudgeExecutor>("Judge"), IMessageHandler<int>
internal sealed class JudgeExecutor() : Executor<int>("Judge")
{
private readonly int _targetNumber;
private int _tries;
@@ -53,21 +52,20 @@ internal sealed class JudgeExecutor() : ReflectingExecutor<JudgeExecutor>("Judge
this._targetNumber = targetNumber;
}
public async ValueTask HandleAsync(int message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask HandleAsync(int message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
this._tries++;
if (message == this._targetNumber)
{
await context.YieldOutputAsync($"{this._targetNumber} found in {this._tries} tries!", cancellationToken)
.ConfigureAwait(false);
await context.YieldOutputAsync($"{this._targetNumber} found in {this._tries} tries!", cancellationToken);
}
else if (message < this._targetNumber)
{
await context.SendMessageAsync(NumberSignal.Below, cancellationToken: cancellationToken).ConfigureAwait(false);
await context.SendMessageAsync(NumberSignal.Below, cancellationToken: cancellationToken);
}
else
{
await context.SendMessageAsync(NumberSignal.Above, cancellationToken: cancellationToken).ConfigureAwait(false);
await context.SendMessageAsync(NumberSignal.Above, cancellationToken: cancellationToken);
}
}
}
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows;
using Microsoft.Agents.AI.Workflows.Reflection;
namespace WorkflowLoopSample;
@@ -33,8 +32,8 @@ public static class Program
.BuildAsync<NumberSignal>();
// Execute the workflow
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, NumberSignal.Init).ConfigureAwait(false);
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, NumberSignal.Init);
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
{
if (evt is WorkflowOutputEvent outputEvent)
{
@@ -57,7 +56,7 @@ internal enum NumberSignal
/// <summary>
/// Executor that makes a guess based on the current bounds.
/// </summary>
internal sealed class GuessNumberExecutor : ReflectingExecutor<GuessNumberExecutor>, IMessageHandler<NumberSignal>
internal sealed class GuessNumberExecutor : Executor<NumberSignal>
{
/// <summary>
/// The lower bound of the guessing range.
@@ -83,20 +82,20 @@ internal sealed class GuessNumberExecutor : ReflectingExecutor<GuessNumberExecut
private int NextGuess => (this.LowerBound + this.UpperBound) / 2;
public async ValueTask HandleAsync(NumberSignal message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask HandleAsync(NumberSignal message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
switch (message)
{
case NumberSignal.Init:
await context.SendMessageAsync(this.NextGuess, cancellationToken: cancellationToken).ConfigureAwait(false);
await context.SendMessageAsync(this.NextGuess, cancellationToken: cancellationToken);
break;
case NumberSignal.Above:
this.UpperBound = this.NextGuess - 1;
await context.SendMessageAsync(this.NextGuess, cancellationToken: cancellationToken).ConfigureAwait(false);
await context.SendMessageAsync(this.NextGuess, cancellationToken: cancellationToken);
break;
case NumberSignal.Below:
this.LowerBound = this.NextGuess + 1;
await context.SendMessageAsync(this.NextGuess, cancellationToken: cancellationToken).ConfigureAwait(false);
await context.SendMessageAsync(this.NextGuess, cancellationToken: cancellationToken);
break;
}
}
@@ -105,7 +104,7 @@ internal sealed class GuessNumberExecutor : ReflectingExecutor<GuessNumberExecut
/// <summary>
/// Executor that judges the guess and provides feedback.
/// </summary>
internal sealed class JudgeExecutor : ReflectingExecutor<JudgeExecutor>, IMessageHandler<int>
internal sealed class JudgeExecutor : Executor<int>
{
private readonly int _targetNumber;
private int _tries;
@@ -120,21 +119,21 @@ internal sealed class JudgeExecutor : ReflectingExecutor<JudgeExecutor>, IMessag
this._targetNumber = targetNumber;
}
public async ValueTask HandleAsync(int message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask HandleAsync(int message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
this._tries++;
if (message == this._targetNumber)
{
await context.YieldOutputAsync($"{this._targetNumber} found in {this._tries} tries!", cancellationToken)
.ConfigureAwait(false);
;
}
else if (message < this._targetNumber)
{
await context.SendMessageAsync(NumberSignal.Below, cancellationToken: cancellationToken).ConfigureAwait(false);
await context.SendMessageAsync(NumberSignal.Below, cancellationToken: cancellationToken);
}
else
{
await context.SendMessageAsync(NumberSignal.Above, cancellationToken: cancellationToken).ConfigureAwait(false);
await context.SendMessageAsync(NumberSignal.Above, cancellationToken: cancellationToken);
}
}
}
@@ -3,7 +3,6 @@
using System.Diagnostics;
using Azure.Monitor.OpenTelemetry.Exporter;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Agents.AI.Workflows.Reflection;
using OpenTelemetry;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
@@ -69,7 +68,7 @@ public static class Program
/// <summary>
/// First executor: converts input text to uppercase.
/// </summary>
internal sealed class UppercaseExecutor() : ReflectingExecutor<UppercaseExecutor>("UppercaseExecutor"), IMessageHandler<string, string>
internal sealed class UppercaseExecutor() : Executor<string, string>("UppercaseExecutor")
{
/// <summary>
/// Processes the input message by converting it to uppercase.
@@ -79,14 +78,14 @@ internal sealed class UppercaseExecutor() : ReflectingExecutor<UppercaseExecutor
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
/// The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>The input text converted to uppercase</returns>
public async ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default) =>
public override async ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default) =>
message.ToUpperInvariant(); // The return value will be sent as a message along an edge to subsequent executors
}
/// <summary>
/// Second executor: reverses the input text and completes the workflow.
/// </summary>
internal sealed class ReverseTextExecutor() : ReflectingExecutor<ReverseTextExecutor>("ReverseTextExecutor"), IMessageHandler<string, string>
internal sealed class ReverseTextExecutor() : Executor<string, string>("ReverseTextExecutor")
{
/// <summary>
/// Processes the input message by reversing the text.
@@ -96,6 +95,6 @@ internal sealed class ReverseTextExecutor() : ReflectingExecutor<ReverseTextExec
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
/// The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>The input text reversed</returns>
public async ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
=> new(message.Reverse().ToArray());
}
@@ -2,7 +2,6 @@
using System.Diagnostics;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Agents.AI.Workflows.Reflection;
using OpenTelemetry;
using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
@@ -71,7 +70,7 @@ public static class Program
/// <summary>
/// First executor: converts input text to uppercase.
/// </summary>
internal sealed class UppercaseExecutor() : ReflectingExecutor<UppercaseExecutor>("UppercaseExecutor"), IMessageHandler<string, string>
internal sealed class UppercaseExecutor() : Executor<string, string>("UppercaseExecutor")
{
/// <summary>
/// Processes the input message by converting it to uppercase.
@@ -81,14 +80,14 @@ internal sealed class UppercaseExecutor() : ReflectingExecutor<UppercaseExecutor
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
/// The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>The input text converted to uppercase</returns>
public async ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default) =>
public override async ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default) =>
message.ToUpperInvariant(); // The return value will be sent as a message along an edge to subsequent executors
}
/// <summary>
/// Second executor: reverses the input text and completes the workflow.
/// </summary>
internal sealed class ReverseTextExecutor() : ReflectingExecutor<ReverseTextExecutor>("ReverseTextExecutor"), IMessageHandler<string, string>
internal sealed class ReverseTextExecutor() : Executor<string, string>("ReverseTextExecutor")
{
/// <summary>
/// Processes the input message by reversing the text.
@@ -98,6 +97,6 @@ internal sealed class ReverseTextExecutor() : ReflectingExecutor<ReverseTextExec
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
/// The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>The input text reversed</returns>
public async ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
=> new(message.Reverse().ToArray());
}
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows;
using Microsoft.Agents.AI.Workflows.Reflection;
namespace WorkflowSharedStatesSample;
@@ -52,9 +51,9 @@ internal static class FileContentStateConstants
public const string FileContentStateScope = "FileContentState";
}
internal sealed class FileReadExecutor() : ReflectingExecutor<FileReadExecutor>("FileReadExecutor"), IMessageHandler<string, string>
internal sealed class FileReadExecutor() : Executor<string, string>("FileReadExecutor")
{
public async ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
// Read file content from embedded resource
string fileContent = Resources.Read(message);
@@ -72,9 +71,9 @@ internal sealed class FileStats
public int WordCount { get; set; }
}
internal sealed class WordCountingExecutor() : ReflectingExecutor<WordCountingExecutor>("WordCountingExecutor"), IMessageHandler<string, FileStats>
internal sealed class WordCountingExecutor() : Executor<string, FileStats>("WordCountingExecutor")
{
public async ValueTask<FileStats> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask<FileStats> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
// Retrieve the file content from the shared state
var fileContent = await context.ReadStateAsync<string>(message, scopeName: FileContentStateConstants.FileContentStateScope, cancellationToken)
@@ -86,10 +85,9 @@ internal sealed class WordCountingExecutor() : ReflectingExecutor<WordCountingEx
}
}
internal sealed class ParagraphCountingExecutor() : ReflectingExecutor<ParagraphCountingExecutor>("ParagraphCountingExecutor"),
IMessageHandler<string, FileStats>
internal sealed class ParagraphCountingExecutor() : Executor<string, FileStats>("ParagraphCountingExecutor")
{
public async ValueTask<FileStats> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask<FileStats> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
// Retrieve the file content from the shared state
var fileContent = await context.ReadStateAsync<string>(message, scopeName: FileContentStateConstants.FileContentStateScope, cancellationToken)
@@ -101,11 +99,11 @@ internal sealed class ParagraphCountingExecutor() : ReflectingExecutor<Paragraph
}
}
internal sealed class AggregationExecutor() : ReflectingExecutor<AggregationExecutor>("AggregationExecutor"), IMessageHandler<FileStats>
internal sealed class AggregationExecutor() : Executor<FileStats>("AggregationExecutor")
{
private readonly List<FileStats> _messages = [];
public async ValueTask HandleAsync(FileStats message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override async ValueTask HandleAsync(FileStats message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
this._messages.Add(message);
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows;
using Microsoft.Agents.AI.Workflows.Reflection;
namespace WorkflowExecutorsAndEdgesSample;
@@ -44,7 +43,7 @@ public static class Program
/// <summary>
/// First executor: converts input text to uppercase.
/// </summary>
internal sealed class UppercaseExecutor() : ReflectingExecutor<UppercaseExecutor>("UppercaseExecutor"), IMessageHandler<string, string>
internal sealed class UppercaseExecutor() : Executor<string, string>("UppercaseExecutor")
{
/// <summary>
/// Processes the input message by converting it to uppercase.
@@ -54,14 +53,14 @@ internal sealed class UppercaseExecutor() : ReflectingExecutor<UppercaseExecutor
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
/// The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>The input text converted to uppercase</returns>
public async ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default) =>
message.ToUpperInvariant(); // The return value will be sent as a message along an edge to subsequent executors
public override ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default) =>
ValueTask.FromResult(message.ToUpperInvariant()); // The return value will be sent as a message along an edge to subsequent executors
}
/// <summary>
/// Second executor: reverses the input text and completes the workflow.
/// </summary>
internal sealed class ReverseTextExecutor() : ReflectingExecutor<ReverseTextExecutor>("ReverseTextExecutor"), IMessageHandler<string, string>
internal sealed class ReverseTextExecutor() : Executor<string, string>("ReverseTextExecutor")
{
/// <summary>
/// Processes the input message by reversing the text.
@@ -71,9 +70,9 @@ internal sealed class ReverseTextExecutor() : ReflectingExecutor<ReverseTextExec
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
/// The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>The input text reversed</returns>
public async ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
public override ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
// Because we do not suppress it, the returned result will be yielded as an output from this executor.
return string.Concat(message.Reverse());
return ValueTask.FromResult(string.Concat(message.Reverse()));
}
}
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows;
using Microsoft.Agents.AI.Workflows.Reflection;
namespace WorkflowStreamingSample;
@@ -30,7 +29,7 @@ public static class Program
// Execute the workflow in streaming mode
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, "Hello, World!");
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
{
if (evt is ExecutorCompletedEvent executorCompleted)
{
@@ -43,7 +42,7 @@ public static class Program
/// <summary>
/// First executor: converts input text to uppercase.
/// </summary>
internal sealed class UppercaseExecutor() : ReflectingExecutor<UppercaseExecutor>("UppercaseExecutor"), IMessageHandler<string, string>
internal sealed class UppercaseExecutor() : Executor<string, string>("UppercaseExecutor")
{
/// <summary>
/// Processes the input message by converting it to uppercase.
@@ -53,14 +52,14 @@ internal sealed class UppercaseExecutor() : ReflectingExecutor<UppercaseExecutor
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
/// The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>The input text converted to uppercase</returns>
public async ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default) =>
message.ToUpperInvariant(); // The return value will be sent as a message along an edge to subsequent executors
public override ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default) =>
ValueTask.FromResult(message.ToUpperInvariant()); // The return value will be sent as a message along an edge to subsequent executors
}
/// <summary>
/// Second executor: reverses the input text and completes the workflow.
/// </summary>
internal sealed class ReverseTextExecutor() : ReflectingExecutor<ReverseTextExecutor>("ReverseTextExecutor"), IMessageHandler<string, string>
internal sealed class ReverseTextExecutor() : Executor<string, string>("ReverseTextExecutor")
{
/// <summary>
/// Processes the input message by reversing the text.
@@ -70,9 +69,9 @@ internal sealed class ReverseTextExecutor() : ReflectingExecutor<ReverseTextExec
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
/// The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>The input text reversed</returns>
public async ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken)
public override ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
// Because we do not suppress it, the returned result will be yielded as an output from this executor.
return string.Concat(message.Reverse());
return ValueTask.FromResult(string.Concat(message.Reverse()));
}
}
@@ -50,7 +50,7 @@ public static class Program
// The agents are wrapped as executors. When they receive messages,
// they will cache the messages and only start processing when they receive a TurnToken.
await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
{
if (evt is AgentRunUpdateEvent executorComplete)
{
@@ -86,7 +86,7 @@ public static class Program
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))
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
{
if (evt is AgentRunUpdateEvent e)
{
@@ -58,8 +58,7 @@ AIAgent reporter = new ChatClientAgent(anthropic,
// Build a sequential workflow: Researcher -> Fact-Checker -> Reporter
AIAgent workflowAgent = await AgentWorkflowBuilder.BuildSequential(researcher, factChecker, reporter)
.AsAgentAsync()
.ConfigureAwait(false);
.AsAgentAsync();
// Run the workflow, streaming the output as it arrives.
string? lastAuthor = null;