// Copyright (c) Microsoft. All rights reserved. using Microsoft.Agents.AI.Workflows; 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 = new WorkflowBuilder(guessNumberExecutor) .AddEdge(guessNumberExecutor, judgeExecutor) .AddEdge(judgeExecutor, guessNumberExecutor) .WithOutputFrom(judgeExecutor) .Build(); // Execute the workflow await using StreamingRun run = await InProcessExecution.RunStreamingAsync(workflow, NumberSignal.Init); await foreach (WorkflowEvent evt in run.WatchStreamAsync()) { 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 : Executor { /// /// 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 override async ValueTask HandleAsync(NumberSignal message, IWorkflowContext context, CancellationToken cancellationToken = default) { switch (message) { case NumberSignal.Init: await context.SendMessageAsync(this.NextGuess, cancellationToken: cancellationToken); break; case NumberSignal.Above: this.UpperBound = this.NextGuess - 1; await context.SendMessageAsync(this.NextGuess, cancellationToken: cancellationToken); break; case NumberSignal.Below: this.LowerBound = this.NextGuess + 1; await context.SendMessageAsync(this.NextGuess, cancellationToken: cancellationToken); break; } } } /// /// Executor that judges the guess and provides feedback. /// internal sealed class JudgeExecutor : Executor { 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 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) ; } else if (message < this._targetNumber) { await context.SendMessageAsync(NumberSignal.Below, cancellationToken: cancellationToken); } else { await context.SendMessageAsync(NumberSignal.Above, cancellationToken: cancellationToken); } } }