Skip to content

Commit ad271d9

Browse files
[.NET] Add happy path test for in-memory agent && Simplify HelloAgent example && some clean-up in extension APIs (#4227)
* add happy path test * remove unnecessary namespace * fix build error * Update AgentBaseTests.cs * revert changes ---------
1 parent d23d172 commit ad271d9

File tree

8 files changed

+126
-41
lines changed

8 files changed

+126
-41
lines changed

dotnet/samples/Hello/HelloAgent/HelloAgent.csproj

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<OutputType>Exe</OutputType>
44
<TargetFramework>net8.0</TargetFramework>
@@ -8,7 +8,6 @@
88

99
<ItemGroup>
1010
<PackageReference Include="Microsoft.Extensions.Hosting" />
11-
<PackageReference Include="Aspire.Hosting.AppHost" />
1211
</ItemGroup>
1312

1413
<ItemGroup>

dotnet/src/Microsoft.AutoGen/Agents/App.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public static class AgentsApp
1212
{
1313
// need a variable to store the runtime instance
1414
public static WebApplication? Host { get; private set; }
15+
1516
[MemberNotNull(nameof(Host))]
1617
public static async ValueTask<WebApplication> StartAsync(WebApplicationBuilder? builder = null, AgentTypes? agentTypes = null, bool local = false)
1718
{
@@ -58,7 +59,7 @@ public static async ValueTask ShutdownAsync()
5859
await Host.StopAsync();
5960
}
6061

61-
private static AgentApplicationBuilder AddAgents(this AgentApplicationBuilder builder, AgentTypes? agentTypes)
62+
private static IHostApplicationBuilder AddAgents(this IHostApplicationBuilder builder, AgentTypes? agentTypes)
6263
{
6364
agentTypes ??= AgentTypes.GetAgentTypesFromAssembly()
6465
?? throw new InvalidOperationException("No agent types found in the assembly");

dotnet/src/Microsoft.AutoGen/Agents/Services/AgentWorkerHostingExtensions.cs

+4-21
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33

44
using System.Diagnostics;
55
using Microsoft.AspNetCore.Builder;
6-
using Microsoft.AspNetCore.Hosting;
7-
using Microsoft.AspNetCore.Server.Kestrel.Core;
86
using Microsoft.Extensions.DependencyInjection;
97
using Microsoft.Extensions.DependencyInjection.Extensions;
108
using Microsoft.Extensions.Hosting;
@@ -13,25 +11,9 @@ namespace Microsoft.AutoGen.Agents;
1311

1412
public static class AgentWorkerHostingExtensions
1513
{
16-
public static WebApplicationBuilder AddAgentService(this WebApplicationBuilder builder, bool local = false, bool useGrpc = true)
14+
public static IHostApplicationBuilder AddAgentService(this IHostApplicationBuilder builder, bool local = false, bool useGrpc = true)
1715
{
18-
if (local)
19-
{
20-
//TODO: make configuration more flexible
21-
builder.WebHost.ConfigureKestrel(serverOptions =>
22-
{
23-
serverOptions.ListenLocalhost(5001, listenOptions =>
24-
{
25-
listenOptions.Protocols = HttpProtocols.Http2;
26-
listenOptions.UseHttps();
27-
});
28-
});
29-
builder.AddOrleans(local);
30-
}
31-
else
32-
{
33-
builder.AddOrleans();
34-
}
16+
builder.AddOrleans(local);
3517

3618
builder.Services.TryAddSingleton(DistributedContextPropagator.Current);
3719

@@ -45,10 +27,11 @@ public static WebApplicationBuilder AddAgentService(this WebApplicationBuilder b
4527
return builder;
4628
}
4729

48-
public static WebApplicationBuilder AddLocalAgentService(this WebApplicationBuilder builder, bool useGrpc = true)
30+
public static IHostApplicationBuilder AddLocalAgentService(this IHostApplicationBuilder builder, bool useGrpc = true)
4931
{
5032
return builder.AddAgentService(local: true, useGrpc);
5133
}
34+
5235
public static WebApplication MapAgentService(this WebApplication app, bool local = false, bool useGrpc = true)
5336
{
5437
if (useGrpc) { app.MapGrpcService<GrpcGatewayService>(); }

dotnet/src/Microsoft.AutoGen/Agents/Services/HostBuilderExtensions.cs

+20-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,23 @@ namespace Microsoft.AutoGen.Agents;
1616
public static class HostBuilderExtensions
1717
{
1818
private const string _defaultAgentServiceAddress = "https://localhost:5001";
19-
public static AgentApplicationBuilder AddAgentWorker(this IHostApplicationBuilder builder, string agentServiceAddress = _defaultAgentServiceAddress, bool local = false)
19+
20+
public static IHostApplicationBuilder AddAgent<
21+
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TAgent>(this IHostApplicationBuilder builder, string typeName) where TAgent : AgentBase
22+
{
23+
builder.Services.AddKeyedSingleton("AgentTypes", (sp, key) => Tuple.Create(typeName, typeof(TAgent)));
24+
25+
return builder;
26+
}
27+
28+
public static IHostApplicationBuilder AddAgent(this IHostApplicationBuilder builder, string typeName, Type agentType)
29+
{
30+
builder.Services.AddKeyedSingleton("AgentTypes", (sp, key) => Tuple.Create(typeName, agentType));
31+
32+
return builder;
33+
}
34+
35+
public static IHostApplicationBuilder AddAgentWorker(this IHostApplicationBuilder builder, string agentServiceAddress = _defaultAgentServiceAddress, bool local = false)
2036
{
2137
builder.Services.TryAddSingleton(DistributedContextPropagator.Current);
2238

@@ -98,7 +114,9 @@ public static AgentApplicationBuilder AddAgentWorker(this IHostApplicationBuilde
98114
return new EventTypes(typeRegistry, types, eventsMap);
99115
});
100116
builder.Services.AddSingleton<Client>();
101-
return new AgentApplicationBuilder(builder);
117+
builder.Services.AddSingleton(new AgentApplicationBuilder(builder));
118+
119+
return builder;
102120
}
103121

104122
private static MessageDescriptor? GetMessageDescriptor(Type type)

dotnet/src/Microsoft.AutoGen/Agents/Services/Orleans/OrleansRuntimeHostingExtenions.cs

+17-11
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,17 @@ public static class OrleansRuntimeHostingExtenions
1515
{
1616
public static WebApplicationBuilder AddOrleans(this WebApplicationBuilder builder, bool local = false)
1717
{
18+
return builder.AddOrleans(local);
19+
}
1820

21+
public static IHostApplicationBuilder AddOrleans(this IHostApplicationBuilder builder, bool local = false)
22+
{
1923
builder.Services.AddSerializer(serializer => serializer.AddProtobufSerializer());
24+
builder.Services.AddSingleton<IRegistryGrain, RegistryGrain>();
25+
2026
// Ensure Orleans is added before the hosted service to guarantee that it starts first.
2127
//TODO: make all of this configurable
22-
builder.Host.UseOrleans(siloBuilder =>
28+
builder.UseOrleans((siloBuilder) =>
2329
{
2430
// Development mode or local mode uses in-memory storage and streams
2531
if (builder.Environment.IsDevelopment() || local)
@@ -51,16 +57,16 @@ public static WebApplicationBuilder AddOrleans(this WebApplicationBuilder builde
5157
options.SystemResponseTimeout = TimeSpan.FromMinutes(3);
5258
});
5359
siloBuilder.Configure<ClientMessagingOptions>(options =>
54-
{
55-
options.ResponseTimeout = TimeSpan.FromMinutes(3);
56-
});
60+
{
61+
options.ResponseTimeout = TimeSpan.FromMinutes(3);
62+
});
5763
siloBuilder.UseCosmosClustering(o =>
58-
{
59-
o.ConfigureCosmosClient(cosmosDbconnectionString);
60-
o.ContainerName = "AutoGen";
61-
o.DatabaseName = "clustering";
62-
o.IsResourceCreationEnabled = true;
63-
});
64+
{
65+
o.ConfigureCosmosClient(cosmosDbconnectionString);
66+
o.ContainerName = "AutoGen";
67+
o.DatabaseName = "clustering";
68+
o.IsResourceCreationEnabled = true;
69+
});
6470

6571
siloBuilder.UseCosmosReminderService(o =>
6672
{
@@ -84,7 +90,7 @@ public static WebApplicationBuilder AddOrleans(this WebApplicationBuilder builde
8490
.AddMemoryGrainStorage("PubSubStore");
8591
}
8692
});
87-
builder.Services.AddSingleton<IRegistryGrain, RegistryGrain>();
93+
8894
return builder;
8995
}
9096
}

dotnet/test/Microsoft.AutoGen.Agents.Tests/AgentBaseTests.cs

+77-3
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// AgentBaseTests.cs
33

4+
using System.Collections.Concurrent;
45
using FluentAssertions;
56
using Google.Protobuf.Reflection;
67
using Microsoft.AutoGen.Abstractions;
8+
using Microsoft.Extensions.DependencyInjection;
9+
using Microsoft.Extensions.Hosting;
710
using Microsoft.Extensions.Logging;
811
using Moq;
912
using Xunit;
13+
using static Microsoft.AutoGen.Agents.Tests.AgentBaseTests;
1014

1115
namespace Microsoft.AutoGen.Agents.Tests;
1216

13-
public class AgentBaseTests
17+
[Collection(ClusterFixtureCollection.Name)]
18+
public class AgentBaseTests(InMemoryAgentRuntimeFixture fixture)
1419
{
20+
private readonly InMemoryAgentRuntimeFixture _fixture = fixture;
21+
1522
[Fact]
1623
public async Task ItInvokeRightHandlerTestAsync()
1724
{
@@ -26,12 +33,36 @@ public async Task ItInvokeRightHandlerTestAsync()
2633
agent.ReceivedItems[1].Should().Be(42);
2734
}
2835

36+
[Fact]
37+
public async Task ItDelegateMessageToTestAgentAsync()
38+
{
39+
var client = _fixture.AppHost.Services.GetRequiredService<Client>();
40+
41+
await client.PublishMessageAsync(new TextMessage()
42+
{
43+
Source = nameof(ItDelegateMessageToTestAgentAsync),
44+
TextMessage_ = "buffer"
45+
}, token: CancellationToken.None);
46+
47+
// wait for 10 seconds
48+
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
49+
while (!TestAgent.ReceivedMessages.ContainsKey(nameof(ItDelegateMessageToTestAgentAsync)) && !cts.Token.IsCancellationRequested)
50+
{
51+
await Task.Delay(100);
52+
}
53+
54+
TestAgent.ReceivedMessages[nameof(ItDelegateMessageToTestAgentAsync)].Should().NotBeNull();
55+
}
56+
2957
/// <summary>
3058
/// The test agent is a simple agent that is used for testing purposes.
3159
/// </summary>
32-
public class TestAgent : AgentBase, IHandle<string>, IHandle<int>
60+
public class TestAgent : AgentBase, IHandle<string>, IHandle<int>, IHandle<TextMessage>
3361
{
34-
public TestAgent(IAgentRuntime context, EventTypes eventTypes, Logger<AgentBase> logger) : base(context, eventTypes, logger)
62+
public TestAgent(
63+
IAgentRuntime context,
64+
[FromKeyedServices("EventTypes")] EventTypes eventTypes,
65+
Logger<AgentBase>? logger = null) : base(context, eventTypes, logger)
3566
{
3667
}
3768

@@ -47,6 +78,49 @@ public Task Handle(int item)
4778
return Task.CompletedTask;
4879
}
4980

81+
public Task Handle(TextMessage item)
82+
{
83+
ReceivedMessages[item.Source] = item.TextMessage_;
84+
return Task.CompletedTask;
85+
}
86+
5087
public List<object> ReceivedItems { get; private set; } = [];
88+
89+
/// <summary>
90+
/// Key: source
91+
/// Value: message
92+
/// </summary>
93+
public static ConcurrentDictionary<string, object> ReceivedMessages { get; private set; } = new();
94+
}
95+
}
96+
97+
public sealed class InMemoryAgentRuntimeFixture : IDisposable
98+
{
99+
public InMemoryAgentRuntimeFixture()
100+
{
101+
var builder = Microsoft.Extensions.Hosting.Host.CreateApplicationBuilder();
102+
103+
// step 1: create in-memory agent runtime
104+
// step 2: register TestAgent to that agent runtime
105+
builder
106+
.AddAgentService(local: true, useGrpc: false)
107+
.AddAgentWorker(local: true)
108+
.AddAgent<TestAgent>(nameof(TestAgent));
109+
110+
AppHost = builder.Build();
111+
AppHost.StartAsync().Wait();
112+
}
113+
public IHost AppHost { get; }
114+
115+
void IDisposable.Dispose()
116+
{
117+
AppHost.StopAsync().Wait();
118+
AppHost.Dispose();
51119
}
52120
}
121+
122+
[CollectionDefinition(Name)]
123+
public sealed class ClusterFixtureCollection : ICollectionFixture<InMemoryAgentRuntimeFixture>
124+
{
125+
public const string Name = nameof(ClusterFixtureCollection);
126+
}

dotnet/test/Microsoft.AutoGen.Agents.Tests/Microsoft.AutoGen.Agents.Tests.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
<ItemGroup>
1111
<ProjectReference Include="..\..\src\Microsoft.AutoGen\Agents\Microsoft.AutoGen.Agents.csproj" />
12+
<PackageReference Include="Microsoft.Extensions.Hosting" />
1213
</ItemGroup>
1314

1415
</Project>

protos/agent_events.proto

+4-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ syntax = "proto3";
33
package agents;
44

55
option csharp_namespace = "Microsoft.AutoGen.Abstractions";
6-
6+
message TextMessage {
7+
string textMessage = 1;
8+
string source = 2;
9+
}
710
message Input {
811
string message = 1;
912
}

0 commit comments

Comments
 (0)