diff --git a/dotnet/AutoGen.sln b/dotnet/AutoGen.sln index 83147d38dc..4b8ce8ff01 100644 --- a/dotnet/AutoGen.sln +++ b/dotnet/AutoGen.sln @@ -125,7 +125,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AIModelClientHostingExtensi EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample", "sample", "{686480D7-8FEC-4ED3-9C5D-CEBE1057A7ED}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HelloAgentState", "samples\Hello\HelloAgentState\HelloAgentState.csproj", "{64EF61E7-00A6-4E5E-9808-62E10993A0E5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HelloAgentState", "samples\Hello\HelloAgentState\HelloAgentState.csproj", "{64EF61E7-00A6-4E5E-9808-62E10993A0E5}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/dotnet/samples/Hello/HelloAgent/Program.cs b/dotnet/samples/Hello/HelloAgent/Program.cs index d378a5b4f7..bccc66dfb9 100644 --- a/dotnet/samples/Hello/HelloAgent/Program.cs +++ b/dotnet/samples/Hello/HelloAgent/Program.cs @@ -18,10 +18,11 @@ namespace Hello [TopicSubscription("HelloAgents")] public class HelloAgent( IAgentContext context, - [FromKeyedServices("EventTypes")] EventTypes typeRegistry) : ConsoleAgent( + [FromKeyedServices("EventTypes")] EventTypes typeRegistry) : AgentBase( context, typeRegistry), ISayHello, + IHandleConsole, IHandle, IHandle { diff --git a/dotnet/src/Microsoft.AutoGen/Agents/AgentBase.cs b/dotnet/src/Microsoft.AutoGen/Agents/AgentBase.cs index d4a6ca0f4e..cfe2b40913 100644 --- a/dotnet/src/Microsoft.AutoGen/Agents/AgentBase.cs +++ b/dotnet/src/Microsoft.AutoGen/Agents/AgentBase.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.Reflection; using System.Text; using System.Text.Json; using System.Threading.Channels; @@ -17,6 +18,8 @@ public abstract class AgentBase : IAgentBase private readonly Channel _mailbox = Channel.CreateUnbounded(); private readonly IAgentContext _context; + public string Route { get; set; } = "base"; + protected internal ILogger Logger => _context.Logger; public IAgentContext Context => _context; protected readonly EventTypes EventTypes; @@ -212,14 +215,39 @@ public abstract class AgentBase : IAgentBase public Task CallHandler(CloudEvent item) { // Only send the event to the handler if the agent type is handling that type - if (EventTypes.EventsMap[GetType()].Contains(item.Type)) + // foreach of the keys in the EventTypes.EventsMap[] if it contains the item.type + foreach (var key in EventTypes.EventsMap.Keys) { - 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 methodInfo = genericInterfaceType.GetMethod(nameof(IHandle.Handle)) ?? throw new InvalidOperationException($"Method not found on type {genericInterfaceType.FullName}"); - return methodInfo.Invoke(this, [payload]) as Task ?? Task.CompletedTask; + 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]); + + MethodInfo methodInfo; + try + { + // 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."); + } + } + 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/Agents/IOAgent/ConsoleAgent/IHandleConsole.cs b/dotnet/src/Microsoft.AutoGen/Agents/Agents/IOAgent/ConsoleAgent/IHandleConsole.cs new file mode 100644 index 0000000000..739dce1251 --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/Agents/Agents/IOAgent/ConsoleAgent/IHandleConsole.cs @@ -0,0 +1,47 @@ +using Microsoft.AutoGen.Abstractions; + +namespace Microsoft.AutoGen.Agents +{ + public interface IHandleConsole : IHandle, IHandle + { + string Route { get; } + AgentId AgentId { get; } + ValueTask PublishEvent(CloudEvent item); + + async Task IHandle.Handle(Output item) + { + // Assuming item has a property `Message` that we want to write to the console + Console.WriteLine(item.Message); + await ProcessOutput(item.Message); + + var evt = new OutputWritten + { + Route = "console" + }.ToCloudEvent(AgentId.Key); + await PublishEvent(evt); + } + async Task IHandle.Handle(Input item) + { + Console.WriteLine("Please enter input:"); + string content = Console.ReadLine() ?? string.Empty; + + await ProcessInput(content); + + var evt = new InputProcessed + { + Route = "console" + }.ToCloudEvent(AgentId.Key); + await PublishEvent(evt); + } + static Task ProcessOutput(string message) + { + // Implement your output processing logic here + return Task.CompletedTask; + } + static Task ProcessInput(string message) + { + // Implement your input processing logic here + return Task.FromResult(message); + } + } +} diff --git a/dotnet/src/Microsoft.AutoGen/Agents/GrpcAgentWorkerRuntime.cs b/dotnet/src/Microsoft.AutoGen/Agents/GrpcAgentWorkerRuntime.cs index b7168b9cb1..3a8355166d 100644 --- a/dotnet/src/Microsoft.AutoGen/Agents/GrpcAgentWorkerRuntime.cs +++ b/dotnet/src/Microsoft.AutoGen/Agents/GrpcAgentWorkerRuntime.cs @@ -83,6 +83,7 @@ public sealed class GrpcAgentWorkerRuntime : IHostedService, IDisposable, IAgent message.Response.RequestId = request.OriginalRequestId; request.Agent.ReceiveMessage(message); break; + case Message.MessageOneofCase.RegisterAgentTypeResponse: if (!message.RegisterAgentTypeResponse.Success) { diff --git a/dotnet/src/Microsoft.AutoGen/Agents/HostBuilderExtensions.cs b/dotnet/src/Microsoft.AutoGen/Agents/HostBuilderExtensions.cs index f13756f6dd..74d19042e9 100644 --- a/dotnet/src/Microsoft.AutoGen/Agents/HostBuilderExtensions.cs +++ b/dotnet/src/Microsoft.AutoGen/Agents/HostBuilderExtensions.cs @@ -71,7 +71,52 @@ public static class HostBuilderExtensions .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IHandle<>)) .Select(i => (GetMessageDescriptor(i.GetGenericArguments().First())?.FullName ?? "")).ToHashSet())) .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()) + .Where(type => ReflectionHelper.IsSubclassOfGeneric(type, typeof(AgentBase)) && !type.IsAbstract) + .Select(t => (t, t.GetMethods() + .Where(m => m.Name == "Handle") + .Select(m => (GetMessageDescriptor(m.GetParameters().First().ParameterType)?.FullName ?? "")).ToHashSet())) + .ToDictionary(item => item.t, item => item.Item2); + // get interfaces implemented by the agent and get the methods of the interface if they are named Handle + var ifaceHandlersMap = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(assembly => assembly.GetTypes()) + .Where(type => ReflectionHelper.IsSubclassOfGeneric(type, typeof(AgentBase)) && !type.IsAbstract) + .Select(t => t.GetInterfaces() + .Select(i => (t, i, i.GetMethods() + .Where(m => m.Name == "Handle") + .Select(m => (GetMessageDescriptor(m.GetParameters().First().ParameterType)?.FullName ?? "")) + //to dictionary of type t and paramter type of the method + .ToDictionary(m => m, m => m).Keys.ToHashSet())).ToList()); + // for each item in ifaceHandlersMap, add the handlers to eventsMap with item as the key + foreach (var item in ifaceHandlersMap) + { + foreach (var iface in item) + { + if (eventsMap.TryGetValue(iface.Item2, out var events)) + { + events.UnionWith(iface.Item3); + } + else + { + eventsMap[iface.Item2] = iface.Item3; + } + } + } + // merge the handlersMap into the eventsMap + foreach (var item in handlersMap) + { + if (eventsMap.TryGetValue(item.Key, out var events)) + { + events.UnionWith(item.Value); + } + else + { + eventsMap[item.Key] = item.Value; + } + } return new EventTypes(typeRegistry, types, eventsMap); }); return new AgentApplicationBuilder(builder);