mirror of https://github.com/microsoft/autogen.git
interface inheritance examples (#3989)
changes to AgentBase and HostBuilderExtensions to enable leveraging handlers from composition (interfaces) vs inheritance... see HelloAgents sample for usage closes #3928 is related to #3925
This commit is contained in:
parent
4a49844996
commit
51cd5b8d1f
|
@ -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
|
||||
|
|
|
@ -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<NewMessageReceived>,
|
||||
IHandle<ConversationClosed>
|
||||
{
|
||||
|
|
|
@ -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<object> _mailbox = Channel.CreateUnbounded<object>();
|
||||
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)
|
||||
{
|
||||
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 methodInfo = genericInterfaceType.GetMethod(nameof(IHandle<object>.Handle)) ?? throw new InvalidOperationException($"Method not found on type {genericInterfaceType.FullName}");
|
||||
|
||||
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<object>.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<object>.Handle)}");
|
||||
throw; // TODO: ?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
using Microsoft.AutoGen.Abstractions;
|
||||
|
||||
namespace Microsoft.AutoGen.Agents
|
||||
{
|
||||
public interface IHandleConsole : IHandle<Output>, IHandle<Input>
|
||||
{
|
||||
string Route { get; }
|
||||
AgentId AgentId { get; }
|
||||
ValueTask PublishEvent(CloudEvent item);
|
||||
|
||||
async Task IHandle<Output>.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<Input>.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<string> ProcessInput(string message)
|
||||
{
|
||||
// Implement your input processing logic here
|
||||
return Task.FromResult(message);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue