Compare commits

...

3 Commits

Author SHA1 Message Date
XiaoYun Zhang c9d13586e0 reformat 2024-10-30 10:02:25 -07:00
Xiaoyun Zhang 5b0422bd07
Merge branch 'main' into u/fix#3996 2024-10-30 09:51:51 -07:00
Ryan Sweet 51cd5b8d1f
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
2024-10-30 09:51:01 -07:00
6 changed files with 129 additions and 8 deletions

View File

@ -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

View File

@ -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>
{

View File

@ -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)
{
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}");
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<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;
}

View File

@ -0,0 +1,46 @@
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);
}
}

View File

@ -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)
{

View File

@ -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);