mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
32711cfa95
* feat: Define Workflow and Executor APIs * feat: Define IExecutionContext and Events * feat: Simple Workflow Demos * refactor: Move Workflows classes to separate assembly * feat: Move FanOut/In to LowLevel API with new semantics * feat: Implement Local Execution * refactor: Assembly name .Workflow => .Workflows * feat: Enable Default Message Handling * also lifts Bind in MessageHandlerInfo to better be able to direclty invoke handlers (for AOT, later) * feat: Implement StreamingHandle APIs This allows the user to respond to WorkflowEvents with external messages, enabling HIL. * feat: Add checks for duplicate edges and chain cycles * feat: Add built-in WorkflowEvents * refactor: Pull classes into own files * refactor: Simplify Disposal pattern in Executor * refactor: Break EdgeRunner file into per-type files * refactor: Use Throw.IfNull() * refactor: Remove AddLoop() Per https://github.com/microsoft/agent-framework/pull/272#discussion_r2241739079 we decided this was not very useful. * refactor: Normalize use of ValueTask * fix: Build Break from removing .AddLoop * refactor: Explicit routing and RouteBuilder Split out reflection from MessageRouter implemention into build phase, enabling AOT compilation to drive RouteBuilding without reflection. * test: Add Reflection/Invocation tests * fix: Terminate on Completion event * refactor: Update public API surface * feat: Add support for external requests * feat: Support hosting AIAgent instances in Workflows * fix: Fix routing to go through Executor.ExecuteAsync * test: Update samples for "must SendMessage" semantics * Add invoking samples to unit tests to avoid future breaks * fix: ExternalRequest should block Workflow completion * feat: Normalize API surface against Python * Also adds xmldoc to all public APIs * refactor: Normalize UnitTest and Sample namespaces * fix: Formatting * refactor: Normalize project/folder names * feat: Remove DynamicCodeExecution from ValueTaskTypeErasure * fix: Fix ILTrim warnings * docs: Add missing docs and fix typos * feat: Hosted Agents should report Run events * fix: Fix type propagation for ILTrim changes * refactor: Simplify DynamicallyAccessedMembers annotations * sample: Use static-Type construction of InputPort * feat: Support non-Streaming Run Mode * test: Add test for non-streaming execution * refactor: Remove unused types * refactor: Simplify Event and EdgeData type hierarchies * feat: Add Switch (=Conditional Edge Group) control flow * feat: Make .NET AutoSend the MessageHandler result * feat: Implement State APIs * refactor: Simplify public namespaces and code organization * refactor: Reconcile .NET and Python names --------- Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>
120 lines
4.3 KiB
C#
120 lines
4.3 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
using System;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.Agents.Workflows.Execution;
|
|
|
|
namespace Microsoft.Agents.Workflows.UnitTests;
|
|
|
|
public class StateSmokeTest
|
|
{
|
|
[Fact]
|
|
public void Test_ScopeId_Equality()
|
|
{
|
|
// The rules of ScopeId are simple: Private executor scopes (executorId, scopeId=null) are only equal to
|
|
// themselves. Public ScopeIds are equal when their scopeNames are equal, regardless of executorId.
|
|
|
|
ScopeId privateScope1 = new("executor1", null);
|
|
ScopeId privateScope2 = new("executor2", null);
|
|
|
|
Assert.NotEqual(privateScope1, privateScope2);
|
|
Assert.Equal(privateScope1, new ScopeId("executor1", null));
|
|
|
|
ScopeId sharedScope1 = new("executor1", "sharedScope");
|
|
ScopeId sharedScope2 = new("executor2", "sharedScope");
|
|
|
|
Assert.Equal(sharedScope1, sharedScope2);
|
|
Assert.NotEqual(sharedScope1, new ScopeId("executor1", "differentScope"));
|
|
Assert.NotEqual(sharedScope1, privateScope1);
|
|
}
|
|
|
|
[Fact]
|
|
public void Test_UpdateKey_Equality()
|
|
{
|
|
// The rules of UpdateKey are different from ScopeId. In the case of "shared scope",
|
|
// two update keys with different ExecutorIds are not the same.
|
|
|
|
const string Key1 = "key1";
|
|
const string Key2 = "key2";
|
|
UpdateKey privateScope1Key = new("executor1", null, Key1);
|
|
UpdateKey privateScope1Key2 = new("executor1", null, Key2);
|
|
|
|
Assert.NotEqual(privateScope1Key, privateScope1Key2);
|
|
|
|
UpdateKey privateScope2Key = new("executor2", null, Key1);
|
|
|
|
Assert.NotEqual(privateScope1Key, privateScope2Key);
|
|
|
|
UpdateKey scope1Executor1Key = new("executor1", "sharedScope", Key1);
|
|
UpdateKey scope1Executor2Key = new("executor2", "sharedScope", Key1);
|
|
|
|
Assert.NotEqual(scope1Executor1Key, scope1Executor2Key);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Test_ReadQueueUpdateAsync()
|
|
{
|
|
ScopeId sharedScope1 = new("executor1", "sharedScope");
|
|
ScopeId sharedScope2 = new("executor2", "sharedScope");
|
|
|
|
StateManager manager = new();
|
|
|
|
// Default reads on "object" should be null
|
|
const string Key = "key1";
|
|
Assert.Null(await manager.ReadStateAsync<object>(sharedScope1, Key));
|
|
Assert.Null(await manager.ReadStateAsync<object>(sharedScope1, Key));
|
|
|
|
await manager.WriteStateAsync(sharedScope1, Key, new object());
|
|
|
|
// After writing, we should be able to read the value from the executor's scope
|
|
// but not the shared scope yet
|
|
Assert.NotNull(await manager.ReadStateAsync<object>(sharedScope1, Key));
|
|
Assert.Null(await manager.ReadStateAsync<object>(sharedScope2, Key));
|
|
|
|
// Writes to one key should not impact other keys
|
|
Assert.Null(await manager.ReadStateAsync<object>(sharedScope1, "key2"));
|
|
|
|
// Publish the write
|
|
await manager.PublishUpdatesAsync();
|
|
|
|
// Now all the executors should be able to see the new state
|
|
Assert.NotNull(await manager.ReadStateAsync<object>(sharedScope1, Key));
|
|
Assert.NotNull(await manager.ReadStateAsync<object>(sharedScope2, Key));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Test_ConflictingWritesRaiseExceptionAsync()
|
|
{
|
|
ScopeId sharedScope1 = new("executor1", "sharedScope");
|
|
ScopeId sharedScope2 = new("executor2", "sharedScope");
|
|
|
|
StateManager manager = new();
|
|
|
|
const string Key = "key1";
|
|
const string Value1 = "1";
|
|
const string Value2 = "2";
|
|
|
|
// Write values from both executors
|
|
await manager.WriteStateAsync(sharedScope1, Key, Value1);
|
|
await manager.WriteStateAsync(sharedScope2, Key, Value2);
|
|
|
|
// Check that reading each will result in the right value
|
|
Assert.Equal(Value1, await manager.ReadStateAsync<string>(sharedScope1, Key));
|
|
Assert.Equal(Value2, await manager.ReadStateAsync<string>(sharedScope2, Key));
|
|
|
|
// Try to publish the updates
|
|
try
|
|
{
|
|
await manager.PublishUpdatesAsync();
|
|
Assert.Fail("Expected InvalidOperationException due to conflicting writes.");
|
|
}
|
|
catch (InvalidOperationException)
|
|
{
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Assert.Fail($"Expected InvalidOperationException, but got {ex.GetType().Name}.");
|
|
}
|
|
}
|
|
}
|