Skip to content

Commit

Permalink
[.NET] Add happy path test for in-memory agent && Simplify HelloAgent…
Browse files Browse the repository at this point in the history
… example && some clean-up in extension APIs (#4227)

* add happy path test

* remove unnecessary namespace

* fix build error

* Update AgentBaseTests.cs

* revert changes

---------
  • Loading branch information
LittleLittleCloud authored Nov 18, 2024
1 parent d23d172 commit ad271d9
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 41 deletions.
3 changes: 1 addition & 2 deletions dotnet/samples/Hello/HelloAgent/HelloAgent.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
Expand All @@ -8,7 +8,6 @@

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" />
<PackageReference Include="Aspire.Hosting.AppHost" />
</ItemGroup>

<ItemGroup>
Expand Down
3 changes: 2 additions & 1 deletion dotnet/src/Microsoft.AutoGen/Agents/App.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public static class AgentsApp
{
// need a variable to store the runtime instance
public static WebApplication? Host { get; private set; }

[MemberNotNull(nameof(Host))]
public static async ValueTask<WebApplication> StartAsync(WebApplicationBuilder? builder = null, AgentTypes? agentTypes = null, bool local = false)
{
Expand Down Expand Up @@ -58,7 +59,7 @@ public static async ValueTask ShutdownAsync()
await Host.StopAsync();
}

private static AgentApplicationBuilder AddAgents(this AgentApplicationBuilder builder, AgentTypes? agentTypes)
private static IHostApplicationBuilder AddAgents(this IHostApplicationBuilder builder, AgentTypes? agentTypes)
{
agentTypes ??= AgentTypes.GetAgentTypesFromAssembly()
?? throw new InvalidOperationException("No agent types found in the assembly");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@

using System.Diagnostics;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
Expand All @@ -13,25 +11,9 @@ namespace Microsoft.AutoGen.Agents;

public static class AgentWorkerHostingExtensions
{
public static WebApplicationBuilder AddAgentService(this WebApplicationBuilder builder, bool local = false, bool useGrpc = true)
public static IHostApplicationBuilder AddAgentService(this IHostApplicationBuilder builder, bool local = false, bool useGrpc = true)
{
if (local)
{
//TODO: make configuration more flexible
builder.WebHost.ConfigureKestrel(serverOptions =>
{
serverOptions.ListenLocalhost(5001, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http2;
listenOptions.UseHttps();
});
});
builder.AddOrleans(local);
}
else
{
builder.AddOrleans();
}
builder.AddOrleans(local);

builder.Services.TryAddSingleton(DistributedContextPropagator.Current);

Expand All @@ -45,10 +27,11 @@ public static WebApplicationBuilder AddAgentService(this WebApplicationBuilder b
return builder;
}

public static WebApplicationBuilder AddLocalAgentService(this WebApplicationBuilder builder, bool useGrpc = true)
public static IHostApplicationBuilder AddLocalAgentService(this IHostApplicationBuilder builder, bool useGrpc = true)
{
return builder.AddAgentService(local: true, useGrpc);
}

public static WebApplication MapAgentService(this WebApplication app, bool local = false, bool useGrpc = true)
{
if (useGrpc) { app.MapGrpcService<GrpcGatewayService>(); }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,23 @@ namespace Microsoft.AutoGen.Agents;
public static class HostBuilderExtensions
{
private const string _defaultAgentServiceAddress = "https://localhost:5001";
public static AgentApplicationBuilder AddAgentWorker(this IHostApplicationBuilder builder, string agentServiceAddress = _defaultAgentServiceAddress, bool local = false)

public static IHostApplicationBuilder AddAgent<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TAgent>(this IHostApplicationBuilder builder, string typeName) where TAgent : AgentBase
{
builder.Services.AddKeyedSingleton("AgentTypes", (sp, key) => Tuple.Create(typeName, typeof(TAgent)));

return builder;
}

public static IHostApplicationBuilder AddAgent(this IHostApplicationBuilder builder, string typeName, Type agentType)
{
builder.Services.AddKeyedSingleton("AgentTypes", (sp, key) => Tuple.Create(typeName, agentType));

return builder;
}

public static IHostApplicationBuilder AddAgentWorker(this IHostApplicationBuilder builder, string agentServiceAddress = _defaultAgentServiceAddress, bool local = false)
{
builder.Services.TryAddSingleton(DistributedContextPropagator.Current);

Expand Down Expand Up @@ -98,7 +114,9 @@ public static AgentApplicationBuilder AddAgentWorker(this IHostApplicationBuilde
return new EventTypes(typeRegistry, types, eventsMap);
});
builder.Services.AddSingleton<Client>();
return new AgentApplicationBuilder(builder);
builder.Services.AddSingleton(new AgentApplicationBuilder(builder));

return builder;
}

private static MessageDescriptor? GetMessageDescriptor(Type type)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,17 @@ public static class OrleansRuntimeHostingExtenions
{
public static WebApplicationBuilder AddOrleans(this WebApplicationBuilder builder, bool local = false)
{
return builder.AddOrleans(local);
}

public static IHostApplicationBuilder AddOrleans(this IHostApplicationBuilder builder, bool local = false)
{
builder.Services.AddSerializer(serializer => serializer.AddProtobufSerializer());
builder.Services.AddSingleton<IRegistryGrain, RegistryGrain>();

// Ensure Orleans is added before the hosted service to guarantee that it starts first.
//TODO: make all of this configurable
builder.Host.UseOrleans(siloBuilder =>
builder.UseOrleans((siloBuilder) =>
{
// Development mode or local mode uses in-memory storage and streams
if (builder.Environment.IsDevelopment() || local)
Expand Down Expand Up @@ -51,16 +57,16 @@ public static WebApplicationBuilder AddOrleans(this WebApplicationBuilder builde
options.SystemResponseTimeout = TimeSpan.FromMinutes(3);
});
siloBuilder.Configure<ClientMessagingOptions>(options =>
{
options.ResponseTimeout = TimeSpan.FromMinutes(3);
});
{
options.ResponseTimeout = TimeSpan.FromMinutes(3);
});
siloBuilder.UseCosmosClustering(o =>
{
o.ConfigureCosmosClient(cosmosDbconnectionString);
o.ContainerName = "AutoGen";
o.DatabaseName = "clustering";
o.IsResourceCreationEnabled = true;
});
{
o.ConfigureCosmosClient(cosmosDbconnectionString);
o.ContainerName = "AutoGen";
o.DatabaseName = "clustering";
o.IsResourceCreationEnabled = true;
});
siloBuilder.UseCosmosReminderService(o =>
{
Expand All @@ -84,7 +90,7 @@ public static WebApplicationBuilder AddOrleans(this WebApplicationBuilder builde
.AddMemoryGrainStorage("PubSubStore");
}
});
builder.Services.AddSingleton<IRegistryGrain, RegistryGrain>();

return builder;
}
}
80 changes: 77 additions & 3 deletions dotnet/test/Microsoft.AutoGen.Agents.Tests/AgentBaseTests.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// AgentBaseTests.cs

using System.Collections.Concurrent;
using FluentAssertions;
using Google.Protobuf.Reflection;
using Microsoft.AutoGen.Abstractions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;
using static Microsoft.AutoGen.Agents.Tests.AgentBaseTests;

namespace Microsoft.AutoGen.Agents.Tests;

public class AgentBaseTests
[Collection(ClusterFixtureCollection.Name)]
public class AgentBaseTests(InMemoryAgentRuntimeFixture fixture)
{
private readonly InMemoryAgentRuntimeFixture _fixture = fixture;

[Fact]
public async Task ItInvokeRightHandlerTestAsync()
{
Expand All @@ -26,12 +33,36 @@ public async Task ItInvokeRightHandlerTestAsync()
agent.ReceivedItems[1].Should().Be(42);
}

[Fact]
public async Task ItDelegateMessageToTestAgentAsync()
{
var client = _fixture.AppHost.Services.GetRequiredService<Client>();

await client.PublishMessageAsync(new TextMessage()
{
Source = nameof(ItDelegateMessageToTestAgentAsync),
TextMessage_ = "buffer"
}, token: CancellationToken.None);

// wait for 10 seconds
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
while (!TestAgent.ReceivedMessages.ContainsKey(nameof(ItDelegateMessageToTestAgentAsync)) && !cts.Token.IsCancellationRequested)
{
await Task.Delay(100);
}

TestAgent.ReceivedMessages[nameof(ItDelegateMessageToTestAgentAsync)].Should().NotBeNull();
}

/// <summary>
/// The test agent is a simple agent that is used for testing purposes.
/// </summary>
public class TestAgent : AgentBase, IHandle<string>, IHandle<int>
public class TestAgent : AgentBase, IHandle<string>, IHandle<int>, IHandle<TextMessage>
{
public TestAgent(IAgentRuntime context, EventTypes eventTypes, Logger<AgentBase> logger) : base(context, eventTypes, logger)
public TestAgent(
IAgentRuntime context,
[FromKeyedServices("EventTypes")] EventTypes eventTypes,
Logger<AgentBase>? logger = null) : base(context, eventTypes, logger)
{
}

Expand All @@ -47,6 +78,49 @@ public Task Handle(int item)
return Task.CompletedTask;
}

public Task Handle(TextMessage item)
{
ReceivedMessages[item.Source] = item.TextMessage_;
return Task.CompletedTask;
}

public List<object> ReceivedItems { get; private set; } = [];

/// <summary>
/// Key: source
/// Value: message
/// </summary>
public static ConcurrentDictionary<string, object> ReceivedMessages { get; private set; } = new();
}
}

public sealed class InMemoryAgentRuntimeFixture : IDisposable
{
public InMemoryAgentRuntimeFixture()
{
var builder = Microsoft.Extensions.Hosting.Host.CreateApplicationBuilder();

// step 1: create in-memory agent runtime
// step 2: register TestAgent to that agent runtime
builder
.AddAgentService(local: true, useGrpc: false)
.AddAgentWorker(local: true)
.AddAgent<TestAgent>(nameof(TestAgent));

AppHost = builder.Build();
AppHost.StartAsync().Wait();
}
public IHost AppHost { get; }

void IDisposable.Dispose()
{
AppHost.StopAsync().Wait();
AppHost.Dispose();
}
}

[CollectionDefinition(Name)]
public sealed class ClusterFixtureCollection : ICollectionFixture<InMemoryAgentRuntimeFixture>
{
public const string Name = nameof(ClusterFixtureCollection);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AutoGen\Agents\Microsoft.AutoGen.Agents.csproj" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
</ItemGroup>

</Project>
5 changes: 4 additions & 1 deletion protos/agent_events.proto
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ syntax = "proto3";
package agents;

option csharp_namespace = "Microsoft.AutoGen.Abstractions";

message TextMessage {
string textMessage = 1;
string source = 2;
}
message Input {
string message = 1;
}
Expand Down

0 comments on commit ad271d9

Please sign in to comment.