Rysweet hello fix (#3683)

Fixing the HelloWorld sample and some refactoring of .NET code, adding App and Host classes in client and runtime.
This commit is contained in:
Ryan Sweet 2024-10-08 10:02:48 -07:00 committed by GitHub
parent 39aa073de2
commit e40056789a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 173 additions and 67 deletions

View File

@ -93,9 +93,6 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AgentChat", "AgentChat", "{C7A2D42D-9277-47AC-862B-D86DF9D6AD48}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dev-team", "dev-team", "{616F30DF-1F41-4047-BAA4-64BA03BF5AEA}"
ProjectSection(SolutionItems) = preProject
samples\dev-team\DevTeam.ServiceDefaults\DevTeam.ServiceDefaults.csproj = samples\dev-team\DevTeam.ServiceDefaults\DevTeam.ServiceDefaults.csproj
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevTeam.AgentHost", "samples\dev-team\DevTeam.AgentHost\DevTeam.AgentHost.csproj", "{7228A701-C79D-4E15-BF45-48D11F721A84}"
EndProject
@ -127,6 +124,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HelloAgents.Web", "samples\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hello", "samples\Hello\Hello.csproj", "{6C9135E6-9D15-4D86-B3F4-9666DB87060A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AutoGen.ServiceDefaults", "src\Microsoft.AutoGen.ServiceDefaults\Microsoft.AutoGen.ServiceDefaults.csproj", "{F70C6FD7-9615-4EDD-8D55-5460FCC5A46D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -337,6 +336,10 @@ Global
{6C9135E6-9D15-4D86-B3F4-9666DB87060A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6C9135E6-9D15-4D86-B3F4-9666DB87060A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6C9135E6-9D15-4D86-B3F4-9666DB87060A}.Release|Any CPU.Build.0 = Release|Any CPU
{F70C6FD7-9615-4EDD-8D55-5460FCC5A46D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F70C6FD7-9615-4EDD-8D55-5460FCC5A46D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F70C6FD7-9615-4EDD-8D55-5460FCC5A46D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F70C6FD7-9615-4EDD-8D55-5460FCC5A46D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -397,6 +400,7 @@ Global
{6B88F4B3-26AB-4034-B0AC-5BA6EEDEB8E5} = {F7AC0FF1-8500-49C6-8CB3-97C6D52C8BEF}
{8B56BE22-5CF4-44BB-AFA5-732FEA2AFF0B} = {F7AC0FF1-8500-49C6-8CB3-97C6D52C8BEF}
{6C9135E6-9D15-4D86-B3F4-9666DB87060A} = {FBFEAD1F-29EB-4D99-A672-0CD8473E10B9}
{F70C6FD7-9615-4EDD-8D55-5460FCC5A46D} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {93384647-528D-46C8-922C-8DB36A382F0B}

View File

@ -1,23 +1,15 @@
using Microsoft.AutoGen.Agents.Abstractions;
using Microsoft.AutoGen.Agents.Client;
using Microsoft.AutoGen.Agents.Runtime;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = Host.CreateApplicationBuilder(args);
builder.AddAgentService();
builder.UseOrleans(siloBuilder =>
{
siloBuilder.UseLocalhostClustering(); ;
});
builder.AddAgentWorker("https://localhost:5000");
var app = builder.Build();
await app.StartAsync();
app.Services.GetRequiredService<AgentWorkerRuntime>();
var evt = new NewMessageReceived
// send a message to the agent
var app = await App.PublishMessageAsync("HelloAgents", new NewMessageReceived
{
Message = "World"
}.ToCloudEvent("HelloAgents");
}, local: true);
await App.RuntimeApp!.WaitForShutdownAsync();
await app.WaitForShutdownAsync();
[TopicSubscription("HelloAgents")]
@ -32,27 +24,33 @@ public class HelloAgent(
{
public async Task Handle(NewMessageReceived item)
{
var response = await SayHello(item.Message);
var response = await SayHello(item.Message).ConfigureAwait(false);
var evt = new Output
{
Message = response
}.ToCloudEvent(this.AgentId.Key);
await PublishEvent(evt);
await PublishEvent(evt).ConfigureAwait(false);
var goodbye = new ConversationClosed
{
UserId = this.AgentId.Key,
UserMessage = "Goodbye"
}.ToCloudEvent(this.AgentId.Key);
await PublishEvent(goodbye).ConfigureAwait(false);
}
public async Task Handle(ConversationClosed item)
{
var goodbye = "Goodbye!";
var goodbye = $"********************* {item.UserId} said {item.UserMessage} ************************";
var evt = new Output
{
Message = goodbye
}.ToCloudEvent(this.AgentId.Key);
await PublishEvent(evt);
await PublishEvent(evt).ConfigureAwait(false);
await Task.Delay(60000);
await App.ShutdownAsync();
}
public async Task<string> SayHello(string ask)
{
var response = $"Hello {ask}";
var response = $"\n\n\n\n***************Hello {ask}**********************\n\n\n\n";
return response;
}
}

View File

@ -10,7 +10,7 @@
<ItemGroup>
<ProjectReference Include="../../../src/Microsoft.AutoGen.Agents.Runtime/Microsoft.AutoGen.Agents.Runtime.csproj" />
<ProjectReference Include="..\DevTeam.ServiceDefaults\DevTeam.ServiceDefaults.csproj" />
<ProjectReference Include="../../../src/Microsoft.AutoGen.ServiceDefaults/Microsoft.AutoGen.ServiceDefaults.csproj" />
</ItemGroup>
</Project>

View File

@ -10,7 +10,7 @@
<ProjectReference Include="../../../src/Microsoft.AutoGen.Agents.Client/Microsoft.AutoGen.Agents.Client.csproj" />
<ProjectReference Include="..\DevTeam.ServiceDefaults\DevTeam.ServiceDefaults.csproj" />
<ProjectReference Include="../../../src/Microsoft.AutoGen.ServiceDefaults/Microsoft.AutoGen.ServiceDefaults.csproj" />
<ProjectReference Include="..\DevTeam.Shared\DevTeam.Shared.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.AutoGen.Agents.Extensions\SemanticKernel\Microsoft.AutoGen.Agents.Extensions.SemanticKernel.csproj" />
</ItemGroup>

View File

@ -29,7 +29,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DevTeam.ServiceDefaults\DevTeam.ServiceDefaults.csproj" />
<ProjectReference Include="../../../src/Microsoft.AutoGen.ServiceDefaults/Microsoft.AutoGen.ServiceDefaults.csproj" />
<ProjectReference Include="..\DevTeam.Shared\DevTeam.Shared.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.AutoGen.Agents.Extensions\SemanticKernel\Microsoft.AutoGen.Agents.Extensions.SemanticKernel.csproj" />
</ItemGroup>

View File

@ -1,11 +1,11 @@
using System.Diagnostics;
using Google.Protobuf;
using Microsoft.AutoGen.Agents.Abstractions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Microsoft.AutoGen.Agents.Client;
// TODO: Extract this to be part of the Client
public sealed class AgentClient(ILogger<AgentClient> logger, AgentWorkerRuntime runtime, DistributedContextPropagator distributedContextPropagator,
[FromKeyedServices("EventTypes")] EventTypes eventTypes)
: AgentBase(new ClientContext(logger, runtime, distributedContextPropagator), eventTypes)
@ -13,6 +13,10 @@ public sealed class AgentClient(ILogger<AgentClient> logger, AgentWorkerRuntime
public async ValueTask PublishEventAsync(CloudEvent evt) => await PublishEvent(evt);
public async ValueTask<RpcResponse> SendRequestAsync(AgentId target, string method, Dictionary<string, string> parameters) => await RequestAsync(target, method, parameters);
public async ValueTask PublishEventAsync(string topic, IMessage evt)
{
await PublishEventAsync(evt.ToCloudEvent(topic)).ConfigureAwait(false);
}
private sealed class ClientContext(ILogger<AgentClient> logger, AgentWorkerRuntime runtime, DistributedContextPropagator distributedContextPropagator) : IAgentContext
{
public AgentId AgentId { get; } = new AgentId("client", Guid.NewGuid().ToString());

View File

@ -170,7 +170,7 @@ public sealed class AgentWorkerRuntime : IHostedService, IDisposable, IAgentWork
var events = agentType.GetInterfaces()
.Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IHandle<>))
.Select(i => i.GetGenericArguments().First().Name);
var state = agentType.BaseType?.GetGenericArguments().First();
//var state = agentType.BaseType?.GetGenericArguments().First();
var topicTypes = agentType.GetCustomAttributes<TopicSubscriptionAttribute>().Select(t => t.Topic);
await WriteChannelAsync(new Message

View File

@ -3,7 +3,7 @@ using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AutoGen.Agents.Client;
public class ConsoleAgent : IOAgent<AgentState>,
public abstract class ConsoleAgent : IOAgent<AgentState>,
IUseConsole,
IHandle<Input>,
IHandle<Output>
@ -32,6 +32,7 @@ public class ConsoleAgent : IOAgent<AgentState>,
{
// Assuming item has a property `Content` that we want to write to the console
Console.WriteLine(item.Message);
await ProcessOutput(item.Message);
var evt = new OutputWritten
{

View File

@ -1,27 +1,26 @@
using Microsoft.AutoGen.Agents.Abstractions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Microsoft.AutoGen.Agents.Client;
[TopicSubscription("FileIO")]
public class FileAgent : IOAgent<AgentState>,
public abstract class FileAgent(
IAgentContext context,
[FromKeyedServices("EventTypes")] EventTypes typeRegistry,
string inputPath = "input.txt",
string outputPath = "output.txt"
) : IOAgent<AgentState>(context, typeRegistry),
IUseFiles,
IHandle<Input>,
IHandle<Output>
{
public FileAgent(IAgentContext context, EventTypes typeRegistry, string filePath) : base(context, typeRegistry)
{
_filePath = filePath;
}
private readonly string _filePath;
public override async Task Handle(Input item)
{
// validate that the file exists
if (!File.Exists(_filePath))
if (!File.Exists(inputPath))
{
string errorMessage = $"File not found: {_filePath}";
var errorMessage = $"File not found: {inputPath}";
Logger.LogError(errorMessage);
//publish IOError event
var err = new IOError
@ -31,36 +30,30 @@ public class FileAgent : IOAgent<AgentState>,
await PublishEvent(err);
return;
}
string content;
using (var reader = new StreamReader(item.Message))
{
content = await reader.ReadToEndAsync();
}
await ProcessInput(content);
var evt = new InputProcessed
{
Route = _route
}.ToCloudEvent(this.AgentId.Key);
await PublishEvent(evt);
}
public override async Task Handle(Output item)
{
using (var writer = new StreamWriter(_filePath, append: true))
using (var writer = new StreamWriter(outputPath, append: true))
{
await writer.WriteLineAsync(item.Message);
}
var evt = new OutputWritten
{
Route = _route
}.ToCloudEvent(this.AgentId.Key);
await PublishEvent(evt);
}
public override async Task<string> ProcessInput(string message)
{
var evt = new InputProcessed
@ -70,14 +63,12 @@ public class FileAgent : IOAgent<AgentState>,
await PublishEvent(evt);
return message;
}
public override Task ProcessOutput(string message)
{
// Implement your output processing logic here
return Task.CompletedTask;
}
}
public interface IUseFiles
{
}

View File

@ -6,7 +6,7 @@ using Microsoft.Extensions.Logging;
namespace Microsoft.AutoGen.Agents.Client;
public class WebAPIAgent : IOAgent<AgentState>,
public abstract class WebAPIAgent : IOAgent<AgentState>,
IUseWebAPI,
IHandle<Input>,
IHandle<Output>
@ -16,8 +16,8 @@ public class WebAPIAgent : IOAgent<AgentState>,
public WebAPIAgent(
IAgentContext context,
[FromKeyedServices("EventTypes")] EventTypes typeRegistry,
string url,
ILogger<WebAPIAgent> logger) : base(
ILogger<WebAPIAgent> logger,
string url = "/agents/webio") : base(
context,
typeRegistry)
{

View File

@ -0,0 +1,53 @@
using Google.Protobuf;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AutoGen.Agents.Client;
public static class App
{
// need a variable to store the runtime instance
public static WebApplication? RuntimeApp { get; set; }
public static WebApplication? ClientApp { get; set; }
public static async ValueTask<WebApplication> StartAsync(AgentTypes? agentTypes = null, bool local = false)
{
// start the server runtime
RuntimeApp ??= await Runtime.Host.StartAsync(local);
var clientBuilder = WebApplication.CreateBuilder();
var appBuilder = clientBuilder.AddAgentWorker();
agentTypes ??= AgentTypes.GetAgentTypesFromAssembly()
?? throw new InvalidOperationException("No agent types found in the assembly");
foreach (var type in agentTypes.Types)
{
appBuilder.AddAgent(type.Key, type.Value);
}
ClientApp = clientBuilder.Build();
await ClientApp.StartAsync().ConfigureAwait(false);
return ClientApp;
}
public static async ValueTask<WebApplication> PublishMessageAsync(
string topic,
IMessage message,
AgentTypes? agentTypes = null,
bool local = false)
{
if (ClientApp == null)
{
ClientApp = await App.StartAsync(agentTypes, local);
}
var client = ClientApp.Services.GetRequiredService<AgentClient>() ?? throw new InvalidOperationException("Client not started");
await client.PublishEventAsync(topic, message).ConfigureAwait(false);
return ClientApp;
}
public static async ValueTask ShutdownAsync()
{
if (ClientApp == null)
{
throw new InvalidOperationException("Client not started");
}
await RuntimeApp!.StopAsync();
await ClientApp.StopAsync();
}
}

View File

@ -14,7 +14,8 @@ namespace Microsoft.AutoGen.Agents.Client;
public static class HostBuilderExtensions
{
public static AgentApplicationBuilder AddAgentWorker(this IHostApplicationBuilder builder, string agentServiceAddress)
private const string _defaultAgentServiceAddress = "https://localhost:5001";
public static AgentApplicationBuilder AddAgentWorker(this IHostApplicationBuilder builder, string agentServiceAddress = _defaultAgentServiceAddress, bool local = false)
{
builder.Services.AddGrpcClient<AgentRpc.AgentRpcClient>(options =>
{
@ -48,6 +49,7 @@ public static class HostBuilderExtensions
});
});
builder.Services.TryAddSingleton(DistributedContextPropagator.Current);
builder.Services.AddSingleton<AgentClient>();
builder.Services.AddSingleton<AgentWorkerRuntime>();
builder.Services.AddSingleton<IHostedService>(sp => sp.GetRequiredService<AgentWorkerRuntime>());
builder.Services.AddKeyedSingleton("EventTypes", (sp, key) =>
@ -64,7 +66,7 @@ public static class HostBuilderExtensions
var eventsMap = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(assembly => assembly.GetTypes())
.Where(type => IsSubclassOfGeneric(type, typeof(AiAgent<>)) && !type.IsAbstract)
.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()))
@ -80,8 +82,10 @@ public static class HostBuilderExtensions
var property = type.GetProperty("Descriptor", BindingFlags.Static | BindingFlags.Public);
return property?.GetValue(null) as MessageDescriptor;
}
private static bool IsSubclassOfGeneric(Type type, Type genericBaseType)
}
public sealed class ReflectionHelper
{
public static bool IsSubclassOfGeneric(Type type, Type genericBaseType)
{
while (type != null && type != typeof(object))
{
@ -98,7 +102,21 @@ public static class HostBuilderExtensions
return false;
}
}
public sealed class AgentTypes(Dictionary<string, Type> types)
{
public Dictionary<string, Type> Types { get; } = types;
public static AgentTypes? GetAgentTypesFromAssembly()
{
var agents = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(assembly => assembly.GetTypes())
.Where(type => ReflectionHelper.IsSubclassOfGeneric(type, typeof(AgentBase))
&& !type.IsAbstract
&& !type.Name.Equals("AgentClient"))
.ToDictionary(type => type.Name, type => type);
return new AgentTypes(agents);
}
}
public sealed class EventTypes(TypeRegistry typeRegistry, Dictionary<string, Type> types, Dictionary<Type, HashSet<string>> eventsMap)
{
public TypeRegistry TypeRegistry { get; } = typeRegistry;
@ -114,5 +132,10 @@ public sealed class AgentApplicationBuilder(IHostApplicationBuilder builder)
builder.Services.AddKeyedSingleton("AgentTypes", (sp, key) => Tuple.Create(typeName, typeof(TAgent)));
return this;
}
public AgentApplicationBuilder AddAgent(string typeName, Type agentType)
{
builder.Services.AddKeyedSingleton("AgentTypes", (sp, key) => Tuple.Create(typeName, agentType));
return this;
}
}

View File

@ -13,6 +13,7 @@
<ItemGroup>
<ProjectReference Include="../Microsoft.AutoGen.Agents/Microsoft.AutoGen.Agents.csproj" />
<ProjectReference Include="..\Microsoft.AutoGen.Agents.Runtime\Microsoft.AutoGen.Agents.Runtime.csproj" />
</ItemGroup>
<ItemGroup>

View File

@ -1,5 +1,7 @@
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;
@ -23,6 +25,24 @@ public static class AgentWorkerHostingExtensions
return builder;
}
public static WebApplicationBuilder AddLocalAgentService(this WebApplicationBuilder builder)
{
builder.WebHost.ConfigureKestrel(serverOptions =>
{
serverOptions.ListenLocalhost(5001, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http2;
listenOptions.UseHttps();
});
});
builder.AddAgentService();
builder.UseOrleans(siloBuilder =>
{
siloBuilder.UseLocalhostClustering(); ;
});
return builder;
}
public static WebApplication MapAgentService(this WebApplication app)
{
app.MapGrpcService<WorkerGatewayService>();

View File

@ -0,0 +1,23 @@
using Microsoft.AspNetCore.Builder;
namespace Microsoft.AutoGen.Agents.Runtime;
public static class Host
{
public static async Task<WebApplication> StartAsync(bool local = false)
{
var builder = WebApplication.CreateBuilder();
if (local)
{
builder.AddLocalAgentService();
}
else
{
builder.AddAgentService();
}
var app = builder.Build();
app.MapAgentService();
await app.StartAsync().ConfigureAwait(false);
return app;
}
}

View File

@ -36,10 +36,8 @@ public static class Extensions
// {
// options.AllowedSchemes = ["https"];
// });
return builder;
}
public static IHostApplicationBuilder ConfigureOpenTelemetry(this IHostApplicationBuilder builder)
{
builder.Logging.AddOpenTelemetry(logging =>
@ -69,7 +67,6 @@ public static class Extensions
return builder;
}
private static IHostApplicationBuilder AddOpenTelemetryExporters(this IHostApplicationBuilder builder)
{
var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]);
@ -85,19 +82,15 @@ public static class Extensions
// builder.Services.AddOpenTelemetry()
// .UseAzureMonitor();
//}
return builder;
}
public static IHostApplicationBuilder AddDefaultHealthChecks(this IHostApplicationBuilder builder)
{
builder.Services.AddHealthChecks()
// Add a default liveness check to ensure app is responsive
.AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]);
return builder;
}
public static WebApplication MapDefaultEndpoints(this WebApplication app)
{
// Adding health checks endpoints to applications in non-development environments has security implications.
@ -113,7 +106,6 @@ public static class Extensions
Predicate = r => r.Tags.Contains("live")
});
}
return app;
}
}

View File

@ -1,15 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsAspireSharedProject>true</IsAspireSharedProject>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.Extensions.Http.Resilience" />
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" />
@ -18,5 +15,4 @@
<PackageReference Include="OpenTelemetry.Instrumentation.Http" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" />
</ItemGroup>
</Project>