// Copyright (c) Microsoft. All rights reserved. using System; using System.Threading.Tasks; using Microsoft.Agents.Workflows; using Microsoft.Agents.Workflows.Reflection; namespace WorkflowLoopSample; /// /// This sample demonstrates a simple number guessing game using a workflow with looping behavior. /// /// The workflow consists of two executors that are connected in a feedback loop: /// 1. GuessNumberExecutor: Makes a guess based on the current known bounds. /// 2. JudgeExecutor: Evaluates the guess and provides feedback. /// The workflow continues until the correct number is guessed. /// /// /// Pre-requisites: /// - Foundational samples should be completed first. /// public static class Program { private static async Task Main() { // Create the executors GuessNumberExecutor guessNumberExecutor = new("GuessNumber", 1, 100); JudgeExecutor judgeExecutor = new("Judge", 42); // Build the workflow by connecting executors in a loop var workflow = await new WorkflowBuilder(guessNumberExecutor) .AddEdge(guessNumberExecutor, judgeExecutor) .AddEdge(judgeExecutor, guessNumberExecutor) .WithOutputFrom(judgeExecutor) .BuildAsync(); // Execute the workflow StreamingRun run = await InProcessExecution.StreamAsync(workflow, NumberSignal.Init).ConfigureAwait(false); await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false)) { if (evt is WorkflowOutputEvent outputEvent) { Console.WriteLine($"Result: {outputEvent}"); } } } } /// /// Signals used for communication between GuessNumberExecutor and JudgeExecutor. /// internal enum NumberSignal { Init, Above, Below, } /// /// Executor that makes a guess based on the current bounds. /// internal sealed class GuessNumberExecutor : ReflectingExecutor, IMessageHandler { /// /// The lower bound of the guessing range. /// public int LowerBound { get; private set; } /// /// The upper bound of the guessing range. /// public int UpperBound { get; private set; } /// /// Initializes a new instance of the class. /// /// A unique identifier for the executor. /// The initial lower bound of the guessing range. /// The initial upper bound of the guessing range. public GuessNumberExecutor(string id, int lowerBound, int upperBound) : base(id) { this.LowerBound = lowerBound; this.UpperBound = upperBound; } private int NextGuess => (this.LowerBound + this.UpperBound) / 2; public async ValueTask HandleAsync(NumberSignal message, IWorkflowContext context) { switch (message) { case NumberSignal.Init: await context.SendMessageAsync(this.NextGuess).ConfigureAwait(false); break; case NumberSignal.Above: this.UpperBound = this.NextGuess - 1; await context.SendMessageAsync(this.NextGuess).ConfigureAwait(false); break; case NumberSignal.Below: this.LowerBound = this.NextGuess + 1; await context.SendMessageAsync(this.NextGuess).ConfigureAwait(false); break; } } } /// /// Executor that judges the guess and provides feedback. /// internal sealed class JudgeExecutor : ReflectingExecutor, IMessageHandler { private readonly int _targetNumber; private int _tries; /// /// Initializes a new instance of the class. /// /// A unique identifier for the executor. /// The number to be guessed. public JudgeExecutor(string id, int targetNumber) : base(id) { this._targetNumber = targetNumber; } public async ValueTask HandleAsync(int message, IWorkflowContext context) { this._tries++; if (message == this._targetNumber) { await context.YieldOutputAsync($"{this._targetNumber} found in {this._tries} tries!") .ConfigureAwait(false); } else if (message < this._targetNumber) { await context.SendMessageAsync(NumberSignal.Below).ConfigureAwait(false); } else { await context.SendMessageAsync(NumberSignal.Above).ConfigureAwait(false); } } }