mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
39e071c430
* feat: Make Executor id field mandatory When checkpointing is involved, it is critical to keep executor ids consistent between runs, even when recreating a new object tree for the workflow. The default id-setting mechanism generated a guid for part of the id, making it not work when restoring from a checkpoint. This change prevents this situation from arising. * feat: Enable running untyped Workflows With the change to enable delay-instantiation of executors and support for async Executor factory methods, we must instantiate the starting executor to know what are the valid input types for the workflow. To avoid forcing instantiation every time, and to better support workflows with multiple input types, we enable support for build and interacting with the base Workflow type without type annotations, and remove the requirement to know a valid input type when initiating a run. * feat: Support Output from any executor and multiple outputs.
126 lines
3.8 KiB
C#
126 lines
3.8 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
using System;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.Agents.Workflows.Execution;
|
|
using Microsoft.Agents.Workflows.Reflection;
|
|
using Moq;
|
|
|
|
namespace Microsoft.Agents.Workflows.UnitTests;
|
|
|
|
public class BaseTestExecutor<TActual>(string id) : ReflectingExecutor<TActual>(id) where TActual : ReflectingExecutor<TActual>
|
|
{
|
|
protected void OnInvokedHandler() => this.InvokedHandler = true;
|
|
|
|
public bool InvokedHandler
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
}
|
|
|
|
public class DefaultHandler() : BaseTestExecutor<DefaultHandler>(nameof(DefaultHandler)), IMessageHandler<object>
|
|
{
|
|
public ValueTask HandleAsync(object message, IWorkflowContext context)
|
|
{
|
|
this.OnInvokedHandler();
|
|
return this.Handler(message, context);
|
|
}
|
|
|
|
public Func<object, IWorkflowContext, ValueTask> Handler
|
|
{
|
|
get;
|
|
set;
|
|
} = (message, context) => default;
|
|
}
|
|
|
|
public class TypedHandler<TInput>() : BaseTestExecutor<TypedHandler<TInput>>(nameof(TypedHandler<TInput>)), IMessageHandler<TInput>
|
|
{
|
|
public ValueTask HandleAsync(TInput message, IWorkflowContext context)
|
|
{
|
|
this.OnInvokedHandler();
|
|
return this.Handler(message, context);
|
|
}
|
|
|
|
public Func<TInput, IWorkflowContext, ValueTask> Handler
|
|
{
|
|
get;
|
|
set;
|
|
} = (message, context) => default;
|
|
}
|
|
|
|
public class TypedHandlerWithOutput<TInput, TResult>() : BaseTestExecutor<TypedHandlerWithOutput<TInput, TResult>>(nameof(TypedHandlerWithOutput<TInput, TResult>)), IMessageHandler<TInput, TResult>
|
|
{
|
|
public ValueTask<TResult> HandleAsync(TInput message, IWorkflowContext context)
|
|
{
|
|
this.OnInvokedHandler();
|
|
return this.Handler(message, context);
|
|
}
|
|
public Func<TInput, IWorkflowContext, ValueTask<TResult>> Handler
|
|
{
|
|
get;
|
|
set;
|
|
} = (message, context) => default;
|
|
}
|
|
|
|
public class RoutingReflectionTests
|
|
{
|
|
private static async ValueTask<CallResult?> RunTestReflectAndRouteMessageAsync<TInput, TE>(BaseTestExecutor<TE> executor, TInput? input = default) where TInput : new() where TE : ReflectingExecutor<TE>
|
|
{
|
|
MessageRouter router = executor.Router;
|
|
|
|
Assert.NotNull(router);
|
|
input ??= new();
|
|
Assert.True(router.CanHandle(input.GetType()));
|
|
Assert.True(router.CanHandle(input));
|
|
|
|
CallResult? result = await router.RouteMessageAsync(input, Mock.Of<IWorkflowContext>());
|
|
|
|
Assert.True(executor.InvokedHandler);
|
|
|
|
return result;
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Test_ReflectAndExecute_DefaultHandlerAsync()
|
|
{
|
|
DefaultHandler executor = new();
|
|
|
|
CallResult? result = await RunTestReflectAndRouteMessageAsync<object, DefaultHandler>(executor);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.True(result.IsSuccess);
|
|
Assert.True(result.IsVoid);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Test_ReflectAndExecute_HandlerReturnsVoidAsync()
|
|
{
|
|
TypedHandler<int> executor = new();
|
|
|
|
CallResult? result = await RunTestReflectAndRouteMessageAsync<object, TypedHandler<int>>(executor, 3);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.True(result.IsSuccess);
|
|
Assert.True(result.IsVoid);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Test_ReflectAndExecute_HandlerReturnsValueAsync()
|
|
{
|
|
TypedHandlerWithOutput<int, string> executor = new()
|
|
{
|
|
Handler = (message, context) => new ValueTask<string>($"{message}")
|
|
};
|
|
|
|
const string Expected = "3";
|
|
CallResult? result = await RunTestReflectAndRouteMessageAsync<object, TypedHandlerWithOutput<int, string>>(executor, int.Parse(Expected));
|
|
|
|
Assert.NotNull(result);
|
|
Assert.True(result.IsSuccess);
|
|
Assert.False(result.IsVoid);
|
|
|
|
Assert.Equal(Expected, result.Result);
|
|
}
|
|
}
|