mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Port Agent Runtime abstractions / inprocess runtime (#149)
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
# Suppressing errors for Test projects under dotnet/tests folder
|
||||
[*.cs]
|
||||
dotnet_diagnostic.CA1707.severity = none # Underscores in method names
|
||||
dotnet_diagnostic.CA2007.severity = none # Do not directly await a Task
|
||||
dotnet_diagnostic.CS1591.severity = none # Missing XML comment for publicly visible type or member
|
||||
dotnet_diagnostic.IDE1006.severity = warning # Naming rule violations
|
||||
|
||||
@@ -30,7 +30,7 @@ public abstract class ChatClientAgentRunStreamingTests<TAgentFixture>(Func<TAgen
|
||||
var chatResponses = await agent.RunStreamingAsync(thread).ToListAsync();
|
||||
|
||||
// Assert
|
||||
var chatResponseText = string.Join("", chatResponses.Select(x => x.Text));
|
||||
var chatResponseText = string.Concat(chatResponses.Select(x => x.Text));
|
||||
Assert.Contains("Computer says no", chatResponseText, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ public abstract class ChatClientAgentRunStreamingTests<TAgentFixture>(Func<TAgen
|
||||
thread).ToListAsync();
|
||||
|
||||
// Assert
|
||||
var chatResponseText = string.Join("", chatResponses.Select(x => x.Text));
|
||||
var chatResponseText = string.Concat(chatResponses.Select(x => x.Text));
|
||||
Assert.Contains(questionAndAnswer.ExpectedAnswer, chatResponseText, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ public abstract class RunStreamingTests<TAgentFixture>(Func<TAgentFixture> creat
|
||||
var chatResponses = await agent.RunStreamingAsync("What is the capital of France.", thread).ToListAsync();
|
||||
|
||||
// Assert
|
||||
var chatResponseText = string.Join("", chatResponses.Select(x => x.Text));
|
||||
var chatResponseText = string.Concat(chatResponses.Select(x => x.Text));
|
||||
Assert.Contains("Paris", chatResponseText);
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ public abstract class RunStreamingTests<TAgentFixture>(Func<TAgentFixture> creat
|
||||
var chatResponses = await agent.RunStreamingAsync(new ChatMessage(ChatRole.User, "What is the capital of France."), thread).ToListAsync();
|
||||
|
||||
// Assert
|
||||
var chatResponseText = string.Join("", chatResponses.Select(x => x.Text));
|
||||
var chatResponseText = string.Concat(chatResponses.Select(x => x.Text));
|
||||
Assert.Contains("Paris", chatResponseText);
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ public abstract class RunStreamingTests<TAgentFixture>(Func<TAgentFixture> creat
|
||||
thread).ToListAsync();
|
||||
|
||||
// Assert
|
||||
var chatResponseText = string.Join("", chatResponses.Select(x => x.Text));
|
||||
var chatResponseText = string.Concat(chatResponses.Select(x => x.Text));
|
||||
Assert.Contains("Paris", chatResponseText);
|
||||
}
|
||||
|
||||
@@ -84,8 +84,8 @@ public abstract class RunStreamingTests<TAgentFixture>(Func<TAgentFixture> creat
|
||||
var chatResponses2 = await agent.RunStreamingAsync(q2, thread).ToListAsync();
|
||||
|
||||
// Assert
|
||||
var chatResponse1Text = string.Join("", chatResponses1.Select(x => x.Text));
|
||||
var chatResponse2Text = string.Join("", chatResponses2.Select(x => x.Text));
|
||||
var chatResponse1Text = string.Concat(chatResponses1.Select(x => x.Text));
|
||||
var chatResponse2Text = string.Concat(chatResponses2.Select(x => x.Text));
|
||||
Assert.Contains("Paris", chatResponse1Text);
|
||||
Assert.Contains("Vienna", chatResponse2Text);
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<IsAotCompatible>false</IsAotCompatible>
|
||||
<ProjectsTargetFrameworks>net472;net9.0</ProjectsTargetFrameworks>
|
||||
<ProjectsDebugTargetFrameworks>net9.0</ProjectsDebugTargetFrameworks>
|
||||
<UserSecretsId>b7762d10-e29b-4bb1-8b74-b6d69a667dd4</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Agents.Orchestration.Concurrent;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
|
||||
using Microsoft.Extensions.AI.Agents.Runtime.InProcess;
|
||||
|
||||
namespace Microsoft.Agents.Orchestration.UnitTest;
|
||||
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Agents.Orchestration.GroupChat;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
|
||||
using Microsoft.Extensions.AI.Agents.Runtime.InProcess;
|
||||
|
||||
namespace Microsoft.Agents.Orchestration.UnitTest;
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ using System.Threading.Tasks;
|
||||
using Microsoft.Agents.Orchestration.Handoff;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
|
||||
using Microsoft.Extensions.AI.Agents.Runtime.InProcess;
|
||||
using OpenAI;
|
||||
|
||||
namespace Microsoft.Agents.Orchestration.UnitTest;
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.AI.Agents.Runtime;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.SemanticKernel.Agents.Runtime;
|
||||
|
||||
namespace Microsoft.Agents.Orchestration.UnitTest;
|
||||
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Agents.Orchestration.Sequential;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
|
||||
using Microsoft.Extensions.AI.Agents.Runtime.InProcess;
|
||||
|
||||
namespace Microsoft.Agents.Orchestration.UnitTest;
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ public class AgentTests
|
||||
private readonly Mock<Agent> _agentMock;
|
||||
private readonly Mock<AgentThread> _agentThreadMock;
|
||||
private readonly ChatResponse _invokeResponse = new();
|
||||
private readonly List<ChatResponseUpdate> _invokeStreamingResponses = new();
|
||||
private readonly List<ChatResponseUpdate> _invokeStreamingResponses = [];
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AgentTests"/> class.
|
||||
@@ -255,9 +255,7 @@ public class AgentTests
|
||||
/// <summary>
|
||||
/// Typed mock thread.
|
||||
/// </summary>
|
||||
public abstract class TestAgentThread : AgentThread
|
||||
{
|
||||
}
|
||||
public abstract class TestAgentThread : AgentThread;
|
||||
|
||||
/// <summary>
|
||||
/// Mock class to test the <see cref="Agent.ValidateOrCreateThreadType{TThreadType}"/> method.
|
||||
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Extensions.AI.Agents.Runtime.Abstractions.Tests;
|
||||
|
||||
public class AgentIdTests()
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
[InlineData("invalid\u007Fkey")] // DEL character (127) is outside ASCII 32-126 range
|
||||
[InlineData("invalid\u0000key")] // NULL character is outside ASCII 32-126 range
|
||||
[InlineData("invalid\u0010key")] // Control character is outside ASCII 32-126 range
|
||||
[InlineData("InvalidKey💀")] // Control character is outside ASCII 32-126 range
|
||||
public void AgentIdShouldThrowArgumentExceptionWithInvalidKey(string? invalidKey)
|
||||
{
|
||||
// Act & Assert
|
||||
ArgumentException exception = Assert.Throws<ArgumentException>(() => new AgentId("validType", invalidKey!));
|
||||
Assert.Contains("Invalid AgentId key", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AgentIdShouldInitializeCorrectlyTest()
|
||||
{
|
||||
AgentId agentId = new("TestType", "TestKey");
|
||||
|
||||
Assert.Equal("TestType", agentId.Type);
|
||||
Assert.Equal("TestKey", agentId.Key);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AgentIdShouldConvertFromTupleTest()
|
||||
{
|
||||
(string, string) agentTuple = ("TupleType", "TupleKey");
|
||||
AgentId agentId = new(agentTuple);
|
||||
|
||||
Assert.Equal("TupleType", agentId.Type);
|
||||
Assert.Equal("TupleKey", agentId.Key);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AgentIdShouldConvertFromAgentType()
|
||||
{
|
||||
AgentType agentType = "TestType";
|
||||
AgentId agentId = new(agentType, "TestKey");
|
||||
|
||||
Assert.Equal("TestType", agentId.Type);
|
||||
Assert.Equal("TestKey", agentId.Key);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AgentIdShouldParseFromStringTest()
|
||||
{
|
||||
AgentId agentId = AgentId.FromStr("ParsedType/ParsedKey");
|
||||
|
||||
Assert.Equal("ParsedType", agentId.Type);
|
||||
Assert.Equal("ParsedKey", agentId.Key);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AgentIdShouldCompareEqualityCorrectlyTest()
|
||||
{
|
||||
AgentId agentId1 = new("SameType", "SameKey");
|
||||
AgentId agentId2 = new("SameType", "SameKey");
|
||||
AgentId agentId3 = new("DifferentType", "DifferentKey");
|
||||
|
||||
Assert.Equal(agentId2, agentId1);
|
||||
Assert.NotEqual(agentId3, agentId1);
|
||||
Assert.True(agentId1 == agentId2);
|
||||
Assert.True(agentId1 != agentId3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AgentIdShouldGenerateCorrectHashCodeTest()
|
||||
{
|
||||
AgentId agentId1 = new("HashType", "HashKey");
|
||||
AgentId agentId2 = new("HashType", "HashKey");
|
||||
AgentId agentId3 = new("DifferentType", "DifferentKey");
|
||||
|
||||
Assert.Equal(agentId2.GetHashCode(), agentId1.GetHashCode());
|
||||
Assert.NotEqual(agentId3.GetHashCode(), agentId1.GetHashCode());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AgentIdShouldConvertExplicitlyFromStringTest()
|
||||
{
|
||||
AgentId agentId = (AgentId)"ConvertedType/ConvertedKey";
|
||||
|
||||
Assert.Equal("ConvertedType", agentId.Type);
|
||||
Assert.Equal("ConvertedKey", agentId.Key);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AgentIdShouldReturnCorrectToStringTest()
|
||||
{
|
||||
AgentId agentId = new("ToStringType", "ToStringKey");
|
||||
|
||||
Assert.Equal("ToStringType/ToStringKey", agentId.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AgentIdShouldCompareInequalityForWrongTypeTest()
|
||||
{
|
||||
AgentId agentId1 = new("Type1", "Key1");
|
||||
|
||||
Assert.False(agentId1.Equals(Guid.NewGuid()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AgentIdShouldCompareInequalityCorrectlyTest()
|
||||
{
|
||||
AgentId agentId1 = new("Type1", "Key1");
|
||||
AgentId agentId2 = new("Type2", "Key2");
|
||||
|
||||
Assert.True(agentId1 != agentId2);
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Microsoft.Extensions.AI.Agents.Runtime.Abstractions.Tests;
|
||||
|
||||
public class AgentMetadataTests()
|
||||
{
|
||||
[Fact]
|
||||
public void AgentMetadataShouldInitializeCorrectlyTest()
|
||||
{
|
||||
// Arrange & Act
|
||||
AgentMetadata metadata = new("TestType", "TestKey", "TestDescription");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("TestType", metadata.Type);
|
||||
Assert.Equal("TestKey", metadata.Key);
|
||||
Assert.Equal("TestDescription", metadata.Description);
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Moq;
|
||||
|
||||
namespace Microsoft.Extensions.AI.Agents.Runtime.Abstractions.Tests;
|
||||
|
||||
public class AgentProxyTests
|
||||
{
|
||||
private readonly Mock<IAgentRuntime> _mockRuntime;
|
||||
private readonly AgentId _agentId;
|
||||
private readonly AgentProxy _agentProxy;
|
||||
|
||||
public AgentProxyTests()
|
||||
{
|
||||
this._mockRuntime = new Mock<IAgentRuntime>();
|
||||
this._agentId = new AgentId("testType", "testKey");
|
||||
this._agentProxy = new AgentProxy(this._agentId, this._mockRuntime.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IdMatchesAgentIdTest()
|
||||
{
|
||||
// Assert
|
||||
Assert.Equal(this._agentId, this._agentProxy.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MetadataShouldMatchAgentTest()
|
||||
{
|
||||
AgentMetadata expectedMetadata = new("testType", "testKey", "testDescription");
|
||||
this._mockRuntime.Setup(r => r.GetAgentMetadataAsync(this._agentId))
|
||||
.ReturnsAsync(expectedMetadata);
|
||||
|
||||
Assert.Equal(expectedMetadata, this._agentProxy.Metadata);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendMessageResponseTestAsync()
|
||||
{
|
||||
// Arrange
|
||||
object message = new { Content = "Hello" };
|
||||
AgentId sender = new("senderType", "senderKey");
|
||||
object response = new { Content = "Response" };
|
||||
|
||||
this._mockRuntime.Setup(r => r.SendMessageAsync(message, this._agentId, sender, null, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(response);
|
||||
|
||||
// Act
|
||||
object? result = await this._agentProxy.SendMessageAsync(message, sender);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(response, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LoadStateTestAsync()
|
||||
{
|
||||
// Arrange
|
||||
JsonElement state = JsonDocument.Parse("{\"key\":\"value\"}").RootElement;
|
||||
|
||||
this._mockRuntime.Setup(r => r.LoadAgentStateAsync(this._agentId, state))
|
||||
.Returns(default(ValueTask));
|
||||
|
||||
// Act
|
||||
await this._agentProxy.LoadStateAsync(state);
|
||||
|
||||
// Assert
|
||||
this._mockRuntime.Verify(r => r.LoadAgentStateAsync(this._agentId, state), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveStateTestAsync()
|
||||
{
|
||||
// Arrange
|
||||
JsonElement expectedState = JsonDocument.Parse("{\"key\":\"value\"}").RootElement;
|
||||
|
||||
this._mockRuntime.Setup(r => r.SaveAgentStateAsync(this._agentId))
|
||||
.ReturnsAsync(expectedState);
|
||||
|
||||
// Act
|
||||
JsonElement result = await this._agentProxy.SaveStateAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedState, result);
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Extensions.AI.Agents.Runtime.Abstractions.Tests;
|
||||
|
||||
public class AgentTypeTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
[InlineData("invalid type")] // Agent type must only contain alphanumeric letters or underscores
|
||||
[InlineData("123invalidType")] // Agent type cannot start with a number
|
||||
[InlineData("invalid@type")] // Agent type must only contain alphanumeric letters or underscores
|
||||
[InlineData("invalid-type")] // Agent type cannot alphanumeric underscores.
|
||||
public void AgentIdShouldThrowArgumentExceptionWithInvalidType(string? invalidType)
|
||||
{
|
||||
// Act & Assert
|
||||
ArgumentException exception = Assert.Throws<ArgumentException>(() => new AgentType(invalidType!));
|
||||
Assert.Contains("Invalid AgentId type", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ImplicitConversionFromStringTest()
|
||||
{
|
||||
// Arrange
|
||||
string agentTypeName = "TestAgent";
|
||||
|
||||
// Act
|
||||
AgentType agentType = agentTypeName;
|
||||
|
||||
// Assert
|
||||
Assert.Equal(agentTypeName, agentType.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ImplicitConversionToStringTest()
|
||||
{
|
||||
// Arrange
|
||||
AgentType agentType = "TestAgent";
|
||||
|
||||
// Act
|
||||
string agentTypeName = agentType;
|
||||
|
||||
// Assert
|
||||
Assert.Equal("TestAgent", agentTypeName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExplicitConversionFromTypeTest()
|
||||
{
|
||||
// Arrange
|
||||
Type type = typeof(string);
|
||||
|
||||
// Act
|
||||
AgentType agentType = (AgentType)type;
|
||||
|
||||
// Assert
|
||||
Assert.Equal(type.Name, agentType.Name);
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Microsoft.Extensions.AI.Agents.Runtime.Abstractions.Tests;
|
||||
|
||||
public class MessageContextTests
|
||||
{
|
||||
[Fact]
|
||||
public void ConstructWithMessageIdAndCancellationTokenTest()
|
||||
{
|
||||
// Arrange
|
||||
string messageId = Guid.NewGuid().ToString();
|
||||
CancellationToken cancellationToken = new();
|
||||
|
||||
// Act
|
||||
MessageContext messageContext = new(messageId, cancellationToken);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(messageId, messageContext.MessageId);
|
||||
Assert.Equal(cancellationToken, messageContext.CancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConstructWithCancellationTokenTest()
|
||||
{
|
||||
// Arrange
|
||||
CancellationToken cancellationToken = new();
|
||||
|
||||
// Act
|
||||
MessageContext messageContext = new(cancellationToken);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(messageContext.MessageId);
|
||||
Assert.Equal(cancellationToken, messageContext.CancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AssignSenderTest()
|
||||
{
|
||||
// Arrange
|
||||
MessageContext messageContext = new(new CancellationToken());
|
||||
AgentId sender = new("type", "key");
|
||||
|
||||
// Act
|
||||
messageContext.Sender = sender;
|
||||
|
||||
// Assert
|
||||
Assert.Equal(sender, messageContext.Sender);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AssignTopicTest()
|
||||
{
|
||||
// Arrange
|
||||
MessageContext messageContext = new(new CancellationToken());
|
||||
TopicId topic = new("type", "source");
|
||||
|
||||
// Act
|
||||
messageContext.Topic = topic;
|
||||
|
||||
// Assert
|
||||
Assert.Equal(topic, messageContext.Topic);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AssignIsRpcPropertyTest()
|
||||
{
|
||||
// Arrange
|
||||
MessageContext messageContext = new(new CancellationToken())
|
||||
{
|
||||
// Act
|
||||
IsRpc = true
|
||||
};
|
||||
|
||||
// Assert
|
||||
Assert.True(messageContext.IsRpc);
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>$(ProjectsTargetFrameworks)</TargetFrameworks>
|
||||
<TargetFrameworks Condition="'$(Configuration)' == 'Debug'">$(ProjectsDebugTargetFrameworks)</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.Extensions.AI.Agents.Runtime.Abstractions\Microsoft.Extensions.AI.Agents.Runtime.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Extensions.AI.Agents.Runtime.Abstractions.Tests;
|
||||
|
||||
public class TopicIdTests
|
||||
{
|
||||
[Fact]
|
||||
public void ConstrWithTypeOnlyTest()
|
||||
{
|
||||
// Arrange & Act
|
||||
TopicId topicId = new("testtype");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("testtype", topicId.Type);
|
||||
Assert.Equal(TopicId.DefaultSource, topicId.Source);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConstructWithTypeAndSourceTest()
|
||||
{
|
||||
// Arrange & Act
|
||||
TopicId topicId = new("testtype", "customsource");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("testtype", topicId.Type);
|
||||
Assert.Equal("customsource", topicId.Source);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConstructWithTupleTest()
|
||||
{
|
||||
// Arrange
|
||||
(string, string) tuple = ("testtype", "customsource");
|
||||
|
||||
// Act
|
||||
TopicId topicId = new(tuple);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("testtype", topicId.Type);
|
||||
Assert.Equal("customsource", topicId.Source);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConvertFromStringTest()
|
||||
{
|
||||
// Arrange
|
||||
const string TopicIdStr = "testtype/customsource";
|
||||
|
||||
// Act
|
||||
TopicId topicId = TopicId.FromStr(TopicIdStr);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("testtype", topicId.Type);
|
||||
Assert.Equal("customsource", topicId.Source);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("invalid-format")]
|
||||
[InlineData("too/many/parts")]
|
||||
[InlineData("")]
|
||||
public void InvalidFormatFromStringThrowsTest(string invalidInput)
|
||||
{
|
||||
// Act & Assert
|
||||
Assert.Throws<FormatException>(() => TopicId.FromStr(invalidInput));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToStringTest()
|
||||
{
|
||||
// Arrange
|
||||
TopicId topicId = new("testtype", "customsource");
|
||||
|
||||
// Act
|
||||
string result = topicId.ToString();
|
||||
|
||||
// Assert
|
||||
Assert.Equal("testtype/customsource", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualityTest()
|
||||
{
|
||||
// Arrange
|
||||
TopicId topicId1 = new("testtype", "customsource");
|
||||
TopicId topicId2 = new("testtype", "customsource");
|
||||
|
||||
// Act & Assert
|
||||
Assert.True(topicId1.Equals(topicId2));
|
||||
Assert.True(topicId1.Equals((object)topicId2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InequalityTest()
|
||||
{
|
||||
// Arrange
|
||||
TopicId topicId1 = new("testtype1", "source1");
|
||||
TopicId topicId2 = new("testtype2", "source2");
|
||||
TopicId topicId3 = new("testtype1", "source2");
|
||||
TopicId topicId4 = new("testtype2", "source1");
|
||||
|
||||
// Act & Assert
|
||||
Assert.False(topicId1.Equals(topicId2));
|
||||
Assert.False(topicId1.Equals(topicId3));
|
||||
Assert.False(topicId1.Equals(topicId4));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NullEqualityTest()
|
||||
{
|
||||
// Arrange
|
||||
TopicId topicId = new("testtype", "customsource");
|
||||
|
||||
// Act & Assert
|
||||
Assert.False(topicId.Equals(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DifferentTypeEqualityTest()
|
||||
{
|
||||
// Arrange
|
||||
TopicId topicId = new("testtype", "customsource");
|
||||
const string DifferentType = "not-a-topic-id";
|
||||
|
||||
// Act & Assert
|
||||
Assert.False(topicId.Equals(DifferentType));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetHashCodeTest()
|
||||
{
|
||||
// Arrange
|
||||
TopicId topicId1 = new("testtype", "customsource");
|
||||
TopicId topicId2 = new("testtype", "customsource");
|
||||
|
||||
// Act
|
||||
int hash1 = topicId1.GetHashCode();
|
||||
int hash2 = topicId2.GetHashCode();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(hash1, hash2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExplicitConversionTest()
|
||||
{
|
||||
// Arrange
|
||||
string topicIdStr = "testtype/customsource";
|
||||
|
||||
// Act
|
||||
TopicId topicId = (TopicId)topicIdStr;
|
||||
|
||||
// Assert
|
||||
Assert.Equal("testtype", topicId.Type);
|
||||
Assert.Equal("customsource", topicId.Source);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsWildcardMatchTest()
|
||||
{
|
||||
// Arrange
|
||||
TopicId topicId1 = new("testtype", "source1");
|
||||
TopicId topicId2 = new("testtype", "source2");
|
||||
|
||||
// Act & Assert
|
||||
Assert.True(topicId1.IsWildcardMatch(topicId2));
|
||||
Assert.True(topicId2.IsWildcardMatch(topicId1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsWildcardMismatchTest()
|
||||
{
|
||||
// Arrange
|
||||
TopicId topicId1 = new("testtype1", "source");
|
||||
TopicId topicId2 = new("testtype2", "source");
|
||||
|
||||
// Act & Assert
|
||||
Assert.False(topicId1.IsWildcardMatch(topicId2));
|
||||
Assert.False(topicId2.IsWildcardMatch(topicId1));
|
||||
}
|
||||
}
|
||||
+333
@@ -0,0 +1,333 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Extensions.AI.Agents.Runtime.InProcess.Tests;
|
||||
|
||||
public class InProcessRuntimeTests()
|
||||
{
|
||||
[Fact]
|
||||
public async Task RuntimeStatusLifecycleTestAsync()
|
||||
{
|
||||
// Arrange & Act
|
||||
await using InProcessRuntime runtime = new();
|
||||
|
||||
// Assert
|
||||
Assert.False(runtime.DeliverToSelf);
|
||||
Assert.Equal(0, runtime.messageQueueCount);
|
||||
|
||||
// Act
|
||||
await runtime.StopAsync(); // Already stopped
|
||||
await runtime.RunUntilIdleAsync(); // Never throws
|
||||
|
||||
await runtime.StartAsync();
|
||||
|
||||
// Assert
|
||||
// Invalid to start runtime that is already started
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => runtime.StartAsync());
|
||||
Assert.Equal(0, runtime.messageQueueCount);
|
||||
|
||||
// Act
|
||||
await runtime.StopAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, runtime.messageQueueCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SubscriptionRegistrationLifecycleTestAsync()
|
||||
{
|
||||
// Arrange
|
||||
await using InProcessRuntime runtime = new();
|
||||
TestSubscription subscription = new("TestTopic", "MyAgent");
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(async () => await runtime.RemoveSubscriptionAsync(subscription.Id));
|
||||
|
||||
// Arrange
|
||||
await runtime.AddSubscriptionAsync(subscription);
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(async () => await runtime.AddSubscriptionAsync(subscription));
|
||||
|
||||
// Act
|
||||
await runtime.RemoveSubscriptionAsync(subscription.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AgentRegistrationLifecycleTestAsync()
|
||||
{
|
||||
// Arrange
|
||||
const string AgentType = "MyAgent";
|
||||
const string AgentDescription = "A test agent";
|
||||
List<MockAgent> agents = [];
|
||||
await using InProcessRuntime runtime = new();
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(async () => await runtime.GetAgentAsync(AgentType, lazy: false));
|
||||
|
||||
// Arrange
|
||||
await runtime.RegisterAgentFactoryAsync(AgentType, factoryFunc);
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(async () => await runtime.RegisterAgentFactoryAsync(AgentType, factoryFunc));
|
||||
|
||||
// Act: Lookup by type
|
||||
AgentId agentId = await runtime.GetAgentAsync(AgentType, lazy: false);
|
||||
|
||||
// Assert
|
||||
Assert.Single(agents);
|
||||
Assert.Single(runtime.agentInstances);
|
||||
|
||||
// Act
|
||||
MockAgent agent = await runtime.TryGetUnderlyingAgentInstanceAsync<MockAgent>(agentId);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(agentId, agent.Id);
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(async () => await runtime.TryGetUnderlyingAgentInstanceAsync<WrongAgent>(agentId));
|
||||
|
||||
// Act: Lookup by ID
|
||||
AgentId sameId = await runtime.GetAgentAsync(agentId, lazy: false);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(agentId, sameId);
|
||||
|
||||
// Act: Lookup by Type
|
||||
sameId = await runtime.GetAgentAsync((AgentType)agent.Id.Type, lazy: false);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(agentId, sameId);
|
||||
|
||||
// Act: Lookup metadata
|
||||
AgentMetadata metadata = await runtime.GetAgentMetadataAsync(agentId);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(agentId.Type, metadata.Type);
|
||||
Assert.Equal(AgentDescription, metadata.Description);
|
||||
Assert.Equal(agentId.Key, metadata.Key);
|
||||
|
||||
// Act: Access proxy
|
||||
AgentProxy proxy = await runtime.TryGetAgentProxyAsync(agentId);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(agentId, proxy.Id);
|
||||
Assert.Equal(metadata.Type, proxy.Metadata.Type);
|
||||
Assert.Equal(metadata.Description, proxy.Metadata.Description);
|
||||
Assert.Equal(metadata.Key, proxy.Metadata.Key);
|
||||
|
||||
async ValueTask<MockAgent> factoryFunc(AgentId id, IAgentRuntime runtime)
|
||||
{
|
||||
MockAgent agent = new(id, runtime, AgentDescription);
|
||||
agents.Add(agent);
|
||||
return agent;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AgentStateLifecycleTestAsync()
|
||||
{
|
||||
// Arrange
|
||||
const string AgentType = "MyAgent";
|
||||
const string TestMessage = "test message";
|
||||
|
||||
await using InProcessRuntime firstRuntime = new();
|
||||
await firstRuntime.RegisterAgentFactoryAsync(AgentType, factoryFunc);
|
||||
|
||||
// Act
|
||||
AgentId agentId = await firstRuntime.GetAgentAsync(AgentType, lazy: false);
|
||||
|
||||
// Assert
|
||||
Assert.Single(firstRuntime.agentInstances);
|
||||
|
||||
// Arrange
|
||||
MockAgent agent = (MockAgent)firstRuntime.agentInstances[agentId];
|
||||
agent.ReceivedMessages.Add(TestMessage);
|
||||
|
||||
// Act
|
||||
JsonElement agentState = await firstRuntime.SaveAgentStateAsync(agentId);
|
||||
|
||||
// Arrange
|
||||
await using InProcessRuntime secondRuntime = new();
|
||||
await secondRuntime.RegisterAgentFactoryAsync(AgentType, factoryFunc);
|
||||
|
||||
// Act
|
||||
await secondRuntime.LoadAgentStateAsync(agentId, agentState);
|
||||
|
||||
// Assert
|
||||
Assert.Single(secondRuntime.agentInstances);
|
||||
MockAgent copy = (MockAgent)secondRuntime.agentInstances[agentId];
|
||||
Assert.Single(copy.ReceivedMessages);
|
||||
Assert.Equal(TestMessage, copy.ReceivedMessages.Single().ToString());
|
||||
|
||||
static async ValueTask<MockAgent> factoryFunc(AgentId id, IAgentRuntime runtime)
|
||||
{
|
||||
MockAgent agent = new(id, runtime, "A test agent");
|
||||
return agent;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RuntimeSendMessageTestAsync()
|
||||
{
|
||||
// Arrange
|
||||
await using InProcessRuntime runtime = new();
|
||||
MockAgent? agent = null;
|
||||
await runtime.RegisterAgentFactoryAsync("MyAgent", async (id, runtime) =>
|
||||
{
|
||||
agent = new MockAgent(id, runtime, "A test agent");
|
||||
return agent;
|
||||
});
|
||||
|
||||
// Act: Ensure the agent is actually created
|
||||
AgentId agentId = await runtime.GetAgentAsync("MyAgent", lazy: false);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(agent);
|
||||
Assert.Empty(agent.ReceivedMessages);
|
||||
|
||||
// Act: Send message
|
||||
await runtime.StartAsync();
|
||||
await runtime.SendMessageAsync("TestMessage", agent.Id);
|
||||
await runtime.RunUntilIdleAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, runtime.messageQueueCount);
|
||||
Assert.Single(agent.ReceivedMessages);
|
||||
}
|
||||
|
||||
// Agent will not deliver to self will success when runtime.DeliverToSelf is false (default)
|
||||
[Theory]
|
||||
[InlineData(false, 0)]
|
||||
[InlineData(true, 1)]
|
||||
public async Task RuntimeAgentPublishToSelfTestAsync(bool selfPublish, int receiveCount)
|
||||
{
|
||||
// Arrange
|
||||
await using InProcessRuntime runtime = new()
|
||||
{
|
||||
DeliverToSelf = selfPublish
|
||||
};
|
||||
|
||||
MockAgent? agent = null;
|
||||
await runtime.RegisterAgentFactoryAsync("MyAgent", async (id, runtime) =>
|
||||
{
|
||||
agent = new MockAgent(id, runtime, "A test agent");
|
||||
return agent;
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Empty(runtime.agentInstances);
|
||||
|
||||
// Act: Ensure the agent is actually created
|
||||
AgentId agentId = await runtime.GetAgentAsync("MyAgent", lazy: false);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(agent);
|
||||
Assert.Single(runtime.agentInstances);
|
||||
|
||||
const string TopicType = "TestTopic";
|
||||
|
||||
// Arrange
|
||||
await runtime.AddSubscriptionAsync(new TestSubscription(TopicType, agentId.Type));
|
||||
|
||||
// Act
|
||||
await runtime.StartAsync();
|
||||
await runtime.PublishMessageAsync("SelfMessage", new TopicId(TopicType), sender: agentId);
|
||||
await runtime.RunUntilIdleAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(receiveCount, agent.ReceivedMessages.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RuntimeShouldSaveLoadStateCorrectlyTestAsync()
|
||||
{
|
||||
// Arrange: Create a runtime and register an agent
|
||||
await using InProcessRuntime runtime = new();
|
||||
MockAgent? agent = null;
|
||||
await runtime.RegisterAgentFactoryAsync("MyAgent", async (id, runtime) =>
|
||||
{
|
||||
agent = new MockAgent(id, runtime, "test agent");
|
||||
return agent;
|
||||
});
|
||||
|
||||
// Get agent ID and instantiate agent by publishing
|
||||
AgentId agentId = await runtime.GetAgentAsync("MyAgent", lazy: false);
|
||||
const string TopicType = "TestTopic";
|
||||
await runtime.AddSubscriptionAsync(new TestSubscription(TopicType, agentId.Type));
|
||||
|
||||
await runtime.StartAsync();
|
||||
await runtime.PublishMessageAsync("test", new TopicId(TopicType));
|
||||
await runtime.RunUntilIdleAsync();
|
||||
|
||||
// Act: Save the state
|
||||
JsonElement savedState = await runtime.SaveStateAsync();
|
||||
|
||||
// Assert: Ensure the agent's state is stored as a valid JSON type
|
||||
Assert.NotNull(agent);
|
||||
Assert.True(savedState.TryGetProperty(agentId.ToString(), out JsonElement agentState));
|
||||
Assert.Equal(JsonValueKind.Array, agentState.ValueKind);
|
||||
Assert.Single(agent.ReceivedMessages);
|
||||
|
||||
// Arrange: Serialize and Deserialize the state to simulate persistence
|
||||
string json = JsonSerializer.Serialize(savedState);
|
||||
Assert.NotNull(json);
|
||||
Assert.NotEmpty(json);
|
||||
IDictionary<string, JsonElement> deserializedState = JsonSerializer.Deserialize<IDictionary<string, JsonElement>>(json)
|
||||
?? throw new InvalidOperationException("Deserialized state is unexpectedly null");
|
||||
Assert.True(deserializedState.ContainsKey(agentId.ToString()));
|
||||
|
||||
// Act: Start new runtime and restore the state
|
||||
agent = null;
|
||||
await using InProcessRuntime newRuntime = new();
|
||||
await newRuntime.StartAsync();
|
||||
await newRuntime.RegisterAgentFactoryAsync("MyAgent", async (id, runtime) =>
|
||||
{
|
||||
agent = new MockAgent(id, runtime, "another agent");
|
||||
return agent;
|
||||
});
|
||||
|
||||
// Assert: Show that no agent instances exist in the new runtime
|
||||
Assert.Empty(newRuntime.agentInstances);
|
||||
|
||||
// Act: Load the state into the new runtime and show that agent is now instantiated
|
||||
await newRuntime.LoadStateAsync(savedState);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(agent);
|
||||
Assert.Single(newRuntime.agentInstances);
|
||||
Assert.True(newRuntime.agentInstances.ContainsKey(agentId));
|
||||
Assert.Single(agent.ReceivedMessages);
|
||||
}
|
||||
|
||||
#pragma warning disable CA1812 // Avoid uninstantiated internal classes
|
||||
private sealed class WrongAgent : IHostableAgent
|
||||
#pragma warning restore CA1812
|
||||
{
|
||||
public AgentId Id => throw new NotImplementedException();
|
||||
|
||||
public AgentMetadata Metadata => throw new NotImplementedException();
|
||||
|
||||
public ValueTask CloseAsync() => default;
|
||||
|
||||
public ValueTask LoadStateAsync(JsonElement state)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public ValueTask<object?> OnMessageAsync(object message, MessageContext messageContext)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public ValueTask<JsonElement> SaveStateAsync()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Extensions.AI.Agents.Runtime.InProcess.Tests;
|
||||
|
||||
public class MessageEnvelopeTests
|
||||
{
|
||||
[Fact]
|
||||
public void ConstructAllParametersTest()
|
||||
{
|
||||
// Arrange
|
||||
object message = new { Content = "Test message" };
|
||||
const string MessageId = "testid";
|
||||
CancellationToken cancellation = new();
|
||||
|
||||
// Act
|
||||
MessageEnvelope envelope = new(message, MessageId, cancellation);
|
||||
|
||||
// Assert
|
||||
Assert.Same(message, envelope.Message);
|
||||
Assert.Equal(MessageId, envelope.MessageId);
|
||||
Assert.Equal(cancellation, envelope.Cancellation);
|
||||
Assert.Null(envelope.Sender);
|
||||
Assert.Null(envelope.Receiver);
|
||||
Assert.Null(envelope.Topic);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConstructOnlyRequiredParametersTest()
|
||||
{
|
||||
// Arrange & Act
|
||||
MessageEnvelope envelope = new("test");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(envelope.MessageId);
|
||||
Assert.NotEmpty(envelope.MessageId);
|
||||
// Verify it's a valid GUID
|
||||
Assert.True(Guid.TryParse(envelope.MessageId, out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithSenderTest()
|
||||
{
|
||||
// Arrange
|
||||
MessageEnvelope envelope = new("test");
|
||||
AgentId sender = new("testtype", "testkey");
|
||||
|
||||
// Act
|
||||
MessageEnvelope result = envelope.WithSender(sender);
|
||||
|
||||
// Assert
|
||||
Assert.Same(envelope, result);
|
||||
Assert.Equal(sender, envelope.Sender);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ForSendTestAsync()
|
||||
{
|
||||
// Arrange
|
||||
MessageEnvelope envelope = new("test");
|
||||
AgentId receiver = new("receivertype", "receiverkey");
|
||||
object expectedResult = new { Response = "Success" };
|
||||
|
||||
ValueTask<object?> servicer(MessageEnvelope env, CancellationToken ct) => new(expectedResult);
|
||||
|
||||
// Act
|
||||
MessageDelivery delivery = envelope.ForSend(receiver, servicer);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(delivery);
|
||||
Assert.Same(envelope, delivery.Message);
|
||||
Assert.Equal(receiver, envelope.Receiver);
|
||||
|
||||
// Invoke the servicer to verify result sink works
|
||||
await delivery.InvokeAsync(CancellationToken.None);
|
||||
Assert.True(delivery.ResultSink.Future.IsCompleted);
|
||||
object? result = await delivery.ResultSink.Future;
|
||||
Assert.Same(expectedResult, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ForPublishTest()
|
||||
{
|
||||
// Arrange
|
||||
MessageEnvelope envelope = new("test");
|
||||
TopicId topic = new("testtopic");
|
||||
|
||||
static ValueTask servicer(MessageEnvelope env, CancellationToken ct) => default;
|
||||
|
||||
// Act
|
||||
MessageDelivery delivery = envelope.ForPublish(topic, servicer);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(delivery);
|
||||
Assert.Same(envelope, delivery.Message);
|
||||
Assert.Equal(topic, envelope.Topic);
|
||||
}
|
||||
}
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Extensions.AI.Agents.Runtime.InProcess.Tests;
|
||||
|
||||
public sealed class BasicMessage
|
||||
{
|
||||
public string Content { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
#pragma warning disable RCS1194 // Implement exception constructors
|
||||
public sealed class TestException : Exception;
|
||||
#pragma warning restore RCS1194 // Implement exception constructors
|
||||
|
||||
public sealed class PublisherAgent : TestAgent, IHandle<BasicMessage>
|
||||
{
|
||||
private readonly IList<TopicId> _targetTopics;
|
||||
|
||||
public PublisherAgent(AgentId id, IAgentRuntime runtime, string description, IList<TopicId> targetTopics)
|
||||
: base(id, runtime, description)
|
||||
{
|
||||
this._targetTopics = targetTopics;
|
||||
}
|
||||
|
||||
public async ValueTask HandleAsync(BasicMessage item, MessageContext messageContext)
|
||||
{
|
||||
this.ReceivedMessages.Add(item);
|
||||
foreach (TopicId targetTopic in this._targetTopics)
|
||||
{
|
||||
await this.PublishMessageAsync(
|
||||
new BasicMessage { Content = $"@{targetTopic}: {item.Content}" },
|
||||
targetTopic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class SendOnAgent : TestAgent, IHandle<BasicMessage>
|
||||
{
|
||||
private readonly IList<Guid> _targetKeys;
|
||||
|
||||
public SendOnAgent(AgentId id, IAgentRuntime runtime, string description, IList<Guid> targetKeys)
|
||||
: base(id, runtime, description)
|
||||
{
|
||||
this._targetKeys = targetKeys;
|
||||
}
|
||||
|
||||
public async ValueTask HandleAsync(BasicMessage item, MessageContext messageContext)
|
||||
{
|
||||
foreach (Guid targetKey in this._targetKeys)
|
||||
{
|
||||
AgentId targetId = new(nameof(ReceiverAgent), targetKey.ToString());
|
||||
BasicMessage response = new() { Content = $"@{targetKey}: {item.Content}" };
|
||||
await this.SendMessageAsync(response, targetId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ReceiverAgent : TestAgent, IHandle<BasicMessage>
|
||||
{
|
||||
public List<BasicMessage> Messages { get; } = [];
|
||||
|
||||
public ReceiverAgent(AgentId id, IAgentRuntime runtime, string description)
|
||||
: base(id, runtime, description)
|
||||
{
|
||||
}
|
||||
|
||||
public ValueTask HandleAsync(BasicMessage item, MessageContext messageContext)
|
||||
{
|
||||
this.Messages.Add(item);
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ProcessorAgent : TestAgent, IHandle<BasicMessage, BasicMessage>
|
||||
{
|
||||
private Func<string, string> ProcessFunc { get; }
|
||||
|
||||
public ProcessorAgent(AgentId id, IAgentRuntime runtime, Func<string, string> processFunc, string description)
|
||||
: base(id, runtime, description)
|
||||
{
|
||||
this.ProcessFunc = processFunc;
|
||||
}
|
||||
|
||||
public ValueTask<BasicMessage> HandleAsync(BasicMessage item, MessageContext messageContext)
|
||||
{
|
||||
BasicMessage result = new() { Content = this.ProcessFunc.Invoke(((BasicMessage)item).Content) };
|
||||
|
||||
return new(result);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class CancelAgent : TestAgent, IHandle<BasicMessage>
|
||||
{
|
||||
public CancelAgent(AgentId id, IAgentRuntime runtime, string description)
|
||||
: base(id, runtime, description)
|
||||
{
|
||||
}
|
||||
|
||||
public ValueTask HandleAsync(BasicMessage item, MessageContext messageContext)
|
||||
{
|
||||
CancellationToken cancelledToken = new(canceled: true);
|
||||
cancelledToken.ThrowIfCancellationRequested();
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ErrorAgent : TestAgent, IHandle<BasicMessage>
|
||||
{
|
||||
public ErrorAgent(AgentId id, IAgentRuntime runtime, string description)
|
||||
: base(id, runtime, description)
|
||||
{
|
||||
}
|
||||
|
||||
public bool DidThrow { get; private set; }
|
||||
|
||||
public ValueTask HandleAsync(BasicMessage item, MessageContext messageContext)
|
||||
{
|
||||
this.DidThrow = true;
|
||||
|
||||
throw new TestException();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class MessagingTestFixture
|
||||
{
|
||||
private Dictionary<Type, object> AgentsTypeMap { get; } = [];
|
||||
public InProcessRuntime Runtime { get; } = new();
|
||||
|
||||
public ValueTask<AgentType> RegisterFactoryMapInstances<TAgent>(AgentType type, Func<AgentId, IAgentRuntime, ValueTask<TAgent>> factory)
|
||||
where TAgent : IHostableAgent
|
||||
{
|
||||
async ValueTask<TAgent> WrappedFactory(AgentId id, IAgentRuntime runtime)
|
||||
{
|
||||
TAgent agent = await factory(id, runtime);
|
||||
this.GetAgentInstances<TAgent>()[id] = agent;
|
||||
return agent;
|
||||
}
|
||||
|
||||
return this.Runtime.RegisterAgentFactoryAsync(type, WrappedFactory);
|
||||
}
|
||||
|
||||
public Dictionary<AgentId, TAgent> GetAgentInstances<TAgent>() where TAgent : IHostableAgent
|
||||
{
|
||||
if (!this.AgentsTypeMap.TryGetValue(typeof(TAgent), out object? maybeAgentMap) ||
|
||||
maybeAgentMap is not Dictionary<AgentId, TAgent> result)
|
||||
{
|
||||
this.AgentsTypeMap[typeof(TAgent)] = result = [];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
public async ValueTask RegisterReceiverAgentAsync(string? agentNameSuffix = null, params string[] topicTypes)
|
||||
{
|
||||
await this.RegisterFactoryMapInstances(
|
||||
$"{nameof(ReceiverAgent)}{agentNameSuffix ?? string.Empty}",
|
||||
(id, runtime) => new ValueTask<ReceiverAgent>(new ReceiverAgent(id, runtime, string.Empty)));
|
||||
|
||||
foreach (string topicType in topicTypes)
|
||||
{
|
||||
await this.Runtime.AddSubscriptionAsync(new TestSubscription(topicType, $"{nameof(ReceiverAgent)}{agentNameSuffix ?? string.Empty}"));
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask RegisterErrorAgentAsync(string? agentNameSuffix = null, params string[] topicTypes)
|
||||
{
|
||||
await this.RegisterFactoryMapInstances(
|
||||
$"{nameof(ErrorAgent)}{agentNameSuffix ?? string.Empty}",
|
||||
(id, runtime) => new ValueTask<ErrorAgent>(new ErrorAgent(id, runtime, string.Empty)));
|
||||
|
||||
foreach (string topicType in topicTypes)
|
||||
{
|
||||
await this.Runtime.AddSubscriptionAsync(new TestSubscription(topicType, $"{nameof(ErrorAgent)}{agentNameSuffix ?? string.Empty}"));
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask RunPublishTestAsync(TopicId sendTarget, object message, string? messageId = null)
|
||||
{
|
||||
messageId ??= Guid.NewGuid().ToString();
|
||||
|
||||
await this.Runtime.StartAsync();
|
||||
await this.Runtime.PublishMessageAsync(message, sendTarget, messageId: messageId);
|
||||
await this.Runtime.RunUntilIdleAsync();
|
||||
}
|
||||
|
||||
public async ValueTask<object?> RunSendTestAsync(AgentId sendTarget, object message, string? messageId = null)
|
||||
{
|
||||
messageId ??= Guid.NewGuid().ToString();
|
||||
|
||||
await this.Runtime.StartAsync();
|
||||
|
||||
object? result = await this.Runtime.SendMessageAsync(message, sendTarget, messageId: messageId);
|
||||
|
||||
await this.Runtime.RunUntilIdleAsync();
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>$(ProjectsTargetFrameworks)</TargetFrameworks>
|
||||
<TargetFrameworks Condition="'$(Configuration)' == 'Debug'">$(ProjectsDebugTargetFrameworks)</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.Extensions.AI.Agents.Runtime.InProcess\Microsoft.Extensions.AI.Agents.Runtime.InProcess.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Extensions.AI.Agents.Runtime.InProcess.Tests;
|
||||
|
||||
public class PublishMessageTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task Test_PublishMessage_SuccessAsync()
|
||||
{
|
||||
MessagingTestFixture fixture = new();
|
||||
|
||||
await fixture.RegisterReceiverAgentAsync(topicTypes: "TestTopic");
|
||||
await fixture.RegisterReceiverAgentAsync("2", topicTypes: "TestTopic");
|
||||
|
||||
await fixture.RunPublishTestAsync(new TopicId("TestTopic"), new BasicMessage { Content = "1" });
|
||||
|
||||
var values = fixture.GetAgentInstances<ReceiverAgent>().Values;
|
||||
Assert.Equal(2, values.Count);
|
||||
Assert.All(values, receiverAgent =>
|
||||
{
|
||||
Assert.NotNull(receiverAgent.Messages);
|
||||
Assert.Single(receiverAgent.Messages);
|
||||
Assert.Contains(receiverAgent.Messages, m => m.Content == "1");
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Test_PublishMessage_SingleFailureAsync()
|
||||
{
|
||||
MessagingTestFixture fixture = new();
|
||||
|
||||
await fixture.RegisterErrorAgentAsync(topicTypes: "TestTopic");
|
||||
|
||||
// Test that we wrap single errors appropriately
|
||||
var e = await Assert.ThrowsAsync<AggregateException>(async () => await fixture.RunPublishTestAsync(new TopicId("TestTopic"), new BasicMessage { Content = "1" }));
|
||||
Assert.IsType<TestException>(Assert.Single(e.InnerExceptions));
|
||||
|
||||
var values = fixture.GetAgentInstances<ReceiverAgent>().Values;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Test_PublishMessage_MultipleFailuresAsync()
|
||||
{
|
||||
MessagingTestFixture fixture = new();
|
||||
|
||||
await fixture.RegisterErrorAgentAsync(topicTypes: "TestTopic");
|
||||
await fixture.RegisterErrorAgentAsync("2", topicTypes: "TestTopic");
|
||||
|
||||
// What we are really testing here is that a single exception does not prevent sending to the remaining agents
|
||||
var e = await Assert.ThrowsAsync<AggregateException>(async () => await fixture.RunPublishTestAsync(new TopicId("TestTopic"), new BasicMessage { Content = "1" }));
|
||||
Assert.Equal(2, e.InnerExceptions.Count);
|
||||
Assert.All(e.InnerExceptions, innerException => Assert.IsType<TestException>(innerException));
|
||||
|
||||
var values = fixture.GetAgentInstances<ErrorAgent>().Values;
|
||||
Assert.Equal(2, values.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Test_PublishMessage_MixedSuccessFailureAsync()
|
||||
{
|
||||
MessagingTestFixture fixture = new();
|
||||
|
||||
await fixture.RegisterReceiverAgentAsync(topicTypes: "TestTopic");
|
||||
await fixture.RegisterReceiverAgentAsync("2", topicTypes: "TestTopic");
|
||||
|
||||
await fixture.RegisterErrorAgentAsync(topicTypes: "TestTopic");
|
||||
await fixture.RegisterErrorAgentAsync("2", topicTypes: "TestTopic");
|
||||
|
||||
// What we are really testing here is that raising exceptions does not prevent sending to the remaining agents
|
||||
var e = await Assert.ThrowsAsync<AggregateException>(async () => await fixture.RunPublishTestAsync(new TopicId("TestTopic"), new BasicMessage { Content = "1" }));
|
||||
Assert.Equal(2, e.InnerExceptions.Count);
|
||||
Assert.All(e.InnerExceptions, innerException => Assert.IsType<TestException>(innerException));
|
||||
|
||||
var agents = fixture.GetAgentInstances<ReceiverAgent>().Values;
|
||||
Assert.Equal(2, agents.Count);
|
||||
Assert.All(agents, receiverAgent =>
|
||||
{
|
||||
Assert.NotNull(receiverAgent.Messages);
|
||||
Assert.Single(receiverAgent.Messages);
|
||||
Assert.Contains(receiverAgent.Messages, m => m.Content == "1");
|
||||
});
|
||||
|
||||
var errors = fixture.GetAgentInstances<ErrorAgent>().Values;
|
||||
Assert.Equal(2, errors.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Test_PublishMessage_RecurrentPublishSucceedsAsync()
|
||||
{
|
||||
MessagingTestFixture fixture = new();
|
||||
|
||||
await fixture.RegisterFactoryMapInstances(
|
||||
nameof(PublisherAgent),
|
||||
(id, runtime) => new ValueTask<PublisherAgent>(new PublisherAgent(id, runtime, string.Empty, [new TopicId("TestTopic")])));
|
||||
|
||||
await fixture.Runtime.AddSubscriptionAsync(new TestSubscription("RunTest", nameof(PublisherAgent)));
|
||||
|
||||
await fixture.RegisterReceiverAgentAsync(topicTypes: "TestTopic");
|
||||
await fixture.RegisterReceiverAgentAsync("2", topicTypes: "TestTopic");
|
||||
|
||||
await fixture.RunPublishTestAsync(new TopicId("RunTest"), new BasicMessage { Content = "1" });
|
||||
|
||||
TopicId testTopicId = new("TestTopic");
|
||||
var values = fixture.GetAgentInstances<ReceiverAgent>().Values;
|
||||
Assert.Equal(2, values.Count);
|
||||
Assert.All(values, receiver =>
|
||||
{
|
||||
Assert.NotNull(receiver.Messages);
|
||||
Assert.Single(receiver.Messages);
|
||||
Assert.Contains(receiver.Messages, m => m.Content == $"@{testTopicId}: 1");
|
||||
});
|
||||
}
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks.Sources;
|
||||
|
||||
namespace Microsoft.Extensions.AI.Agents.Runtime.InProcess.Tests;
|
||||
|
||||
public class ResultSinkTests
|
||||
{
|
||||
[Fact]
|
||||
public void GetResultTest()
|
||||
{
|
||||
// Arrange
|
||||
ResultSink<int> sink = new();
|
||||
const int ExpectedResult = 42;
|
||||
|
||||
// Act
|
||||
sink.SetResult(ExpectedResult);
|
||||
int result = sink.GetResult(0);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(ExpectedResult, result);
|
||||
Assert.Equal(ValueTaskSourceStatus.Succeeded, sink.GetStatus(0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FutureResultTestAsync()
|
||||
{
|
||||
// Arrange
|
||||
ResultSink<string> sink = new();
|
||||
const string ExpectedResult = "test";
|
||||
|
||||
// Act
|
||||
sink.SetResult(ExpectedResult);
|
||||
string result = await sink.Future;
|
||||
|
||||
// Assert
|
||||
Assert.Equal(ExpectedResult, result);
|
||||
Assert.Equal(ValueTaskSourceStatus.Succeeded, sink.GetStatus(0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SetExceptionTestAsync()
|
||||
{
|
||||
// Arrange
|
||||
ResultSink<int> sink = new();
|
||||
InvalidOperationException expectedException = new("Test exception");
|
||||
|
||||
// Act
|
||||
sink.SetException(expectedException);
|
||||
|
||||
// Assert
|
||||
Exception exception = await Assert.ThrowsAsync<InvalidOperationException>(async () => await sink.Future);
|
||||
Assert.Equal(expectedException.Message, exception.Message);
|
||||
exception = Assert.Throws<InvalidOperationException>(() => sink.GetResult(0));
|
||||
Assert.Equal(expectedException.Message, exception.Message);
|
||||
Assert.Equal(ValueTaskSourceStatus.Faulted, sink.GetStatus(0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SetCancelledTestAsync()
|
||||
{
|
||||
// Arrange
|
||||
ResultSink<int> sink = new();
|
||||
|
||||
// Act
|
||||
sink.SetCancelled();
|
||||
|
||||
// Assert
|
||||
Assert.True(sink.IsCancelled);
|
||||
Assert.Throws<OperationCanceledException>(() => sink.GetResult(0));
|
||||
await Assert.ThrowsAsync<OperationCanceledException>(async () => await sink.Future);
|
||||
Assert.Equal(ValueTaskSourceStatus.Canceled, sink.GetStatus(0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnCompletedTest()
|
||||
{
|
||||
// Arrange
|
||||
ResultSink<int> sink = new();
|
||||
bool continuationCalled = false;
|
||||
const int ExpectedResult = 42;
|
||||
|
||||
// Register the continuation
|
||||
sink.OnCompleted(
|
||||
state => continuationCalled = true,
|
||||
state: null,
|
||||
token: 0,
|
||||
ValueTaskSourceOnCompletedFlags.None);
|
||||
|
||||
// Assert
|
||||
Assert.False(continuationCalled, "Continuation should have been called");
|
||||
|
||||
// Act
|
||||
sink.SetResult(ExpectedResult);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(ExpectedResult, sink.GetResult(0));
|
||||
Assert.Equal(ValueTaskSourceStatus.Succeeded, sink.GetStatus(0));
|
||||
Assert.True(continuationCalled, "Continuation should have been called");
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Extensions.AI.Agents.Runtime.InProcess.Tests;
|
||||
|
||||
public class SendMessageTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task Test_SendMessage_ReturnsValueAsync()
|
||||
{
|
||||
static string ProcessFunc(string s) => $"Processed({s})";
|
||||
|
||||
MessagingTestFixture fixture = new();
|
||||
|
||||
await fixture.RegisterFactoryMapInstances(nameof(ProcessorAgent),
|
||||
(id, runtime) => new ValueTask<ProcessorAgent>(new ProcessorAgent(id, runtime, ProcessFunc, string.Empty)));
|
||||
|
||||
AgentId targetAgent = new(nameof(ProcessorAgent), Guid.NewGuid().ToString());
|
||||
object? maybeResult = await fixture.RunSendTestAsync(targetAgent, new BasicMessage { Content = "1" });
|
||||
|
||||
Assert.Equal("Processed(1)", Assert.IsType<BasicMessage>(maybeResult).Content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Test_SendMessage_CancellationAsync()
|
||||
{
|
||||
MessagingTestFixture fixture = new();
|
||||
|
||||
await fixture.RegisterFactoryMapInstances(nameof(CancelAgent),
|
||||
(id, runtime) => new ValueTask<CancelAgent>(new CancelAgent(id, runtime, string.Empty)));
|
||||
|
||||
AgentId targetAgent = new(nameof(CancelAgent), Guid.NewGuid().ToString());
|
||||
|
||||
await Assert.ThrowsAsync<OperationCanceledException>(() => fixture.RunSendTestAsync(targetAgent, new BasicMessage { Content = "1" }).AsTask());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Test_SendMessage_ErrorAsync()
|
||||
{
|
||||
MessagingTestFixture fixture = new();
|
||||
|
||||
await fixture.RegisterFactoryMapInstances(nameof(ErrorAgent),
|
||||
(id, runtime) => new ValueTask<ErrorAgent>(new ErrorAgent(id, runtime, string.Empty)));
|
||||
|
||||
AgentId targetAgent = new(nameof(ErrorAgent), Guid.NewGuid().ToString());
|
||||
|
||||
await Assert.ThrowsAsync<TestException>(() => fixture.RunSendTestAsync(targetAgent, new BasicMessage { Content = "1" }).AsTask());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Test_SendMessage_FromSendMessageHandlerAsync()
|
||||
{
|
||||
Guid[] targetGuids = [Guid.NewGuid(), Guid.NewGuid()];
|
||||
|
||||
MessagingTestFixture fixture = new();
|
||||
|
||||
Dictionary<AgentId, SendOnAgent> sendAgents = fixture.GetAgentInstances<SendOnAgent>();
|
||||
Dictionary<AgentId, ReceiverAgent> receiverAgents = fixture.GetAgentInstances<ReceiverAgent>();
|
||||
|
||||
await fixture.RegisterFactoryMapInstances(nameof(SendOnAgent),
|
||||
(id, runtime) => new ValueTask<SendOnAgent>(new SendOnAgent(id, runtime, string.Empty, targetGuids)));
|
||||
|
||||
await fixture.RegisterFactoryMapInstances(nameof(ReceiverAgent),
|
||||
(id, runtime) => new ValueTask<ReceiverAgent>(new ReceiverAgent(id, runtime, string.Empty)));
|
||||
|
||||
AgentId targetAgent = new(nameof(SendOnAgent), Guid.NewGuid().ToString());
|
||||
BasicMessage input = new() { Content = "Hello" };
|
||||
Task testTask = fixture.RunSendTestAsync(targetAgent, input).AsTask();
|
||||
|
||||
// We do not actually expect to wait the timeout here, but it is still better than waiting the 10 min
|
||||
// timeout that the tests default to. A failure will fail regardless of what timeout value we set.
|
||||
TimeSpan timeout = Debugger.IsAttached ? TimeSpan.FromSeconds(120) : TimeSpan.FromSeconds(10);
|
||||
Task timeoutTask = Task.Delay(timeout);
|
||||
|
||||
Task completedTask = await Task.WhenAny([testTask, timeoutTask]);
|
||||
Assert.Same(testTask, completedTask);
|
||||
|
||||
// Check that each of the target agents received the message
|
||||
foreach (Guid targetKey in targetGuids)
|
||||
{
|
||||
AgentId targetId = new(nameof(ReceiverAgent), targetKey.ToString());
|
||||
Assert.Single(receiverAgents[targetId].Messages);
|
||||
Assert.Contains(receiverAgents[targetId].Messages, m => m.Content == $"@{targetKey}: {input.Content}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Extensions.AI.Agents.Runtime.InProcess.Tests;
|
||||
|
||||
public abstract class TestAgent : BaseAgent
|
||||
{
|
||||
internal List<object> ReceivedMessages = [];
|
||||
|
||||
protected TestAgent(AgentId id, IAgentRuntime runtime, string description)
|
||||
: base(id, runtime, description)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A test agent that captures the messages it receives and
|
||||
/// is able to save and load its state.
|
||||
/// </summary>
|
||||
public sealed class MockAgent : TestAgent, IHandle<string>
|
||||
{
|
||||
public MockAgent(AgentId id, IAgentRuntime runtime, string description)
|
||||
: base(id, runtime, description) { }
|
||||
|
||||
public ValueTask HandleAsync(string item, MessageContext messageContext)
|
||||
{
|
||||
this.ReceivedMessages.Add(item);
|
||||
return default;
|
||||
}
|
||||
|
||||
public override async ValueTask<JsonElement> SaveStateAsync()
|
||||
{
|
||||
JsonElement json = JsonSerializer.SerializeToElement(this.ReceivedMessages);
|
||||
return json;
|
||||
}
|
||||
|
||||
public override ValueTask LoadStateAsync(JsonElement state)
|
||||
{
|
||||
this.ReceivedMessages = JsonSerializer.Deserialize<List<object>>(state) ?? throw new InvalidOperationException("Failed to deserialize state");
|
||||
return default;
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Microsoft.Extensions.AI.Agents.Runtime.InProcess.Tests;
|
||||
|
||||
public class TestSubscription(string topicType, string agentType, string? id = null) : ISubscriptionDefinition
|
||||
{
|
||||
public string Id { get; } = id ?? Guid.NewGuid().ToString();
|
||||
|
||||
public string TopicType { get; } = topicType;
|
||||
|
||||
public AgentId MapToAgent(TopicId topic)
|
||||
{
|
||||
if (!this.Matches(topic))
|
||||
{
|
||||
throw new InvalidOperationException("TopicId does not match the subscription.");
|
||||
}
|
||||
|
||||
return new AgentId(agentType, topic.Source);
|
||||
}
|
||||
|
||||
public bool Equals(ISubscriptionDefinition? other) => this.Id == other?.Id;
|
||||
|
||||
public override bool Equals([NotNullWhen(true)] object? obj) => obj is TestSubscription other && other.Equals(this);
|
||||
|
||||
public override int GetHashCode() => this.Id.GetHashCode();
|
||||
|
||||
public bool Matches(TopicId topic)
|
||||
{
|
||||
return topic.Type == this.TopicType;
|
||||
}
|
||||
}
|
||||
@@ -35,16 +35,16 @@ public class OpenAIAssistantFixture : IChatClientAgentFixture
|
||||
throw new InvalidOperationException("The thread must be of type ChatClientAgentThread to retrieve chat history.");
|
||||
}
|
||||
|
||||
List<ChatMessage> messages = new();
|
||||
List<ChatMessage> messages = [];
|
||||
await foreach (var agentMessage in this._assistantClient!.GetMessagesAsync(chatClientThread.Id, new() { Order = MessageCollectionOrder.Ascending }))
|
||||
{
|
||||
messages.Add(new()
|
||||
{
|
||||
Role = agentMessage.Role == MessageRole.User ? ChatRole.User : ChatRole.Assistant,
|
||||
Contents = new List<AIContent>()
|
||||
{
|
||||
Contents =
|
||||
[
|
||||
new TextContent(agentMessage.Content[0].Text ?? string.Empty)
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user