chore: import upstream snapshot with attribution
CodeQL / Analyze (csharp) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
CodeQL / Analyze (csharp) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
This commit is contained in:
+15
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Workflows\Microsoft.Agents.AI.Workflows.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
|
||||
namespace WorkflowHumanInTheLoopBasicSample;
|
||||
|
||||
/// <summary>
|
||||
/// This sample introduces the concept of RequestPort and ExternalRequest to enable
|
||||
/// human-in-the-loop interaction scenarios.
|
||||
/// A request port can be used as if it were an executor in the workflow graph. Upon receiving
|
||||
/// a message, the request port generates an RequestInfoEvent that gets emitted to the external world.
|
||||
/// The external world can then respond to the request by sending an ExternalResponse back to
|
||||
/// the workflow.
|
||||
/// The sample implements a simple number guessing game where the external user tries to guess
|
||||
/// a pre-defined target number. The workflow consists of a single JudgeExecutor that judges
|
||||
/// the user's guesses and provides feedback.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Pre-requisites:
|
||||
/// - Foundational samples should be completed first.
|
||||
/// </remarks>
|
||||
public static class Program
|
||||
{
|
||||
private static async Task Main()
|
||||
{
|
||||
// Create the workflow
|
||||
var workflow = WorkflowFactory.BuildWorkflow();
|
||||
|
||||
// Execute the workflow
|
||||
await using StreamingRun handle = await InProcessExecution.RunStreamingAsync(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);
|
||||
break;
|
||||
|
||||
case WorkflowOutputEvent outputEvt:
|
||||
// The workflow has yielded output
|
||||
Console.WriteLine($"Workflow completed with result: {outputEvt.Data}");
|
||||
return;
|
||||
|
||||
case WorkflowErrorEvent workflowError:
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.Error.WriteLine(workflowError.Exception?.ToString() ?? "Unknown workflow error occurred.");
|
||||
Console.ResetColor();
|
||||
return;
|
||||
|
||||
case ExecutorFailedEvent executorFailed:
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.Error.WriteLine($"Executor '{executorFailed.ExecutorId}' failed with {(executorFailed.Data == null ? "unknown error" : $"exception {executorFailed.Data}")}.");
|
||||
Console.ResetColor();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static ExternalResponse HandleExternalRequest(ExternalRequest request)
|
||||
{
|
||||
if (request.TryGetDataAs<NumberSignal>(out var signal))
|
||||
{
|
||||
switch (signal)
|
||||
{
|
||||
case NumberSignal.Init:
|
||||
int initialGuess = ReadIntegerFromConsole("Please provide your initial guess: ");
|
||||
return request.CreateResponse(initialGuess);
|
||||
case NumberSignal.Above:
|
||||
int lowerGuess = ReadIntegerFromConsole("You previously guessed too large. Please provide a new guess: ");
|
||||
return request.CreateResponse(lowerGuess);
|
||||
case NumberSignal.Below:
|
||||
int higherGuess = ReadIntegerFromConsole("You previously guessed too small. Please provide a new guess: ");
|
||||
return request.CreateResponse(higherGuess);
|
||||
}
|
||||
}
|
||||
|
||||
throw new NotSupportedException($"Request {request.PortInfo.RequestType} is not supported");
|
||||
}
|
||||
|
||||
private static int ReadIntegerFromConsole(string prompt)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
Console.Write(prompt);
|
||||
string? input = Console.ReadLine();
|
||||
if (int.TryParse(input, out int value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
Console.WriteLine("Invalid input. Please enter a valid integer.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
|
||||
namespace WorkflowHumanInTheLoopBasicSample;
|
||||
|
||||
internal static class WorkflowFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Get a workflow that plays a number guessing game with human-in-the-loop interaction.
|
||||
/// An input port allows the external world to provide inputs to the workflow upon requests.
|
||||
/// </summary>
|
||||
internal static Workflow BuildWorkflow()
|
||||
{
|
||||
// Create the executors
|
||||
RequestPort numberRequestPort = RequestPort.Create<NumberSignal, int>("GuessNumber");
|
||||
JudgeExecutor judgeExecutor = new(42);
|
||||
|
||||
// Build the workflow by connecting executors in a loop
|
||||
return new WorkflowBuilder(numberRequestPort)
|
||||
.AddEdge(numberRequestPort, judgeExecutor)
|
||||
.AddEdge(judgeExecutor, numberRequestPort)
|
||||
.WithOutputFrom(judgeExecutor)
|
||||
.Build();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signals used for communication between guesses and the JudgeExecutor.
|
||||
/// </summary>
|
||||
internal enum NumberSignal
|
||||
{
|
||||
Init,
|
||||
Above,
|
||||
Below,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executor that judges the guess and provides feedback.
|
||||
/// </summary>
|
||||
[SendsMessage(typeof(NumberSignal))]
|
||||
[YieldsOutput(typeof(string))]
|
||||
internal sealed class JudgeExecutor() : Executor<int>("Judge")
|
||||
{
|
||||
private readonly int _targetNumber;
|
||||
private int _tries;
|
||||
|
||||
/// <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 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user