Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[.NET] Add happy path test for in-memory agent && Simplify HelloAgent example && some clean-up in extension APIs #4227

Merged
merged 12 commits into from
Nov 18, 2024
Merged
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 =>
rysweet marked this conversation as resolved.
Show resolved Hide resolved
{
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
Loading