diff --git a/dotnet/samples/Hello-distributed/Backend/Agents/HelloAgent.cs b/dotnet/samples/Hello-distributed/Backend/Agents/HelloAgent.cs index 23d615ee32f..91facc8c94e 100644 --- a/dotnet/samples/Hello-distributed/Backend/Agents/HelloAgent.cs +++ b/dotnet/samples/Hello-distributed/Backend/Agents/HelloAgent.cs @@ -9,23 +9,21 @@ namespace Backend.Agents; [TopicSubscription("HelloAgents")] public class HelloAgent( IAgentRuntime context, - [FromKeyedServices("EventTypes")] EventTypes typeRegistry) : AgentBase( + [FromKeyedServices("EventTypes")] EventTypes typeRegistry, ILogger logger) : AgentBase( context, - typeRegistry), - IHandleConsole, + typeRegistry, logger), IHandle { public async Task Handle(NewGreetingRequested item) { + _logger.LogInformation($"HelloAgent with Id: {AgentId} received NewGreetingRequested with {item.Message}"); var response = await SayHello(item.Message).ConfigureAwait(false); - var evt = new Output { Message = response }; - await PublishMessageAsync(evt).ConfigureAwait(false); - var goodbye = new NewGreetingGenerated + var greeting = new NewGreetingGenerated { UserId = AgentId.Key, - UserMessage = "Goodbye" + UserMessage = response }; - await PublishMessageAsync(goodbye).ConfigureAwait(false); + await PublishMessageAsync(greeting).ConfigureAwait(false); } public async Task SayHello(string ask) diff --git a/dotnet/samples/Hello-distributed/Backend/Agents/OutputAgent.cs b/dotnet/samples/Hello-distributed/Backend/Agents/OutputAgent.cs index cf692c4f88c..1771793a58a 100644 --- a/dotnet/samples/Hello-distributed/Backend/Agents/OutputAgent.cs +++ b/dotnet/samples/Hello-distributed/Backend/Agents/OutputAgent.cs @@ -9,15 +9,13 @@ namespace Backend.Agents; [TopicSubscription("HelloAgents")] public class OutputAgent( IAgentRuntime context, - [FromKeyedServices("EventTypes")] EventTypes typeRegistry) : AgentBase( + [FromKeyedServices("EventTypes")] EventTypes typeRegistry, ILogger logger) : AgentBase( context, - typeRegistry), - IHandleConsole, + typeRegistry, logger), IHandle { public async Task Handle(NewGreetingGenerated item) { - // TODO: store to memory - + _logger.LogInformation($"OutputAgent with Id: {AgentId} received NewGreetingGenerated with {item.UserMessage}"); } } diff --git a/dotnet/samples/Hello-distributed/Backend/Program.cs b/dotnet/samples/Hello-distributed/Backend/Program.cs index db487fe80e5..f94daae5eeb 100644 --- a/dotnet/samples/Hello-distributed/Backend/Program.cs +++ b/dotnet/samples/Hello-distributed/Backend/Program.cs @@ -19,7 +19,8 @@ var agentHostUrl = builder.Configuration["AGENT_HOST"]!; builder.AddAgentWorker(agentHostUrl) - .AddAgent(nameof(HelloAgent)); + .AddAgent(nameof(HelloAgent)) + .AddAgent(nameof(OutputAgent)); builder.Services.AddSingleton(); @@ -27,7 +28,7 @@ app.MapDefaultEndpoints(); -app.MapPost("/sessions", async ([FromBody]string message, AgentWorker client) => +app.MapPost("/sessions", async ([FromBody]string message, Client client) => { var session = Guid.NewGuid().ToString(); await client.PublishEventAsync(new NewGreetingRequested { Message = message }.ToCloudEvent(session)); diff --git a/dotnet/src/Microsoft.AutoGen/Agents/AgentBase.cs b/dotnet/src/Microsoft.AutoGen/Agents/AgentBase.cs index 6fffdaadf1d..9f999918b4b 100644 --- a/dotnet/src/Microsoft.AutoGen/Agents/AgentBase.cs +++ b/dotnet/src/Microsoft.AutoGen/Agents/AgentBase.cs @@ -225,36 +225,34 @@ public Task CallHandler(CloudEvent item) { // Only send the event to the handler if the agent type is handling that type // foreach of the keys in the EventTypes.EventsMap[] if it contains the item.type - foreach (var key in EventTypes.EventsMap.Keys) + + foreach (var type in EventTypes.TypesMap[item.Type]) { - if (EventTypes.EventsMap[key].Contains(item.Type)) - { - var payload = item.ProtoData.Unpack(EventTypes.TypeRegistry); - var convertedPayload = Convert.ChangeType(payload, EventTypes.Types[item.Type]); - var genericInterfaceType = typeof(IHandle<>).MakeGenericType(EventTypes.Types[item.Type]); + var payload = item.ProtoData.Unpack(EventTypes.TypeRegistry); + var convertedPayload = Convert.ChangeType(payload, EventTypes.Types[item.Type]); + var genericInterfaceType = typeof(IHandle<>).MakeGenericType(EventTypes.Types[item.Type]); - MethodInfo methodInfo; - try + MethodInfo methodInfo; + try + { + // check that our target actually implements this interface, otherwise call the default static + if (genericInterfaceType.IsAssignableFrom(this.GetType())) { - // check that our target actually implements this interface, otherwise call the default static - if (genericInterfaceType.IsAssignableFrom(this.GetType())) - { - methodInfo = genericInterfaceType.GetMethod(nameof(IHandle.Handle), BindingFlags.Public | BindingFlags.Instance) - ?? throw new InvalidOperationException($"Method not found on type {genericInterfaceType.FullName}"); - return methodInfo.Invoke(this, [payload]) as Task ?? Task.CompletedTask; - } - else - { - // The error here is we have registered for an event that we do not have code to listen to - throw new InvalidOperationException($"No handler found for event '{item.Type}'; expecting IHandle<{item.Type}> implementation."); - } + methodInfo = genericInterfaceType.GetMethod(nameof(IHandle.Handle), BindingFlags.Public | BindingFlags.Instance) + ?? throw new InvalidOperationException($"Method not found on type {genericInterfaceType.FullName}"); + return methodInfo.Invoke(this, [payload]) as Task ?? Task.CompletedTask; } - catch (Exception ex) + else { - _logger.LogError(ex, $"Error invoking method {nameof(IHandle.Handle)}"); - throw; // TODO: ? + // The error here is we have registered for an event that we do not have code to listen to + throw new InvalidOperationException($"No handler found for event '{item.Type}'; expecting IHandle<{item.Type}> implementation."); } } + catch (Exception ex) + { + _logger.LogError(ex, $"Error invoking method {nameof(IHandle.Handle)}"); + throw; // TODO: ? + } } return Task.CompletedTask; diff --git a/dotnet/src/Microsoft.AutoGen/Agents/Services/HostBuilderExtensions.cs b/dotnet/src/Microsoft.AutoGen/Agents/Services/HostBuilderExtensions.cs index 8ded5cb03d2..c3f8a1e38bf 100644 --- a/dotnet/src/Microsoft.AutoGen/Agents/Services/HostBuilderExtensions.cs +++ b/dotnet/src/Microsoft.AutoGen/Agents/Services/HostBuilderExtensions.cs @@ -41,14 +41,22 @@ public static AgentApplicationBuilder AddAgentWorker(this IHostApplicationBuilde var descriptors = pairs.Select(t => t.Item2); var typeRegistry = TypeRegistry.FromMessages(descriptors); var types = pairs.ToDictionary(item => item.Item2?.FullName ?? "", item => item.t); - - var eventsMap = AppDomain.CurrentDomain.GetAssemblies() + var typesForEvents = new Dictionary>(); + var eventsForType = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(assembly => assembly.GetTypes()) .Where(type => ReflectionHelper.IsSubclassOfGeneric(type, typeof(AgentBase)) && !type.IsAbstract) - .Select(t => (t, t.GetInterfaces() - .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IHandle<>)) - .Select(i => (GetMessageDescriptor(i.GetGenericArguments().First())?.FullName ?? "")).ToHashSet())) - .ToDictionary(item => item.t, item => item.Item2); + .Select(t => + { + var events = t.GetInterfaces() + .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IHandle<>)) + .Select(i => (GetMessageDescriptor(i.GetGenericArguments().First())?.FullName ?? "")).ToHashSet(); + foreach(var evt in events) { + if (!typesForEvents.TryGetValue(evt, out var value)) { value = new HashSet(); typesForEvents[evt] = value; } + value.Add(t); + } + return (t, events); + }).ToDictionary(item => item.t, item => item.Item2); + // if the assembly contains any interfaces of type IHandler, then add all the methods of the interface to the eventsMap var handlersMap = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(assembly => assembly.GetTypes()) @@ -72,13 +80,13 @@ public static AgentApplicationBuilder AddAgentWorker(this IHostApplicationBuilde { foreach (var iface in item) { - if (eventsMap.TryGetValue(iface.Item2, out var events)) + if (eventsForType.TryGetValue(iface.Item2, out var events)) { events.UnionWith(iface.Item3); } else { - eventsMap[iface.Item2] = iface.Item3; + eventsForType[iface.Item2] = iface.Item3; } } } @@ -86,16 +94,16 @@ public static AgentApplicationBuilder AddAgentWorker(this IHostApplicationBuilde // merge the handlersMap into the eventsMap foreach (var item in handlersMap) { - if (eventsMap.TryGetValue(item.Key, out var events)) + if (eventsForType.TryGetValue(item.Key, out var events)) { events.UnionWith(item.Value); } else { - eventsMap[item.Key] = item.Value; + eventsForType[item.Key] = item.Value; } } - return new EventTypes(typeRegistry, types, eventsMap); + return new EventTypes(typeRegistry, types, eventsForType, typesForEvents); }); builder.Services.AddSingleton(); return new AgentApplicationBuilder(builder); @@ -141,11 +149,12 @@ public sealed class AgentTypes(Dictionary types) return new AgentTypes(agents); } } -public sealed class EventTypes(TypeRegistry typeRegistry, Dictionary types, Dictionary> eventsMap) +public sealed class EventTypes(TypeRegistry typeRegistry, Dictionary types, Dictionary> eventsMap, Dictionary> typesMap) { public TypeRegistry TypeRegistry { get; } = typeRegistry; public Dictionary Types { get; } = types; public Dictionary> EventsMap { get; } = eventsMap; + public Dictionary> TypesMap { get; } = typesMap; } public sealed class AgentApplicationBuilder(IHostApplicationBuilder builder) diff --git a/dotnet/test/Microsoft.AutoGen.Agents.Tests/AgentBaseTests.cs b/dotnet/test/Microsoft.AutoGen.Agents.Tests/AgentBaseTests.cs index b10f82e7d43..0def7029f00 100644 --- a/dotnet/test/Microsoft.AutoGen.Agents.Tests/AgentBaseTests.cs +++ b/dotnet/test/Microsoft.AutoGen.Agents.Tests/AgentBaseTests.cs @@ -16,7 +16,7 @@ public class AgentBaseTests public async Task ItInvokeRightHandlerTestAsync() { var mockContext = new Mock(); - var agent = new TestAgent(mockContext.Object, new EventTypes(TypeRegistry.Empty, [], []), new Logger(new LoggerFactory())); + var agent = new TestAgent(mockContext.Object, new EventTypes(TypeRegistry.Empty, [], [], []), new Logger(new LoggerFactory())); await agent.HandleObject("hello world"); await agent.HandleObject(42);