diff --git a/dotnet/samples/Hello/HelloAgent/HelloAgent.csproj b/dotnet/samples/Hello/HelloAgent/HelloAgent.csproj index f2f3e473fef..88893ccc874 100644 --- a/dotnet/samples/Hello/HelloAgent/HelloAgent.csproj +++ b/dotnet/samples/Hello/HelloAgent/HelloAgent.csproj @@ -1,4 +1,4 @@ - + Exe net8.0 @@ -8,7 +8,6 @@ - diff --git a/dotnet/src/Microsoft.AutoGen/Agents/App.cs b/dotnet/src/Microsoft.AutoGen/Agents/App.cs index fc36d336779..8a233bcd489 100644 --- a/dotnet/src/Microsoft.AutoGen/Agents/App.cs +++ b/dotnet/src/Microsoft.AutoGen/Agents/App.cs @@ -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 StartAsync(WebApplicationBuilder? builder = null, AgentTypes? agentTypes = null, bool local = false) { @@ -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"); diff --git a/dotnet/src/Microsoft.AutoGen/Agents/Services/AgentWorkerHostingExtensions.cs b/dotnet/src/Microsoft.AutoGen/Agents/Services/AgentWorkerHostingExtensions.cs index 8215f203276..3736fc76cb6 100644 --- a/dotnet/src/Microsoft.AutoGen/Agents/Services/AgentWorkerHostingExtensions.cs +++ b/dotnet/src/Microsoft.AutoGen/Agents/Services/AgentWorkerHostingExtensions.cs @@ -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; @@ -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); @@ -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(); } diff --git a/dotnet/src/Microsoft.AutoGen/Agents/Services/HostBuilderExtensions.cs b/dotnet/src/Microsoft.AutoGen/Agents/Services/HostBuilderExtensions.cs index 8ded5cb03d2..f020f0bb667 100644 --- a/dotnet/src/Microsoft.AutoGen/Agents/Services/HostBuilderExtensions.cs +++ b/dotnet/src/Microsoft.AutoGen/Agents/Services/HostBuilderExtensions.cs @@ -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); @@ -98,7 +114,9 @@ public static AgentApplicationBuilder AddAgentWorker(this IHostApplicationBuilde return new EventTypes(typeRegistry, types, eventsMap); }); builder.Services.AddSingleton(); - return new AgentApplicationBuilder(builder); + builder.Services.AddSingleton(new AgentApplicationBuilder(builder)); + + return builder; } private static MessageDescriptor? GetMessageDescriptor(Type type) diff --git a/dotnet/src/Microsoft.AutoGen/Agents/Services/Orleans/OrleansRuntimeHostingExtenions.cs b/dotnet/src/Microsoft.AutoGen/Agents/Services/Orleans/OrleansRuntimeHostingExtenions.cs index cd59bcefc38..374e49f7a50 100644 --- a/dotnet/src/Microsoft.AutoGen/Agents/Services/Orleans/OrleansRuntimeHostingExtenions.cs +++ b/dotnet/src/Microsoft.AutoGen/Agents/Services/Orleans/OrleansRuntimeHostingExtenions.cs @@ -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(); + // 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) @@ -51,16 +57,16 @@ public static WebApplicationBuilder AddOrleans(this WebApplicationBuilder builde options.SystemResponseTimeout = TimeSpan.FromMinutes(3); }); siloBuilder.Configure(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 => { @@ -84,7 +90,7 @@ public static WebApplicationBuilder AddOrleans(this WebApplicationBuilder builde .AddMemoryGrainStorage("PubSubStore"); } }); - builder.Services.AddSingleton(); + return builder; } } diff --git a/dotnet/test/Microsoft.AutoGen.Agents.Tests/AgentBaseTests.cs b/dotnet/test/Microsoft.AutoGen.Agents.Tests/AgentBaseTests.cs index b10f82e7d43..e58fdb00f0a 100644 --- a/dotnet/test/Microsoft.AutoGen.Agents.Tests/AgentBaseTests.cs +++ b/dotnet/test/Microsoft.AutoGen.Agents.Tests/AgentBaseTests.cs @@ -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() { @@ -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(); + + 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(); + } + /// /// The test agent is a simple agent that is used for testing purposes. /// - public class TestAgent : AgentBase, IHandle, IHandle + public class TestAgent : AgentBase, IHandle, IHandle, IHandle { - public TestAgent(IAgentRuntime context, EventTypes eventTypes, Logger logger) : base(context, eventTypes, logger) + public TestAgent( + IAgentRuntime context, + [FromKeyedServices("EventTypes")] EventTypes eventTypes, + Logger? logger = null) : base(context, eventTypes, logger) { } @@ -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 ReceivedItems { get; private set; } = []; + + /// + /// Key: source + /// Value: message + /// + public static ConcurrentDictionary 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(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 +{ + public const string Name = nameof(ClusterFixtureCollection); +} diff --git a/dotnet/test/Microsoft.AutoGen.Agents.Tests/Microsoft.AutoGen.Agents.Tests.csproj b/dotnet/test/Microsoft.AutoGen.Agents.Tests/Microsoft.AutoGen.Agents.Tests.csproj index db7467bf123..4abf1dc834d 100644 --- a/dotnet/test/Microsoft.AutoGen.Agents.Tests/Microsoft.AutoGen.Agents.Tests.csproj +++ b/dotnet/test/Microsoft.AutoGen.Agents.Tests/Microsoft.AutoGen.Agents.Tests.csproj @@ -9,6 +9,7 @@ + diff --git a/protos/agent_events.proto b/protos/agent_events.proto index 811c888c642..5fd88bf8c44 100644 --- a/protos/agent_events.proto +++ b/protos/agent_events.proto @@ -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; }