forked from microsoft/agent-framework
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathWorkflowHelper.cs
More file actions
153 lines (135 loc) · 5.82 KB
/
WorkflowHelper.cs
File metadata and controls
153 lines (135 loc) · 5.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows;
using Microsoft.Agents.AI.Workflows.Reflection;
namespace WorkflowCheckpointAndRehydrateSample;
internal static class WorkflowHelper
{
/// <summary>
/// Get a workflow that plays a number guessing game with checkpointing support.
/// 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>
internal static ValueTask<Workflow<NumberSignal>> GetWorkflowAsync()
{
// Create the executors
GuessNumberExecutor guessNumberExecutor = new(1, 100);
JudgeExecutor judgeExecutor = new(42);
// Build the workflow by connecting executors in a loop
return new WorkflowBuilder(guessNumberExecutor)
.AddEdge(guessNumberExecutor, judgeExecutor)
.AddEdge(judgeExecutor, guessNumberExecutor)
.WithOutputFrom(judgeExecutor)
.BuildAsync<NumberSignal>();
}
}
/// <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>("Guess"), 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; }
private const string StateKey = "GuessNumberExecutorState";
/// <summary>
/// Initializes a new instance of the <see cref="GuessNumberExecutor"/> class.
/// </summary>
/// <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(int lowerBound, int upperBound) : this()
{
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>
/// Checkpoint the current state of the executor.
/// This must be overridden to save any state that is needed to resume the executor.
/// </summary>
protected override ValueTask OnCheckpointingAsync(IWorkflowContext context, CancellationToken cancellationToken = default) =>
context.QueueStateUpdateAsync(StateKey, (this.LowerBound, this.UpperBound));
/// <summary>
/// Restore the state of the executor from a checkpoint.
/// 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).ConfigureAwait(false);
}
/// <summary>
/// Executor that judges the guess and provides feedback.
/// </summary>
internal sealed class JudgeExecutor() : ReflectingExecutor<JudgeExecutor>("Judge"), IMessageHandler<int>
{
private readonly int _targetNumber;
private int _tries;
private const string StateKey = "JudgeExecutorState";
/// <summary>
/// Initializes a new instance of the <see cref="JudgeExecutor"/> class.
/// </summary>
/// <param name="targetNumber">The number to be guessed.</param>
public JudgeExecutor(int targetNumber) : this()
{
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);
}
}
/// <summary>
/// Checkpoint the current state of the executor.
/// This must be overridden to save any state that is needed to resume the executor.
/// </summary>
protected override ValueTask OnCheckpointingAsync(IWorkflowContext context, CancellationToken cancellationToken = default) =>
context.QueueStateUpdateAsync(StateKey, this._tries);
/// <summary>
/// Restore the state of the executor from a checkpoint.
/// 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).ConfigureAwait(false);
}