mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
7ebe00ec3d
* Updates to async run loop. * fix: Workflow Onwership can be release by nonowner * fix: Incorrect handling of blockOnPending in StreamingRun Depending on whether we are running in streaming on non-streaming mode, we may be using the StreamingRun in different ways. Unfortunately, the only place we can really know what is the actual state of execution is in the RunEventStream implementations. This resulted in blocking where blocking was unneeded and occasionally not-blocking when blocking was needed. The fix is to move the logic of handling this blocking into RunEventStream implementations. * fix: Fix cleanup on error and end run This ensures we clean up the background resources correctly. * fix: Ensure we let the run loop proceed when shutting down * fix: Add timeout for Input Waiting * fix: Make the samples properly clean up `Run`s and `StreamingRun`s * fix: Simplify Declarative Workflow Run disposal pattern * Also fixes missing .Disposal() in Integration tests --------- Co-authored-by: Ben Thomas <ben.thomas@microsoft.com>
141 lines
4.7 KiB
C#
141 lines
4.7 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
using Microsoft.Agents.AI.Workflows;
|
|
using Microsoft.Agents.AI.Workflows.Reflection;
|
|
|
|
namespace WorkflowLoopSample;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Pre-requisites:
|
|
/// - Foundational samples should be completed first.
|
|
/// </remarks>
|
|
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<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))
|
|
{
|
|
if (evt is WorkflowOutputEvent outputEvent)
|
|
{
|
|
Console.WriteLine($"Result: {outputEvent}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Signals used for communication between GuessNumberExecutor and JudgeExecutor.
|
|
/// </summary>
|
|
internal enum NumberSignal
|
|
{
|
|
Init,
|
|
Above,
|
|
Below,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executor that makes a guess based on the current bounds.
|
|
/// </summary>
|
|
internal sealed class GuessNumberExecutor : ReflectingExecutor<GuessNumberExecutor>, IMessageHandler<NumberSignal>
|
|
{
|
|
/// <summary>
|
|
/// The lower bound of the guessing range.
|
|
/// </summary>
|
|
public int LowerBound { get; private set; }
|
|
|
|
/// <summary>
|
|
/// The upper bound of the guessing range.
|
|
/// </summary>
|
|
public int UpperBound { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="GuessNumberExecutor"/> class.
|
|
/// </summary>
|
|
/// <param name="id">A unique identifier for the executor.</param>
|
|
/// <param name="lowerBound">The initial lower bound of the guessing range.</param>
|
|
/// <param name="upperBound">The initial upper bound of the guessing range.</param>
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executor that judges the guess and provides feedback.
|
|
/// </summary>
|
|
internal sealed class JudgeExecutor : ReflectingExecutor<JudgeExecutor>, IMessageHandler<int>
|
|
{
|
|
private readonly int _targetNumber;
|
|
private int _tries;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="JudgeExecutor"/> class.
|
|
/// </summary>
|
|
/// <param name="id">A unique identifier for the executor.</param>
|
|
/// <param name="targetNumber">The number to be guessed.</param>
|
|
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);
|
|
}
|
|
}
|
|
}
|