mirror of https://github.com/microsoft/autogen.git
rysweet-unsubscribe-and-agent-tests-4744 (#4920)
* add tests for core functionality and client/server * add remove subscription, get subscriptions * fix LOTS of bugs * add grpc tuning * adapt to latest agreed agents_worker proto changes.
This commit is contained in:
parent
55e929db98
commit
b6597fdd24
|
@ -100,16 +100,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.WebAPI.Sample", "sa
|
|||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DevTeam", "DevTeam", "{05B9C173-6441-4DCA-9AC4-E897EF75F331}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevTeam.AgentHost", "samples\dev-team\DevTeam.AgentHost\DevTeam.AgentHost.csproj", "{462A357B-7BB9-4927-A9FD-4FB7675898E9}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevTeam.Agents", "samples\dev-team\DevTeam.Agents\DevTeam.Agents.csproj", "{83BBB833-A2F0-4A4D-BA1B-8229FC9BCD4F}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevTeam.AppHost", "samples\dev-team\DevTeam.AppHost\DevTeam.AppHost.csproj", "{63280C12-3BE3-4C4E-805E-584CDC6BC1F5}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevTeam.Backend", "samples\dev-team\DevTeam.Backend\DevTeam.Backend.csproj", "{EDA3EF83-FC7F-4BCF-945D-B893620EE4B1}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevTeam.Shared", "samples\dev-team\DevTeam.Shared\DevTeam.Shared.csproj", "{01F5D7C3-41EB-409C-9B77-A945C07FA7E8}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Hello", "Hello", "{7EB336C2-7C0A-4BC8-80C6-A3173AB8DC45}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hello.AppHost", "samples\Hello\Hello.AppHost\Hello.AppHost.csproj", "{09A373A0-8169-409F-8C37-3FBC1654B122}"
|
||||
|
@ -126,8 +120,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HelloAgentState", "samples\
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AutoGen.Extensions.Aspire", "src\Microsoft.AutoGen\Extensions\Aspire\Microsoft.AutoGen.Extensions.Aspire.csproj", "{65059914-5527-4A00-9308-9FAF23D5E85A}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AutoGen.Agents.Tests", "test\Microsoft.AutoGen.Agents.Tests\Microsoft.AutoGen.Agents.Tests.csproj", "{394FDAF8-74F9-4977-94A5-3371737EB774}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AutoGen.Integration.Tests", "test\Microsoft.AutoGen.Integration.Tests\Microsoft.AutoGen.Integration.Tests.csproj", "{D04C6153-8EAF-4E54-9852-52CEC1BE8D31}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HelloAgent.AppHost", "test\Microsoft.AutoGen.Integration.Tests.AppHosts\HelloAgent.AppHost\HelloAgent.AppHost.csproj", "{99D7766B-076F-4E6F-A8D2-3DF1DAFA2599}"
|
||||
|
@ -140,7 +132,17 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AutoGen.Runtime.G
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AutoGen.Agents", "src\Microsoft.AutoGen\Agents\Microsoft.AutoGen.Agents.csproj", "{3892C83E-7F5D-41DF-A88C-4854EAD38856}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Autogen.AgentHost", "src\Microsoft.AutoGen\AgentHost\Microsoft.Autogen.AgentHost.csproj", "{4CB42139-DEE4-40B9-AA81-1E4CCAA2F338}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AutoGen.AgentHost", "src\Microsoft.AutoGen\AgentHost\Microsoft.AutoGen.AgentHost.csproj", "{4CB42139-DEE4-40B9-AA81-1E4CCAA2F338}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AutoGen.Runtime.Grpc.Tests", "test\Microsoft.AutoGen.Runtime.Grpc.Tests\Microsoft.AutoGen.Runtime.Grpc.Tests.csproj", "{0E7983BB-2602-421E-8B37-332E52870A10}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AutoGen.Core.Tests", "test\Microsoft.AutoGen.Core.Tests\Microsoft.AutoGen.Core.Tests.csproj", "{EAFFE339-26CB-4019-991D-BCCE8E7D33A1}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DevTeam.ServiceDefaults", "samples\dev-team\DevTeam.ServiceDefaults\DevTeam.ServiceDefaults.csproj", "{599E1971-1DA9-453F-A7A8-42510BBC95C2}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AutoGen.Core.Grpc.Tests", "test\Microsoft.AutoGen.Core.Grpc.Tests\Microsoft.AutoGen.Core.Grpc.Tests.csproj", "{33A28A4B-123B-4416-9631-0F759B8D6172}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AutoGen.Tests.Shared", "test\Microsoft.AutoGen.Tests.Shared\Microsoft.AutoGen.Tests.Shared.csproj", "{58AD8E1D-83BD-4950-A324-1A20677D78D9}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
@ -296,14 +298,6 @@ Global
|
|||
{4385AFCF-AB4A-49B2-BEBA-D33C950E1EE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4385AFCF-AB4A-49B2-BEBA-D33C950E1EE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4385AFCF-AB4A-49B2-BEBA-D33C950E1EE6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{462A357B-7BB9-4927-A9FD-4FB7675898E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{462A357B-7BB9-4927-A9FD-4FB7675898E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{462A357B-7BB9-4927-A9FD-4FB7675898E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{462A357B-7BB9-4927-A9FD-4FB7675898E9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{83BBB833-A2F0-4A4D-BA1B-8229FC9BCD4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{83BBB833-A2F0-4A4D-BA1B-8229FC9BCD4F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{83BBB833-A2F0-4A4D-BA1B-8229FC9BCD4F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{83BBB833-A2F0-4A4D-BA1B-8229FC9BCD4F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{63280C12-3BE3-4C4E-805E-584CDC6BC1F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{63280C12-3BE3-4C4E-805E-584CDC6BC1F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{63280C12-3BE3-4C4E-805E-584CDC6BC1F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
@ -312,10 +306,6 @@ Global
|
|||
{EDA3EF83-FC7F-4BCF-945D-B893620EE4B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EDA3EF83-FC7F-4BCF-945D-B893620EE4B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EDA3EF83-FC7F-4BCF-945D-B893620EE4B1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{01F5D7C3-41EB-409C-9B77-A945C07FA7E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{01F5D7C3-41EB-409C-9B77-A945C07FA7E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{01F5D7C3-41EB-409C-9B77-A945C07FA7E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{01F5D7C3-41EB-409C-9B77-A945C07FA7E8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{09A373A0-8169-409F-8C37-3FBC1654B122}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{09A373A0-8169-409F-8C37-3FBC1654B122}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{09A373A0-8169-409F-8C37-3FBC1654B122}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
@ -340,10 +330,6 @@ Global
|
|||
{65059914-5527-4A00-9308-9FAF23D5E85A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{65059914-5527-4A00-9308-9FAF23D5E85A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{65059914-5527-4A00-9308-9FAF23D5E85A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{394FDAF8-74F9-4977-94A5-3371737EB774}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{394FDAF8-74F9-4977-94A5-3371737EB774}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{394FDAF8-74F9-4977-94A5-3371737EB774}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{394FDAF8-74F9-4977-94A5-3371737EB774}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D04C6153-8EAF-4E54-9852-52CEC1BE8D31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D04C6153-8EAF-4E54-9852-52CEC1BE8D31}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D04C6153-8EAF-4E54-9852-52CEC1BE8D31}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
@ -372,6 +358,30 @@ Global
|
|||
{4CB42139-DEE4-40B9-AA81-1E4CCAA2F338}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4CB42139-DEE4-40B9-AA81-1E4CCAA2F338}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4CB42139-DEE4-40B9-AA81-1E4CCAA2F338}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{0E7983BB-2602-421E-8B37-332E52870A10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0E7983BB-2602-421E-8B37-332E52870A10}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0E7983BB-2602-421E-8B37-332E52870A10}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0E7983BB-2602-421E-8B37-332E52870A10}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{14F90F79-580E-454D-BA7A-ED6D9723020D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{14F90F79-580E-454D-BA7A-ED6D9723020D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{14F90F79-580E-454D-BA7A-ED6D9723020D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{14F90F79-580E-454D-BA7A-ED6D9723020D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{EAFFE339-26CB-4019-991D-BCCE8E7D33A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{EAFFE339-26CB-4019-991D-BCCE8E7D33A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EAFFE339-26CB-4019-991D-BCCE8E7D33A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EAFFE339-26CB-4019-991D-BCCE8E7D33A1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{599E1971-1DA9-453F-A7A8-42510BBC95C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{599E1971-1DA9-453F-A7A8-42510BBC95C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{599E1971-1DA9-453F-A7A8-42510BBC95C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{599E1971-1DA9-453F-A7A8-42510BBC95C2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{33A28A4B-123B-4416-9631-0F759B8D6172}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{33A28A4B-123B-4416-9631-0F759B8D6172}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{33A28A4B-123B-4416-9631-0F759B8D6172}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{33A28A4B-123B-4416-9631-0F759B8D6172}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{58AD8E1D-83BD-4950-A324-1A20677D78D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{58AD8E1D-83BD-4950-A324-1A20677D78D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{58AD8E1D-83BD-4950-A324-1A20677D78D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{58AD8E1D-83BD-4950-A324-1A20677D78D9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -417,11 +427,8 @@ Global
|
|||
{CB8824F5-9475-451F-87E8-F2AEF2490A12} = {668726B9-77BC-45CF-B576-0F0773BF1615}
|
||||
{4385AFCF-AB4A-49B2-BEBA-D33C950E1EE6} = {668726B9-77BC-45CF-B576-0F0773BF1615}
|
||||
{05B9C173-6441-4DCA-9AC4-E897EF75F331} = {686480D7-8FEC-4ED3-9C5D-CEBE1057A7ED}
|
||||
{462A357B-7BB9-4927-A9FD-4FB7675898E9} = {05B9C173-6441-4DCA-9AC4-E897EF75F331}
|
||||
{83BBB833-A2F0-4A4D-BA1B-8229FC9BCD4F} = {05B9C173-6441-4DCA-9AC4-E897EF75F331}
|
||||
{63280C12-3BE3-4C4E-805E-584CDC6BC1F5} = {05B9C173-6441-4DCA-9AC4-E897EF75F331}
|
||||
{EDA3EF83-FC7F-4BCF-945D-B893620EE4B1} = {05B9C173-6441-4DCA-9AC4-E897EF75F331}
|
||||
{01F5D7C3-41EB-409C-9B77-A945C07FA7E8} = {05B9C173-6441-4DCA-9AC4-E897EF75F331}
|
||||
{7EB336C2-7C0A-4BC8-80C6-A3173AB8DC45} = {686480D7-8FEC-4ED3-9C5D-CEBE1057A7ED}
|
||||
{09A373A0-8169-409F-8C37-3FBC1654B122} = {7EB336C2-7C0A-4BC8-80C6-A3173AB8DC45}
|
||||
{A20B9894-F352-4338-872A-F215A241D43D} = {7EB336C2-7C0A-4BC8-80C6-A3173AB8DC45}
|
||||
|
@ -429,7 +436,6 @@ Global
|
|||
{97550E87-48C6-4EBF-85E1-413ABAE9DBFD} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
|
||||
{64EF61E7-00A6-4E5E-9808-62E10993A0E5} = {7EB336C2-7C0A-4BC8-80C6-A3173AB8DC45}
|
||||
{65059914-5527-4A00-9308-9FAF23D5E85A} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
|
||||
{394FDAF8-74F9-4977-94A5-3371737EB774} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
|
||||
{D04C6153-8EAF-4E54-9852-52CEC1BE8D31} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
|
||||
{99D7766B-076F-4E6F-A8D2-3DF1DAFA2599} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
|
||||
{7F60934B-3E59-48D0-B26D-04A39FEC13EF} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
|
||||
|
@ -437,6 +443,11 @@ Global
|
|||
{8457B68C-CC86-4A3F-8559-C1AE199EC366} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
|
||||
{3892C83E-7F5D-41DF-A88C-4854EAD38856} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
|
||||
{4CB42139-DEE4-40B9-AA81-1E4CCAA2F338} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
|
||||
{0E7983BB-2602-421E-8B37-332E52870A10} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
|
||||
{EAFFE339-26CB-4019-991D-BCCE8E7D33A1} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
|
||||
{599E1971-1DA9-453F-A7A8-42510BBC95C2} = {05B9C173-6441-4DCA-9AC4-E897EF75F331}
|
||||
{33A28A4B-123B-4416-9631-0F759B8D6172} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
|
||||
{58AD8E1D-83BD-4950-A324-1A20677D78D9} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {93384647-528D-46C8-922C-8DB36A382F0B}
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
<PackageVersion Include="MartinCostello.Logging.XUnit" Version="0.4.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.App" Version="8.0.4" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="8.0.8" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Version="8.0.11" />
|
||||
<PackageVersion Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.22.0" />
|
||||
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.AI" Version="$(MicrosoftExtensionsAIVersion)" />
|
||||
|
@ -83,6 +84,7 @@
|
|||
<PackageVersion Include="Microsoft.Orleans.Server" Version="$(MicrosoftOrleans)" />
|
||||
<PackageVersion Include="Microsoft.Orleans.Streaming" Version="$(MicrosoftOrleans)" />
|
||||
<PackageVersion Include="Microsoft.Orleans.Streaming.EventHubs" Version="$(MicrosoftOrleans)" />
|
||||
<PackageVersion Include="Microsoft.Orleans.TestingHost" Version="9.0.1" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel" Version="1.29.0" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Agents.Core" Version="$(MicrosoftSemanticKernelExperimentalVersion)" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.AzureOpenAI" Version="1.29.0" />
|
||||
|
@ -125,7 +127,6 @@
|
|||
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
<PackageVersion Include="Moq" Version="4.20.72" />
|
||||
<PackageVersion Include="Microsoft.PowerShell.SDK" Version="7.4.5" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Version="8.0.11" />
|
||||
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -18,7 +18,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../../src/Microsoft.AutoGen/AgentHost/Microsoft.Autogen.AgentHost.csproj" />
|
||||
<ProjectReference Include="../../../src/Microsoft.AutoGen/AgentHost/Microsoft.AutoGen.AgentHost.csproj" />
|
||||
<ProjectReference Include="..\HelloAgent\HelloAgent.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
var builder = DistributedApplication.CreateBuilder(args);
|
||||
var backend = builder.AddProject<Projects.Microsoft_Autogen_AgentHost>("backend").WithExternalHttpEndpoints();
|
||||
var backend = builder.AddProject<Projects.Microsoft_AutoGen_AgentHost>("backend").WithExternalHttpEndpoints();
|
||||
var client = builder.AddProject<Projects.HelloAgent>("HelloAgentsDotNET")
|
||||
.WithReference(backend)
|
||||
.WithEnvironment("AGENT_HOST", backend.GetEndpoint("https"))
|
||||
|
@ -12,7 +12,8 @@ var client = builder.AddProject<Projects.HelloAgent>("HelloAgentsDotNET")
|
|||
.WaitFor(backend);
|
||||
#pragma warning disable ASPIREHOSTINGPYTHON001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
|
||||
// xlang is over http for now - in prod use TLS between containers
|
||||
builder.AddPythonApp("HelloAgentsPython", "../../../../python/samples/core_xlang_hello_python_agent", "hello_python_agent.py", "../../.venv").WithReference(backend)
|
||||
builder.AddPythonApp("HelloAgentsPython", "../../../../python/samples/core_xlang_hello_python_agent", "hello_python_agent.py", "../../.venv")
|
||||
.WithReference(backend)
|
||||
.WithEnvironment("AGENT_HOST", backend.GetEndpoint("http"))
|
||||
.WithEnvironment("STAY_ALIVE_ON_GOODBYE", "true")
|
||||
.WithEnvironment("GRPC_DNS_RESOLVER", "native")
|
||||
|
|
|
@ -8,17 +8,15 @@ using Microsoft.Extensions.AI;
|
|||
namespace Hello;
|
||||
[TopicSubscription("agents")]
|
||||
public class HelloAIAgent(
|
||||
IAgentWorker worker,
|
||||
[FromKeyedServices("EventTypes")] EventTypes typeRegistry,
|
||||
[FromKeyedServices("AgentsMetadata")] AgentsMetadata typeRegistry,
|
||||
IHostApplicationLifetime hostApplicationLifetime,
|
||||
IChatClient client) : HelloAgent(
|
||||
worker,
|
||||
typeRegistry,
|
||||
hostApplicationLifetime),
|
||||
IHandle<NewMessageReceived>
|
||||
{
|
||||
// This Handle supercedes the one in the base class
|
||||
public new async Task Handle(NewMessageReceived item)
|
||||
public new async Task Handle(NewMessageReceived item, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var prompt = "Please write a limerick greeting someone with the name " + item.Message;
|
||||
var response = await client.CompleteAsync(prompt);
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../../src/Microsoft.AutoGen/Core.Grpc/Microsoft.AutoGen.Core.Grpc.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.AutoGen\Agents\Microsoft.AutoGen.Agents.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.AutoGen\Contracts\Microsoft.AutoGen.Contracts.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.AutoGen\Core\Microsoft.AutoGen.Core.csproj" />
|
||||
|
|
|
@ -18,31 +18,30 @@ if (Environment.GetEnvironmentVariable("AZURE_OPENAI_CONNECTION_STRING") == null
|
|||
}
|
||||
builder.Configuration["ConnectionStrings:HelloAIAgents"] = Environment.GetEnvironmentVariable("AZURE_OPENAI_CONNECTION_STRING");
|
||||
builder.AddChatCompletionService("HelloAIAgents");
|
||||
var agentTypes = new AgentTypes(new Dictionary<string, Type>
|
||||
var _ = new AgentTypes(new Dictionary<string, Type>
|
||||
{
|
||||
{ "HelloAIAgents", typeof(HelloAIAgent) }
|
||||
});
|
||||
var app = await AgentsApp.PublishMessageAsync("HelloAgents", new NewMessageReceived
|
||||
var local = true;
|
||||
if (Environment.GetEnvironmentVariable("AGENT_HOST") != null) { local = false; }
|
||||
var app = await Microsoft.AutoGen.Core.Grpc.AgentsApp.PublishMessageAsync("HelloAgents", new NewMessageReceived
|
||||
{
|
||||
Message = "World"
|
||||
}, builder, agentTypes, local: true);
|
||||
|
||||
}, local: local).ConfigureAwait(false);
|
||||
await app.WaitForShutdownAsync();
|
||||
|
||||
namespace Hello
|
||||
{
|
||||
[TopicSubscription("agents")]
|
||||
[TopicSubscription("HelloAgents")]
|
||||
public class HelloAgent(
|
||||
IAgentWorker worker,
|
||||
[FromKeyedServices("EventTypes")] EventTypes typeRegistry,
|
||||
[FromKeyedServices("AgentsMetadata")] AgentsMetadata typeRegistry,
|
||||
IHostApplicationLifetime hostApplicationLifetime) : ConsoleAgent(
|
||||
worker,
|
||||
typeRegistry),
|
||||
ISayHello,
|
||||
IHandle<NewMessageReceived>,
|
||||
IHandle<ConversationClosed>
|
||||
{
|
||||
public async Task Handle(NewMessageReceived item)
|
||||
public async Task Handle(NewMessageReceived item, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var response = await SayHello(item.Message).ConfigureAwait(false);
|
||||
var evt = new Output
|
||||
|
@ -57,7 +56,7 @@ namespace Hello
|
|||
};
|
||||
await PublishMessageAsync(goodbye).ConfigureAwait(false);
|
||||
}
|
||||
public async Task Handle(ConversationClosed item)
|
||||
public async Task Handle(ConversationClosed item, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var goodbye = $"********************* {item.UserId} said {item.UserMessage} ************************";
|
||||
var evt = new Output
|
||||
|
|
|
@ -18,9 +18,8 @@ namespace Hello
|
|||
{
|
||||
[TopicSubscription("HelloAgents")]
|
||||
public class HelloAgent(
|
||||
IAgentWorker worker, IHostApplicationLifetime hostApplicationLifetime,
|
||||
[FromKeyedServices("EventTypes")] EventTypes typeRegistry) : Agent(
|
||||
worker,
|
||||
IHostApplicationLifetime hostApplicationLifetime,
|
||||
[FromKeyedServices("AgentsMetadata")] AgentsMetadata typeRegistry) : Agent(
|
||||
typeRegistry),
|
||||
ISayHello,
|
||||
IHandleConsole,
|
||||
|
@ -28,7 +27,7 @@ namespace Hello
|
|||
IHandle<ConversationClosed>,
|
||||
IHandle<Shutdown>
|
||||
{
|
||||
public async Task Handle(NewMessageReceived item)
|
||||
public async Task Handle(NewMessageReceived item, CancellationToken cancellationToken)
|
||||
{
|
||||
var response = await SayHello(item.Message).ConfigureAwait(false);
|
||||
var evt = new Output { Message = response };
|
||||
|
@ -40,7 +39,7 @@ namespace Hello
|
|||
};
|
||||
await PublishMessageAsync(goodbye).ConfigureAwait(false);
|
||||
}
|
||||
public async Task Handle(ConversationClosed item)
|
||||
public async Task Handle(ConversationClosed item, CancellationToken cancellationToken)
|
||||
{
|
||||
var goodbye = $"********************* {item.UserId} said {item.UserMessage} ************************";
|
||||
var evt = new Output { Message = goodbye };
|
||||
|
@ -51,13 +50,13 @@ namespace Hello
|
|||
}
|
||||
}
|
||||
|
||||
public async Task Handle(Shutdown item)
|
||||
public async Task Handle(Shutdown item, CancellationToken cancellationToken)
|
||||
{
|
||||
Console.WriteLine("Shutting down...");
|
||||
hostApplicationLifetime.StopApplication();
|
||||
}
|
||||
|
||||
public async Task<string> SayHello(string ask)
|
||||
public async Task<string> SayHello(string ask, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var response = $"\n\n\n\n***************Hello {ask}**********************\n\n\n\n";
|
||||
return response;
|
||||
|
@ -65,6 +64,6 @@ namespace Hello
|
|||
}
|
||||
public interface ISayHello
|
||||
{
|
||||
public Task<string> SayHello(string ask);
|
||||
public Task<string> SayHello(string ask, CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,10 +25,10 @@ Flow Diagram:
|
|||
```mermaid
|
||||
%%{init: {'theme':'forest'}}%%
|
||||
graph LR;
|
||||
A[Main] --> |"PublishEventAsync(NewMessage('World'))"| B{"Handle(NewMessageReceived item)"}
|
||||
A[Main] --> |"PublishEventAsync(NewMessage('World'))"| B{"Handle(NewMessageReceived item, CancellationToken cancellationToken = default)"}
|
||||
B --> |"PublishEventAsync(Output('***Hello, World***'))"| C[ConsoleAgent]
|
||||
C --> D{"WriteConsole()"}
|
||||
B --> |"PublishEventAsync(ConversationClosed('Goodbye'))"| E{"Handle(ConversationClosed item)"}
|
||||
B --> |"PublishEventAsync(ConversationClosed('Goodbye'))"| E{"Handle(ConversationClosed item, CancellationToken cancellationToken = default)"}
|
||||
B --> |"PublishEventAsync(Output('***Goodbye***'))"| C
|
||||
E --> F{"Shutdown()"}
|
||||
|
||||
|
@ -44,14 +44,14 @@ Within that event handler you may optionally *emit* new events, which are then s
|
|||
TopicSubscription("HelloAgents")]
|
||||
public class HelloAgent(
|
||||
iAgentWorker worker,
|
||||
[FromKeyedServices("EventTypes")] EventTypes typeRegistry) : ConsoleAgent(
|
||||
[FromKeyedServices("AgentsMetadata")] AgentsMetadata typeRegistry) : ConsoleAgent(
|
||||
worker,
|
||||
typeRegistry),
|
||||
ISayHello,
|
||||
IHandle<NewMessageReceived>,
|
||||
IHandle<ConversationClosed>
|
||||
{
|
||||
public async Task Handle(NewMessageReceived item)
|
||||
public async Task Handle(NewMessageReceived item, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var response = await SayHello(item.Message).ConfigureAwait(false);
|
||||
var evt = new Output
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../../src/Microsoft.AutoGen/Core.Grpc/Microsoft.AutoGen.Core.Grpc.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.AutoGen\Agents\Microsoft.AutoGen.Agents.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.AutoGen\Contracts\Microsoft.AutoGen.Contracts.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.AutoGen\Core\Microsoft.AutoGen.Core.csproj" />
|
||||
|
|
|
@ -7,21 +7,21 @@ using Microsoft.AutoGen.Contracts;
|
|||
using Microsoft.AutoGen.Core;
|
||||
|
||||
// send a message to the agent
|
||||
var app = await AgentsApp.PublishMessageAsync("HelloAgents", new NewMessageReceived
|
||||
var local = true;
|
||||
if (Environment.GetEnvironmentVariable("AGENT_HOST") != null) { local = false; }
|
||||
var app = await Microsoft.AutoGen.Core.Grpc.AgentsApp.PublishMessageAsync("HelloAgents", new NewMessageReceived
|
||||
{
|
||||
Message = "World"
|
||||
}, local: false);
|
||||
}, local: local).ConfigureAwait(false);
|
||||
|
||||
await app.WaitForShutdownAsync();
|
||||
|
||||
namespace Hello
|
||||
{
|
||||
[TopicSubscription("agents")]
|
||||
[TopicSubscription("HelloAgents")]
|
||||
public class HelloAgent(
|
||||
IAgentWorker worker,
|
||||
IHostApplicationLifetime hostApplicationLifetime,
|
||||
[FromKeyedServices("EventTypes")] EventTypes typeRegistry) : Agent(
|
||||
worker,
|
||||
[FromKeyedServices("AgentsMetadata")] AgentsMetadata typeRegistry) : Agent(
|
||||
typeRegistry),
|
||||
IHandleConsole,
|
||||
IHandle<NewMessageReceived>,
|
||||
|
@ -29,7 +29,7 @@ namespace Hello
|
|||
IHandle<Shutdown>
|
||||
{
|
||||
private AgentState? State { get; set; }
|
||||
public async Task Handle(NewMessageReceived item)
|
||||
public async Task Handle(NewMessageReceived item, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var response = await SayHello(item.Message).ConfigureAwait(false);
|
||||
var evt = new Output
|
||||
|
@ -57,7 +57,7 @@ namespace Hello
|
|||
await PublishMessageAsync(new Shutdown { Message = this.AgentId.Key }).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
public async Task Handle(ConversationClosed item)
|
||||
public async Task Handle(ConversationClosed item, CancellationToken cancellationToken = default)
|
||||
{
|
||||
State = await ReadAsync<AgentState>(this.AgentId).ConfigureAwait(false);
|
||||
var state = JsonSerializer.Deserialize<Dictionary<string, string>>(State.TextData) ?? new Dictionary<string, string> { { "data", "No state data found" } };
|
||||
|
@ -74,7 +74,7 @@ namespace Hello
|
|||
TextData = JsonSerializer.Serialize(state)
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
public async Task Handle(Shutdown item)
|
||||
public async Task Handle(Shutdown item, CancellationToken cancellationToken = default)
|
||||
{
|
||||
string? workflow = null;
|
||||
// make sure the workflow is finished
|
||||
|
|
|
@ -25,10 +25,10 @@ Flow Diagram:
|
|||
```mermaid
|
||||
%%{init: {'theme':'forest'}}%%
|
||||
graph LR;
|
||||
A[Main] --> |"PublishEventAsync(NewMessage('World'))"| B{"Handle(NewMessageReceived item)"}
|
||||
A[Main] --> |"PublishEventAsync(NewMessage('World'))"| B{"Handle(NewMessageReceived item, CancellationToken cancellationToken = default)"}
|
||||
B --> |"PublishEventAsync(Output('***Hello, World***'))"| C[ConsoleAgent]
|
||||
C --> D{"WriteConsole()"}
|
||||
B --> |"PublishEventAsync(ConversationClosed('Goodbye'))"| E{"Handle(ConversationClosed item)"}
|
||||
B --> |"PublishEventAsync(ConversationClosed('Goodbye'))"| E{"Handle(ConversationClosed item, CancellationToken cancellationToken = default)"}
|
||||
B --> |"PublishEventAsync(Output('***Goodbye***'))"| C
|
||||
E --> F{"Shutdown()"}
|
||||
|
||||
|
@ -44,14 +44,14 @@ Within that event handler you may optionally *emit* new events, which are then s
|
|||
TopicSubscription("HelloAgents")]
|
||||
public class HelloAgent(
|
||||
iAgentWorker worker,
|
||||
[FromKeyedServices("EventTypes")] EventTypes typeRegistry) : ConsoleAgent(
|
||||
[FromKeyedServices("AgentsMetadata")] AgentsMetadata typeRegistry) : ConsoleAgent(
|
||||
worker,
|
||||
typeRegistry),
|
||||
ISayHello,
|
||||
IHandle<NewMessageReceived>,
|
||||
IHandle<ConversationClosed>
|
||||
{
|
||||
public async Task Handle(NewMessageReceived item)
|
||||
public async Task Handle(NewMessageReceived item, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var response = await SayHello(item.Message).ConfigureAwait(false);
|
||||
var evt = new Output
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<!--orleans doesn't have strong name package-->
|
||||
<NoWarn>$(NoWarn);CS8002</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../../src/Microsoft.AutoGen/Core/Microsoft.AutoGen.Core.csproj" />
|
||||
<ProjectReference Include="../../../src/Microsoft.AutoGen/Runtime.Grpc/Microsoft.AutoGen.Runtime.Grpc.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.AutoGen\Extensions\Aspire\Microsoft.AutoGen.Extensions.Aspire.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -1,15 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Program.cs
|
||||
using Microsoft.AutoGen.Runtime.Grpc;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.AddServiceDefaults();
|
||||
builder.AddAgentService();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
app.MapDefaultEndpoints();
|
||||
app.MapAgentService();
|
||||
|
||||
app.Run();
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
<ProjectReference Include="../../../src/Microsoft.AutoGen/Core/Microsoft.AutoGen.Core.csproj" />
|
||||
<ProjectReference Include="../../../src/Microsoft.AutoGen/Agents/Microsoft.AutoGen.Agents.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.AutoGen\Extensions\Aspire\Microsoft.AutoGen.Extensions.Aspire.csproj" />
|
||||
<ProjectReference Include="..\DevTeam.Shared\DevTeam.Shared.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.AutoGen\Extensions\SemanticKernel\Microsoft.AutoGen.Extensions.SemanticKernel.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -1,63 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Developer.cs
|
||||
|
||||
using DevTeam.Shared;
|
||||
using Microsoft.AutoGen.Agents;
|
||||
using Microsoft.AutoGen.Contracts;
|
||||
using Microsoft.AutoGen.Core;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Memory;
|
||||
|
||||
namespace DevTeam.Agents;
|
||||
|
||||
[TopicSubscription("devteam")]
|
||||
public class Dev(IAgentWorker worker, Kernel kernel, ISemanticTextMemory memory, [FromKeyedServices("EventTypes")] EventTypes typeRegistry, ILogger<Dev> logger)
|
||||
: SKAiAgent<DeveloperState>(worker, memory, kernel, typeRegistry), IDevelopApps,
|
||||
IHandle<CodeGenerationRequested>,
|
||||
IHandle<CodeChainClosed>
|
||||
{
|
||||
public async Task Handle(CodeGenerationRequested item)
|
||||
{
|
||||
var code = await GenerateCode(item.Ask);
|
||||
var evt = new CodeGenerated
|
||||
{
|
||||
Org = item.Org,
|
||||
Repo = item.Repo,
|
||||
IssueNumber = item.IssueNumber,
|
||||
Code = code
|
||||
};
|
||||
await PublishMessageAsync(evt);
|
||||
}
|
||||
|
||||
public async Task Handle(CodeChainClosed item)
|
||||
{
|
||||
//TODO: Get code from state
|
||||
var lastCode = ""; // _state.State.History.Last().Message
|
||||
var evt = new CodeCreated
|
||||
{
|
||||
Code = lastCode
|
||||
};
|
||||
await PublishMessageAsync(evt);
|
||||
}
|
||||
|
||||
public async Task<string> GenerateCode(string ask)
|
||||
{
|
||||
try
|
||||
{
|
||||
var context = new KernelArguments { ["input"] = AppendChatHistory(ask) };
|
||||
var instruction = "Consider the following architectural guidelines:!waf!";
|
||||
var enhancedContext = await AddKnowledge(instruction, "waf", context);
|
||||
return await CallFunction(DeveloperSkills.Implement, enhancedContext);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error generating code");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface IDevelopApps
|
||||
{
|
||||
public Task<string> GenerateCode(string ask);
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// DeveloperLead.cs
|
||||
|
||||
using DevTeam.Shared;
|
||||
using Microsoft.AutoGen.Agents;
|
||||
using Microsoft.AutoGen.Contracts;
|
||||
using Microsoft.AutoGen.Core;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
using Microsoft.SemanticKernel.Memory;
|
||||
|
||||
namespace DevTeam.Agents;
|
||||
|
||||
[TopicSubscription("devteam")]
|
||||
public class DeveloperLead(IAgentWorker worker, Kernel kernel, ISemanticTextMemory memory, [FromKeyedServices("EventTypes")] EventTypes typeRegistry, ILogger<DeveloperLead> logger)
|
||||
: SKAiAgent<DeveloperLeadState>(worker, memory, kernel, typeRegistry), ILeadDevelopers,
|
||||
IHandle<DevPlanRequested>,
|
||||
IHandle<DevPlanChainClosed>
|
||||
{
|
||||
public async Task Handle(DevPlanRequested item)
|
||||
{
|
||||
var plan = await CreatePlan(item.Ask);
|
||||
var evt = new DevPlanGenerated
|
||||
{
|
||||
Org = item.Org,
|
||||
Repo = item.Repo,
|
||||
IssueNumber = item.IssueNumber,
|
||||
Plan = plan
|
||||
};
|
||||
await PublishMessageAsync(evt);
|
||||
}
|
||||
|
||||
public async Task Handle(DevPlanChainClosed item)
|
||||
{
|
||||
// TODO: Get plan from state
|
||||
var lastPlan = ""; // _state.State.History.Last().Message
|
||||
var evt = new DevPlanCreated
|
||||
{
|
||||
Plan = lastPlan
|
||||
};
|
||||
await PublishMessageAsync(evt);
|
||||
}
|
||||
public async Task<string> CreatePlan(string ask)
|
||||
{
|
||||
try
|
||||
{
|
||||
var context = new KernelArguments { ["input"] = AppendChatHistory(ask) };
|
||||
var instruction = "Consider the following architectural guidelines:!waf!";
|
||||
var enhancedContext = await AddKnowledge(instruction, "waf", context);
|
||||
var settings = new OpenAIPromptExecutionSettings
|
||||
{
|
||||
ResponseFormat = "json_object",
|
||||
MaxTokens = 4096,
|
||||
Temperature = 0.8,
|
||||
TopP = 1
|
||||
};
|
||||
return await CallFunction(DevLeadSkills.Plan, enhancedContext, settings);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error creating development plan");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface ILeadDevelopers
|
||||
{
|
||||
public Task<string> CreatePlan(string ask);
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// ProductManager.cs
|
||||
|
||||
using DevTeam.Shared;
|
||||
using Microsoft.AutoGen.Agents;
|
||||
using Microsoft.AutoGen.Contracts;
|
||||
using Microsoft.AutoGen.Core;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Memory;
|
||||
|
||||
namespace DevTeam.Agents;
|
||||
|
||||
[TopicSubscription("devteam")]
|
||||
public class ProductManager(IAgentWorker worker, Kernel kernel, ISemanticTextMemory memory, [FromKeyedServices("EventTypes")] EventTypes typeRegistry, ILogger<ProductManager> logger)
|
||||
: SKAiAgent<ProductManagerState>(worker, memory, kernel, typeRegistry), IManageProducts,
|
||||
IHandle<ReadmeChainClosed>,
|
||||
IHandle<ReadmeRequested>
|
||||
{
|
||||
public async Task Handle(ReadmeChainClosed item)
|
||||
{
|
||||
// TODO: Get readme from state
|
||||
var lastReadme = ""; // _state.State.History.Last().Message
|
||||
var evt = new ReadmeCreated
|
||||
{
|
||||
Readme = lastReadme
|
||||
};
|
||||
await PublishMessageAsync(evt);
|
||||
}
|
||||
|
||||
public async Task Handle(ReadmeRequested item)
|
||||
{
|
||||
var readme = await CreateReadme(item.Ask);
|
||||
var evt = new ReadmeGenerated
|
||||
{
|
||||
Readme = readme,
|
||||
Org = item.Org,
|
||||
Repo = item.Repo,
|
||||
IssueNumber = item.IssueNumber
|
||||
};
|
||||
await PublishMessageAsync(evt);
|
||||
}
|
||||
|
||||
public async Task<string> CreateReadme(string ask)
|
||||
{
|
||||
try
|
||||
{
|
||||
var context = new KernelArguments { ["input"] = AppendChatHistory(ask) };
|
||||
var instruction = "Consider the following architectural guidelines:!waf!";
|
||||
var enhancedContext = await AddKnowledge(instruction, "waf", context);
|
||||
return await CallFunction(PMSkills.Readme, enhancedContext);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error creating readme");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface IManageProducts
|
||||
{
|
||||
public Task<string> CreateReadme(string ask);
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Program.cs
|
||||
|
||||
using DevTeam.Agents;
|
||||
using Microsoft.AutoGen.Core;
|
||||
using Microsoft.AutoGen.Extensions.SemanticKernel;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.AddServiceDefaults();
|
||||
|
||||
builder.ConfigureSemanticKernel();
|
||||
|
||||
builder.AddAgentWorker(builder.Configuration["AGENT_HOST"]!)
|
||||
.AddAgent<Dev>(nameof(Dev))
|
||||
.AddAgent<ProductManager>(nameof(ProductManager))
|
||||
.AddAgent<DeveloperLead>(nameof(DeveloperLead));
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
app.MapDefaultEndpoints();
|
||||
|
||||
app.Run();
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"profiles": {
|
||||
"DevTeam.Agents": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "https://localhost:50669;http://localhost:50671"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,8 +21,6 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DevTeam.Backend\DevTeam.Backend.csproj" />
|
||||
<ProjectReference Include="..\DevTeam.AgentHost\DevTeam.AgentHost.csproj" />
|
||||
<ProjectReference Include="..\DevTeam.Agents\DevTeam.Agents.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -7,22 +7,16 @@ builder.AddAzureProvisioning();
|
|||
|
||||
var qdrant = builder.AddQdrant("qdrant");
|
||||
|
||||
var orleans = builder.AddOrleans("orleans")
|
||||
.WithDevelopmentClustering();
|
||||
var agentHost = builder.AddContainer("agent-host", "autogen-host")
|
||||
.WithEnvironment("ASPNETCORE_URLS", "https://+;http://+")
|
||||
.WithEnvironment("ASPNETCORE_HTTPS_PORTS", "5001")
|
||||
.WithEnvironment("ASPNETCORE_Kestrel__Certificates__Default__Password", "mysecurepass")
|
||||
.WithEnvironment("ASPNETCORE_Kestrel__Certificates__Default__Path", "/https/devcert.pfx")
|
||||
.WithBindMount("./certs", "/https/", true)
|
||||
.WithHttpsEndpoint(targetPort: 5001);
|
||||
|
||||
var agentHost = builder.AddProject<Projects.DevTeam_AgentHost>("agenthost")
|
||||
.WithReference(orleans);
|
||||
var agentHostHttps = agentHost.GetEndpoint("https");
|
||||
|
||||
//TODO: pass the right variables - aca environment
|
||||
// var environmentId = builder.AddParameter("environmentId");
|
||||
// var acaSessions = builder.AddBicepTemplateString(
|
||||
// name: "aca-sessions",
|
||||
// bicepContent: BicepTemplates.Sessions
|
||||
// )
|
||||
// .WithParameter("environmentId", environmentId);
|
||||
// var acaSessionsEndpoint = acaSessions.GetOutput("endpoint");
|
||||
|
||||
builder.AddProject<Projects.DevTeam_Backend>("backend")
|
||||
.WithEnvironment("AGENT_HOST", $"{agentHostHttps.Property(EndpointProperty.Url)}")
|
||||
.WithEnvironment("Qdrant__Endpoint", $"{qdrant.Resource.HttpEndpoint.Property(EndpointProperty.Url)}")
|
||||
|
@ -33,16 +27,10 @@ builder.AddProject<Projects.DevTeam_Backend>("backend")
|
|||
.WithEnvironment("Github__AppId", builder.Configuration["Github:AppId"])
|
||||
.WithEnvironment("Github__InstallationId", builder.Configuration["Github:InstallationId"])
|
||||
.WithEnvironment("Github__WebhookSecret", builder.Configuration["Github:WebhookSecret"])
|
||||
.WithEnvironment("Github__AppKey", builder.Configuration["Github:AppKey"]);
|
||||
.WithEnvironment("Github__AppKey", builder.Configuration["Github:AppKey"])
|
||||
.WaitFor(agentHost)
|
||||
.WaitFor(qdrant);
|
||||
//TODO: add this to the config in backend
|
||||
//.WithEnvironment("", acaSessionsEndpoint);
|
||||
|
||||
builder.AddProject<Projects.DevTeam_Agents>("dev-agents")
|
||||
.WithEnvironment("AGENT_HOST", $"{agentHostHttps.Property(EndpointProperty.Url)}")
|
||||
.WithEnvironment("Qdrant__Endpoint", $"{qdrant.Resource.HttpEndpoint.Property(EndpointProperty.Url)}")
|
||||
.WithEnvironment("Qdrant__ApiKey", $"{qdrant.Resource.ApiKeyParameter.Value}")
|
||||
.WithEnvironment("Qdrant__VectorSize", "1536")
|
||||
.WithEnvironment("OpenAI__Key", builder.Configuration["OpenAI:Key"])
|
||||
.WithEnvironment("OpenAI__Endpoint", builder.Configuration["OpenAI:Endpoint"]);
|
||||
|
||||
builder.Build().Run();
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:17034;http://localhost:15043",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||
"DOTNET_ENVIRONMENT": "Development",
|
||||
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21249",
|
||||
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22030"
|
||||
}
|
||||
},
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "http://localhost:15043",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||
"DOTNET_ENVIRONMENT": "Development",
|
||||
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19105",
|
||||
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20096"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,21 +1,17 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// AzureGenie.cs
|
||||
|
||||
using DevTeam.Backend;
|
||||
using DevTeam.Shared;
|
||||
using Microsoft.AutoGen.Agents;
|
||||
using DevTeam.Backend.Services;
|
||||
using Microsoft.AutoGen.Core;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Memory;
|
||||
namespace Microsoft.AI.DevTeam;
|
||||
namespace DevTeam.Backend.Agents;
|
||||
|
||||
public class AzureGenie(IAgentWorker worker, Kernel kernel, ISemanticTextMemory memory, [FromKeyedServices("EventTypes")] EventTypes typeRegistry, IManageAzure azureService)
|
||||
: SKAiAgent<object>(worker, memory, kernel, typeRegistry),
|
||||
[TopicSubscription(Consts.TopicName)]
|
||||
public class AzureGenie([FromKeyedServices("AgentsMetadata")] AgentsMetadata typeRegistry, IManageAzure azureService)
|
||||
: Agent(typeRegistry),
|
||||
IHandle<ReadmeCreated>,
|
||||
IHandle<CodeCreated>
|
||||
|
||||
{
|
||||
public async Task Handle(ReadmeCreated item)
|
||||
public async Task Handle(ReadmeCreated item, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// TODO: Not sure we need to store the files if we use ACA Sessions
|
||||
// //var data = item.ToData();
|
||||
|
@ -30,7 +26,7 @@ public class AzureGenie(IAgentWorker worker, Kernel kernel, ISemanticTextMemory
|
|||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task Handle(CodeCreated item)
|
||||
public async Task Handle(CodeCreated item, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// TODO: Not sure we need to store the files if we use ACA Sessions
|
||||
// //var data = item.ToData();
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Developer.cs
|
||||
|
||||
using DevTeam.Agents;
|
||||
using Microsoft.AutoGen.Core;
|
||||
|
||||
namespace DevTeam.Backend.Agents.Developer;
|
||||
|
||||
[TopicSubscription(Consts.TopicName)]
|
||||
public class Dev([FromKeyedServices("AgentsMetadata")] AgentsMetadata typeRegistry, ILogger<Dev> logger)
|
||||
: AiAgent<DeveloperState>(typeRegistry, logger), IDevelopApps,
|
||||
IHandle<CodeGenerationRequested>,
|
||||
IHandle<CodeChainClosed>
|
||||
{
|
||||
public async Task Handle(CodeGenerationRequested item, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var code = await GenerateCode(item.Ask);
|
||||
var evt = new CodeGenerated
|
||||
{
|
||||
Org = item.Org,
|
||||
Repo = item.Repo,
|
||||
IssueNumber = item.IssueNumber,
|
||||
Code = code
|
||||
};
|
||||
// TODO: Read the Topic from the agent metadata
|
||||
await PublishMessageAsync(evt, topic: Consts.TopicName).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task Handle(CodeChainClosed item, CancellationToken cancellationToken = default)
|
||||
{
|
||||
//TODO: Get code from state
|
||||
var lastCode = ""; // _state.State.History.Last().Message
|
||||
var evt = new CodeCreated
|
||||
{
|
||||
Code = lastCode
|
||||
};
|
||||
await PublishMessageAsync(evt, topic: Consts.TopicName).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<string> GenerateCode(string ask)
|
||||
{
|
||||
try
|
||||
{
|
||||
//var context = new KernelArguments { ["input"] = AppendChatHistory(ask) };
|
||||
//var instruction = "Consider the following architectural guidelines:!waf!";
|
||||
//var enhancedContext = await AddKnowledge(instruction, "waf");
|
||||
return await CallFunction(DeveloperSkills.Implement);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error generating code");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface IDevelopApps
|
||||
{
|
||||
public Task<string> GenerateCode(string ask);
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// DeveloperPrompts.cs
|
||||
|
||||
namespace DevTeam.Agents;
|
||||
namespace DevTeam.Backend.Agents.Developer;
|
||||
public static class DeveloperSkills
|
||||
{
|
||||
public const string Implement = """
|
|
@ -0,0 +1,65 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// DeveloperLead.cs
|
||||
|
||||
using DevTeam.Agents;
|
||||
using Microsoft.AutoGen.Core;
|
||||
|
||||
namespace DevTeam.Backend.Agents.DeveloperLead;
|
||||
|
||||
[TopicSubscription(Consts.TopicName)]
|
||||
public class DeveloperLead([FromKeyedServices("AgentsMetadata")] AgentsMetadata typeRegistry, ILogger<DeveloperLead> logger)
|
||||
: AiAgent<DeveloperLeadState>(typeRegistry, logger), ILeadDevelopers,
|
||||
IHandle<DevPlanRequested>,
|
||||
IHandle<DevPlanChainClosed>
|
||||
{
|
||||
public async Task Handle(DevPlanRequested item, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var plan = await CreatePlan(item.Ask);
|
||||
var evt = new DevPlanGenerated
|
||||
{
|
||||
Org = item.Org,
|
||||
Repo = item.Repo,
|
||||
IssueNumber = item.IssueNumber,
|
||||
Plan = plan
|
||||
};
|
||||
await PublishMessageAsync(evt, topic: Consts.TopicName).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task Handle(DevPlanChainClosed item, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// TODO: Get plan from state
|
||||
var lastPlan = ""; // _state.State.History.Last().Message
|
||||
var evt = new DevPlanCreated
|
||||
{
|
||||
Plan = lastPlan
|
||||
};
|
||||
await PublishMessageAsync(evt, topic: Consts.TopicName).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<string> CreatePlan(string ask)
|
||||
{
|
||||
try
|
||||
{
|
||||
//var context = new KernelArguments { ["input"] = AppendChatHistory(ask) };
|
||||
//var instruction = "Consider the following architectural guidelines:!waf!";
|
||||
//var enhancedContext = await AddKnowledge(instruction, "waf", context);
|
||||
//var settings = new OpenAIPromptExecutionSettings
|
||||
//{
|
||||
// ResponseFormat = "json_object",
|
||||
// MaxTokens = 4096,
|
||||
// Temperature = 0.8,
|
||||
// TopP = 1
|
||||
//};
|
||||
return await CallFunction(DevLeadSkills.Plan);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error creating development plan");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface ILeadDevelopers
|
||||
{
|
||||
public Task<string> CreatePlan(string ask);
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// DeveloperLeadPrompts.cs
|
||||
|
||||
namespace DevTeam.Agents;
|
||||
namespace DevTeam.Backend.Agents.DeveloperLead;
|
||||
public static class DevLeadSkills
|
||||
{
|
||||
public const string Plan = """
|
|
@ -2,18 +2,14 @@
|
|||
// Hubber.cs
|
||||
|
||||
using System.Text.Json;
|
||||
using DevTeam;
|
||||
using DevTeam.Backend;
|
||||
using DevTeam.Shared;
|
||||
using Microsoft.AutoGen.Agents;
|
||||
using DevTeam.Backend.Services;
|
||||
using Microsoft.AutoGen.Core;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Memory;
|
||||
|
||||
namespace Microsoft.AI.DevTeam;
|
||||
namespace DevTeam.Backend.Agents;
|
||||
|
||||
public class Hubber(IAgentWorker worker, Kernel kernel, ISemanticTextMemory memory, [FromKeyedServices("EventTypes")] EventTypes typeRegistry, IManageGithub ghService)
|
||||
: SKAiAgent<object>(worker, memory, kernel, typeRegistry),
|
||||
[TopicSubscription(Consts.TopicName)]
|
||||
public class Hubber([FromKeyedServices("AgentsMetadata")] AgentsMetadata typeRegistry, IManageGithub ghService)
|
||||
: Agent(typeRegistry),
|
||||
IHandle<NewAsk>,
|
||||
IHandle<ReadmeGenerated>,
|
||||
IHandle<DevPlanGenerated>,
|
||||
|
@ -21,7 +17,7 @@ public class Hubber(IAgentWorker worker, Kernel kernel, ISemanticTextMemory memo
|
|||
IHandle<ReadmeStored>,
|
||||
IHandle<CodeGenerated>
|
||||
{
|
||||
public async Task Handle(NewAsk item)
|
||||
public async Task Handle(NewAsk item, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var pmIssue = await CreateIssue(item.Org, item.Repo, item.Ask, "PM.Readme", item.IssueNumber);
|
||||
var devLeadIssue = await CreateIssue(item.Org, item.Repo, item.Ask, "DevLead.Plan", item.IssueNumber);
|
||||
|
@ -30,25 +26,25 @@ public class Hubber(IAgentWorker worker, Kernel kernel, ISemanticTextMemory memo
|
|||
await CreateBranch(item.Org, item.Repo, $"sk-{item.IssueNumber}");
|
||||
}
|
||||
|
||||
public async Task Handle(ReadmeGenerated item)
|
||||
public async Task Handle(ReadmeGenerated item, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var contents = string.IsNullOrEmpty(item.Readme) ? "Sorry, I got tired, can you try again please? " : item.Readme;
|
||||
await PostComment(item.Org, item.Repo, item.IssueNumber, contents);
|
||||
}
|
||||
|
||||
public async Task Handle(DevPlanGenerated item)
|
||||
public async Task Handle(DevPlanGenerated item, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var contents = string.IsNullOrEmpty(item.Plan) ? "Sorry, I got tired, can you try again please? " : item.Plan;
|
||||
await PostComment(item.Org, item.Repo, item.IssueNumber, contents);
|
||||
}
|
||||
|
||||
public async Task Handle(CodeGenerated item)
|
||||
public async Task Handle(CodeGenerated item, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var contents = string.IsNullOrEmpty(item.Code) ? "Sorry, I got tired, can you try again please? " : item.Code;
|
||||
await PostComment(item.Org, item.Repo, item.IssueNumber, contents);
|
||||
}
|
||||
|
||||
public async Task Handle(DevPlanCreated item)
|
||||
public async Task Handle(DevPlanCreated item, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var plan = JsonSerializer.Deserialize<DevLeadPlan>(item.Plan);
|
||||
var prompts = plan!.Steps.SelectMany(s => s.Subtasks!.Select(st => st.Prompt));
|
||||
|
@ -62,7 +58,7 @@ public class Hubber(IAgentWorker worker, Kernel kernel, ISemanticTextMemory memo
|
|||
}
|
||||
}
|
||||
|
||||
public async Task Handle(ReadmeStored item)
|
||||
public async Task Handle(ReadmeStored item, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var branch = $"sk-{item.ParentNumber}";
|
||||
await CommitToBranch(item.Org, item.Repo, item.ParentNumber, item.IssueNumber, "output", branch);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// PMPrompts.cs
|
||||
|
||||
namespace DevTeam.Agents;
|
||||
namespace DevTeam.Backend.Agents.ProductManager;
|
||||
public static class PMSkills
|
||||
{
|
||||
public const string BootstrapProject = """
|
|
@ -0,0 +1,59 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// ProductManager.cs
|
||||
|
||||
using DevTeam.Agents;
|
||||
using Microsoft.AutoGen.Core;
|
||||
|
||||
namespace DevTeam.Backend.Agents.ProductManager;
|
||||
|
||||
[TopicSubscription(Consts.TopicName)]
|
||||
public class ProductManager([FromKeyedServices("AgentsMetadata")] AgentsMetadata typeRegistry, ILogger<ProductManager> logger)
|
||||
: AiAgent<ProductManagerState>(typeRegistry, logger), IManageProducts,
|
||||
IHandle<ReadmeChainClosed>,
|
||||
IHandle<ReadmeRequested>
|
||||
{
|
||||
public async Task Handle(ReadmeChainClosed item, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// TODO: Get readme from state
|
||||
var lastReadme = ""; // _state.State.History.Last().Message
|
||||
var evt = new ReadmeCreated
|
||||
{
|
||||
Readme = lastReadme
|
||||
};
|
||||
await PublishMessageAsync(evt, topic: Consts.TopicName).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task Handle(ReadmeRequested item, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var readme = await CreateReadme(item.Ask);
|
||||
var evt = new ReadmeGenerated
|
||||
{
|
||||
Readme = readme,
|
||||
Org = item.Org,
|
||||
Repo = item.Repo,
|
||||
IssueNumber = item.IssueNumber
|
||||
};
|
||||
await PublishMessageAsync(evt, topic: Consts.TopicName).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<string> CreateReadme(string ask)
|
||||
{
|
||||
try
|
||||
{
|
||||
//var context = new KernelArguments { ["input"] = AppendChatHistory(ask) };
|
||||
//var instruction = "Consider the following architectural guidelines:!waf!";
|
||||
//var enhancedContext = await AddKnowledge(instruction, "waf", context);
|
||||
return await CallFunction(PMSkills.Readme);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error creating readme");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface IManageProducts
|
||||
{
|
||||
public Task<string> CreateReadme(string ask);
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
// namespace DevTeam.Backend;
|
||||
|
||||
// public sealed class Sandbox : Agent
|
||||
// public sealed class Sandbox : AgentBase
|
||||
// {
|
||||
// private const string ReminderName = "SandboxRunReminder";
|
||||
// private readonly IManageAzure _azService;
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// AiAgent.cs
|
||||
|
||||
using Microsoft.AutoGen.Core;
|
||||
|
||||
namespace DevTeam.Agents;
|
||||
|
||||
public class AiAgent<T> : Agent
|
||||
{
|
||||
public AiAgent(AgentsMetadata eventTypes, ILogger<AiAgent<T>> logger) : base(eventTypes, logger)
|
||||
{
|
||||
}
|
||||
|
||||
protected async Task AddKnowledge(string instruction, string v)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected async Task<string> CallFunction(string prompt)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Consts.cs
|
||||
|
||||
namespace DevTeam.Backend;
|
||||
|
||||
public class Consts
|
||||
{
|
||||
public const string TopicName = "devteam";
|
||||
}
|
|
@ -1,8 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../../src/Microsoft.AutoGen/Core/Microsoft.AutoGen.Core.csproj" />
|
||||
</ItemGroup>
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
|
@ -12,11 +8,9 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI" />
|
||||
<PackageReference Include="Octokit.Webhooks.AspNetCore" />
|
||||
<PackageReference Include="Octokit" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Connectors.Qdrant" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Plugins.Memory" />
|
||||
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" />
|
||||
<PackageReference Include="Microsoft.Extensions.Azure" />
|
||||
|
@ -26,13 +20,19 @@
|
|||
<PackageReference Include="Azure.Data.Tables" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" />
|
||||
<PackageReference Include="Google.Protobuf" />
|
||||
<PackageReference Include="Grpc.Tools" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../../src/Microsoft.AutoGen/Agents/Microsoft.AutoGen.Agents.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.AutoGen\Extensions\Aspire\Microsoft.AutoGen.Extensions.Aspire.csproj" />
|
||||
<ProjectReference Include="..\DevTeam.Shared\DevTeam.Shared.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.AutoGen\Extensions\SemanticKernel\Microsoft.AutoGen.Extensions.SemanticKernel.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.AutoGen\Core.Grpc\Microsoft.AutoGen.Core.Grpc.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.AutoGen\Core\Microsoft.AutoGen.Core.csproj" />
|
||||
<ProjectReference Include="..\DevTeam.ServiceDefaults\DevTeam.ServiceDefaults.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Protobuf Include="..\Protos\messages.proto" Link="Protos\messages.proto" />
|
||||
<Protobuf Include="..\Protos\states.proto" Link="Protos\states.proto" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -2,11 +2,14 @@
|
|||
// Program.cs
|
||||
|
||||
using Azure.Identity;
|
||||
using DevTeam.Backend;
|
||||
using DevTeam.Backend.Agents;
|
||||
using DevTeam.Backend.Agents.Developer;
|
||||
using DevTeam.Backend.Agents.DeveloperLead;
|
||||
using DevTeam.Backend.Agents.ProductManager;
|
||||
using DevTeam.Backend.Services;
|
||||
using DevTeam.Options;
|
||||
using Microsoft.AI.DevTeam;
|
||||
using Microsoft.AutoGen.Core;
|
||||
using Microsoft.AutoGen.Extensions.SemanticKernel;
|
||||
using Microsoft.AutoGen.Core.Grpc;
|
||||
using Microsoft.Extensions.Azure;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Octokit.Webhooks;
|
||||
|
@ -15,16 +18,19 @@ using Octokit.Webhooks.AspNetCore;
|
|||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.AddServiceDefaults();
|
||||
builder.ConfigureSemanticKernel();
|
||||
|
||||
builder.Services.AddHttpClient();
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddSwaggerGen();
|
||||
|
||||
builder.AddAgentWorker(builder.Configuration["AGENT_HOST"]!)
|
||||
builder.AddGrpcAgentWorker(builder.Configuration["AGENT_HOST"]!)
|
||||
.AddAgentWorker()
|
||||
.AddAgent<AzureGenie>(nameof(AzureGenie))
|
||||
//.AddAgent<Sandbox>(nameof(Sandbox))
|
||||
.AddAgent<Hubber>(nameof(Hubber));
|
||||
.AddAgent<Hubber>(nameof(Hubber))
|
||||
.AddAgent<Dev>(nameof(Dev))
|
||||
.AddAgent<ProductManager>(nameof(ProductManager))
|
||||
.AddAgent<DeveloperLead>(nameof(DeveloperLead));
|
||||
|
||||
builder.Services.AddSingleton<AgentWorker>();
|
||||
builder.Services.AddSingleton<WebhookEventProcessor, GithubWebHookProcessor>();
|
||||
|
@ -58,7 +64,7 @@ builder.Services.AddAzureClients(clientBuilder =>
|
|||
|
||||
var app = builder.Build();
|
||||
|
||||
app.MapDefaultEndpoints();
|
||||
Microsoft.Extensions.Hosting.AspireHostingExtensions.MapDefaultEndpoints(app);
|
||||
app.UseRouting()
|
||||
.UseEndpoints(endpoints =>
|
||||
{
|
||||
|
|
|
@ -12,7 +12,7 @@ using Azure.Storage.Files.Shares;
|
|||
using DevTeam.Options;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace DevTeam.Backend;
|
||||
namespace DevTeam.Backend.Services;
|
||||
|
||||
public class AzureService : IManageAzure
|
||||
{
|
||||
|
|
|
@ -9,7 +9,7 @@ using Microsoft.Extensions.Options;
|
|||
using Microsoft.IdentityModel.Tokens;
|
||||
using Octokit;
|
||||
|
||||
namespace DevTeam.Backend;
|
||||
namespace DevTeam.Backend.Services;
|
||||
public class GithubAuthService
|
||||
{
|
||||
private readonly GithubOptions _githubSettings;
|
||||
|
|
|
@ -8,7 +8,7 @@ using Microsoft.Extensions.Options;
|
|||
using Octokit;
|
||||
using Octokit.Helpers;
|
||||
|
||||
namespace DevTeam.Backend;
|
||||
namespace DevTeam.Backend.Services;
|
||||
|
||||
public class GithubService : IManageGithub
|
||||
{
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// GithubWebHookProcessor.cs
|
||||
|
||||
using System.Globalization;
|
||||
using DevTeam.Shared;
|
||||
using Google.Protobuf;
|
||||
using Microsoft.AutoGen.Contracts;
|
||||
using Microsoft.AutoGen.Core;
|
||||
using Octokit.Webhooks;
|
||||
|
@ -11,12 +11,12 @@ using Octokit.Webhooks.Events.IssueComment;
|
|||
using Octokit.Webhooks.Events.Issues;
|
||||
using Octokit.Webhooks.Models;
|
||||
|
||||
namespace DevTeam.Backend;
|
||||
namespace DevTeam.Backend.Services;
|
||||
|
||||
public sealed class GithubWebHookProcessor(ILogger<GithubWebHookProcessor> logger, AgentWorker client) : WebhookEventProcessor
|
||||
public sealed class GithubWebHookProcessor(ILogger<GithubWebHookProcessor> logger, Client client) : WebhookEventProcessor
|
||||
{
|
||||
private readonly ILogger<GithubWebHookProcessor> _logger = logger;
|
||||
private readonly AgentWorker _client = client;
|
||||
private readonly Client _client = client;
|
||||
|
||||
protected override async Task ProcessIssuesWebhookAsync(WebhookHeaders headers, IssuesEvent issuesEvent, IssuesAction action)
|
||||
{
|
||||
|
@ -43,7 +43,7 @@ public sealed class GithubWebHookProcessor(ILogger<GithubWebHookProcessor> logge
|
|||
return;
|
||||
}
|
||||
|
||||
long? parentNumber = labels.TryGetValue("Parent", out string? value) ? long.Parse(value) : null;
|
||||
long? parentNumber = labels.TryGetValue("Parent", out var value) ? long.Parse(value) : null;
|
||||
var skillName = labels.Keys.Where(k => k != "Parent").FirstOrDefault();
|
||||
|
||||
if (skillName == null)
|
||||
|
@ -114,15 +114,15 @@ public sealed class GithubWebHookProcessor(ILogger<GithubWebHookProcessor> logge
|
|||
{
|
||||
var subject = suffix + issueNumber.ToString();
|
||||
|
||||
var evt = (skillName, functionName) switch
|
||||
IMessage evt = (skillName, functionName) switch
|
||||
{
|
||||
("PM", "Readme") => new ReadmeChainClosed { }.ToCloudEvent(subject),
|
||||
("DevLead", "Plan") => new DevPlanChainClosed { }.ToCloudEvent(subject),
|
||||
("Developer", "Implement") => new CodeChainClosed { }.ToCloudEvent(subject),
|
||||
("PM", "Readme") => new ReadmeChainClosed { },
|
||||
("DevLead", "Plan") => new DevPlanChainClosed { },
|
||||
("Developer", "Implement") => new CodeChainClosed { },
|
||||
_ => new CloudEvent() // TODO: default event
|
||||
};
|
||||
|
||||
await _client.PublishEventAsync(evt);
|
||||
await _client.PublishMessageAsync(evt, Consts.TopicName, subject);
|
||||
}
|
||||
|
||||
private async Task HandleNewAsk(long issueNumber, string skillName, string functionName, string suffix, string input, string org, string repo)
|
||||
|
@ -132,15 +132,15 @@ public sealed class GithubWebHookProcessor(ILogger<GithubWebHookProcessor> logge
|
|||
_logger.LogInformation("Handling new ask");
|
||||
var subject = suffix + issueNumber.ToString();
|
||||
|
||||
var evt = (skillName, functionName) switch
|
||||
IMessage evt = (skillName, functionName) switch
|
||||
{
|
||||
("Do", "It") => new NewAsk { Ask = input, IssueNumber = issueNumber, Org = org, Repo = repo }.ToCloudEvent(subject),
|
||||
("PM", "Readme") => new ReadmeRequested { Ask = input, IssueNumber = issueNumber, Org = org, Repo = repo }.ToCloudEvent(subject),
|
||||
("DevLead", "Plan") => new DevPlanRequested { Ask = input, IssueNumber = issueNumber, Org = org, Repo = repo }.ToCloudEvent(subject),
|
||||
("Developer", "Implement") => new CodeGenerationRequested { Ask = input, IssueNumber = issueNumber, Org = org, Repo = repo }.ToCloudEvent(subject),
|
||||
("Do", "It") => new NewAsk { Ask = input, IssueNumber = issueNumber, Org = org, Repo = repo },
|
||||
("PM", "Readme") => new ReadmeRequested { Ask = input, IssueNumber = issueNumber, Org = org, Repo = repo },
|
||||
("DevLead", "Plan") => new DevPlanRequested { Ask = input, IssueNumber = issueNumber, Org = org, Repo = repo },
|
||||
("Developer", "Implement") => new CodeGenerationRequested { Ask = input, IssueNumber = issueNumber, Org = org, Repo = repo },
|
||||
_ => new CloudEvent()
|
||||
};
|
||||
await _client.PublishEventAsync(evt);
|
||||
await _client.PublishMessageAsync(evt, Consts.TopicName, subject);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
<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" />
|
||||
<PackageReference Include="OpenTelemetry.Extensions.Hosting" />
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" />
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.Http" />
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,120 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Extensions.cs
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OpenTelemetry;
|
||||
using OpenTelemetry.Metrics;
|
||||
using OpenTelemetry.Trace;
|
||||
|
||||
namespace Microsoft.Extensions.Hosting;
|
||||
// Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry.
|
||||
// This project should be referenced by each service project in your solution.
|
||||
// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults
|
||||
public static class Extensions
|
||||
{
|
||||
public static TBuilder AddServiceDefaults<TBuilder>(this TBuilder builder) where TBuilder : IHostApplicationBuilder
|
||||
{
|
||||
builder.ConfigureOpenTelemetry();
|
||||
|
||||
builder.AddDefaultHealthChecks();
|
||||
|
||||
builder.Services.AddServiceDiscovery();
|
||||
|
||||
builder.Services.ConfigureHttpClientDefaults(http =>
|
||||
{
|
||||
// Turn on resilience by default
|
||||
http.AddStandardResilienceHandler();
|
||||
|
||||
// Turn on service discovery by default
|
||||
http.AddServiceDiscovery();
|
||||
});
|
||||
|
||||
// Uncomment the following to restrict the allowed schemes for service discovery.
|
||||
// builder.Services.Configure<ServiceDiscoveryOptions>(options =>
|
||||
// {
|
||||
// options.AllowedSchemes = ["https"];
|
||||
// });
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static TBuilder ConfigureOpenTelemetry<TBuilder>(this TBuilder builder) where TBuilder : IHostApplicationBuilder
|
||||
{
|
||||
builder.Logging.AddOpenTelemetry(logging =>
|
||||
{
|
||||
logging.IncludeFormattedMessage = true;
|
||||
logging.IncludeScopes = true;
|
||||
});
|
||||
|
||||
builder.Services.AddOpenTelemetry()
|
||||
.WithMetrics(metrics =>
|
||||
{
|
||||
metrics.AddAspNetCoreInstrumentation()
|
||||
.AddHttpClientInstrumentation()
|
||||
.AddRuntimeInstrumentation();
|
||||
})
|
||||
.WithTracing(tracing =>
|
||||
{
|
||||
tracing.AddSource(builder.Environment.ApplicationName)
|
||||
.AddAspNetCoreInstrumentation()
|
||||
// Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package)
|
||||
//.AddGrpcClientInstrumentation()
|
||||
.AddHttpClientInstrumentation();
|
||||
});
|
||||
|
||||
builder.AddOpenTelemetryExporters();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static TBuilder AddOpenTelemetryExporters<TBuilder>(this TBuilder builder) where TBuilder : IHostApplicationBuilder
|
||||
{
|
||||
var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]);
|
||||
|
||||
if (useOtlpExporter)
|
||||
{
|
||||
builder.Services.AddOpenTelemetry().UseOtlpExporter();
|
||||
}
|
||||
|
||||
// Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package)
|
||||
//if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"]))
|
||||
//{
|
||||
// builder.Services.AddOpenTelemetry()
|
||||
// .UseAzureMonitor();
|
||||
//}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static TBuilder AddDefaultHealthChecks<TBuilder>(this TBuilder builder) where TBuilder : IHostApplicationBuilder
|
||||
{
|
||||
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.
|
||||
// See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments.
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
// All health checks must pass for app to be considered ready to accept traffic after starting
|
||||
app.MapHealthChecks("/health");
|
||||
|
||||
// Only health checks tagged with the "live" tag must pass for app to be considered alive
|
||||
app.MapHealthChecks("/alive", new HealthCheckOptions
|
||||
{
|
||||
Predicate = r => r.Tags.Contains("live")
|
||||
});
|
||||
}
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../../src/Microsoft.AutoGen/Core/Microsoft.AutoGen.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Connectors.Qdrant" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Plugins.Memory" />
|
||||
<PackageReference Include="Google.Protobuf" />
|
||||
<PackageReference Include="Grpc.Tools" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Protobuf Include="..\Protos\messages.proto" Link="Protos\messages.proto" />
|
||||
<Protobuf Include="..\Protos\states.proto" Link="Protos\states.proto" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -1,51 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// EventExtensions.cs
|
||||
|
||||
using System.Globalization;
|
||||
using Microsoft.AutoGen.Contracts;
|
||||
|
||||
namespace DevTeam;
|
||||
|
||||
public static class EventExtensions
|
||||
{
|
||||
public static GithubContext ToGithubContext(this Event evt)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(evt);
|
||||
var data = new Dictionary<string, string>();// JsonSerializer.Deserialize<Dictionary<string,string>>(evt.Data);
|
||||
return new GithubContext
|
||||
{
|
||||
Org = data?["org"] ?? "",
|
||||
Repo = data?["repo"] ?? "",
|
||||
IssueNumber = data?.TryParseLong("issueNumber") ?? default,
|
||||
ParentNumber = data?.TryParseLong("parentNumber")
|
||||
};
|
||||
}
|
||||
|
||||
public static Dictionary<string, string> ToData(this Event evt)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(evt);
|
||||
return //JsonSerializer.Deserialize<Dictionary<string,string>>(evt.Data) ??
|
||||
new Dictionary<string, string>();
|
||||
}
|
||||
public static Dictionary<string, string> ToData(this GithubContext context)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
|
||||
return new Dictionary<string, string> {
|
||||
{ "org", context.Org },
|
||||
{ "repo", context.Repo },
|
||||
{ "issueNumber", $"{context.IssueNumber}" },
|
||||
{ "parentNumber", context.ParentNumber?.ToString(CultureInfo.InvariantCulture) ?? "" }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class GithubContext
|
||||
{
|
||||
public required string Org { get; set; }
|
||||
public required string Repo { get; set; }
|
||||
public long IssueNumber { get; set; }
|
||||
public long? ParentNumber { get; set; }
|
||||
|
||||
public string Subject => $"{Org}/{Repo}/{IssueNumber}";
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// ParseExtensions.cs
|
||||
|
||||
namespace DevTeam;
|
||||
|
||||
public static class ParseExtensions
|
||||
{
|
||||
public static long TryParseLong(this Dictionary<string, string> data, string key)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(data);
|
||||
|
||||
if (data.TryGetValue(key, out string? value) && !string.IsNullOrEmpty(value) && long.TryParse(value, out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
return default;
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ syntax = "proto3";
|
|||
|
||||
package devteam;
|
||||
|
||||
option csharp_namespace = "DevTeam.Shared";
|
||||
option csharp_namespace = "DevTeam";
|
||||
|
||||
message NewAsk {
|
||||
string org = 1;
|
||||
|
|
|
@ -2,7 +2,7 @@ syntax = "proto3";
|
|||
|
||||
package devteam;
|
||||
|
||||
option csharp_namespace = "DevTeam.Shared";
|
||||
option csharp_namespace = "DevTeam";
|
||||
|
||||
|
||||
message DeveloperState {
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.11.35327.3
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevTeam.AgentHost", "DevTeam.AgentHost\DevTeam.AgentHost.csproj", "{A6FC8B01-A177-4690-BD16-73EE3D0C06A0}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevTeam.Backend", "DevTeam.Backend\DevTeam.Backend.csproj", "{2D4BAD10-85F3-4E4B-B759-13449A212A96}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevTeam.Agents", "DevTeam.Agents\DevTeam.Agents.csproj", "{A51CE540-72B0-4271-B63D-A30CAB61C227}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevTeam.AppHost", "DevTeam.AppHost\DevTeam.AppHost.csproj", "{2B8A3C64-9F4E-4CC5-9466-AFFD8E676D2E}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevTeam.Shared", "DevTeam.Shared\DevTeam.Shared.csproj", "{557701A5-35D8-4CE3-BA75-D5412B4227F5}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{A6FC8B01-A177-4690-BD16-73EE3D0C06A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A6FC8B01-A177-4690-BD16-73EE3D0C06A0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A6FC8B01-A177-4690-BD16-73EE3D0C06A0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A6FC8B01-A177-4690-BD16-73EE3D0C06A0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2D4BAD10-85F3-4E4B-B759-13449A212A96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2D4BAD10-85F3-4E4B-B759-13449A212A96}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2D4BAD10-85F3-4E4B-B759-13449A212A96}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2D4BAD10-85F3-4E4B-B759-13449A212A96}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A51CE540-72B0-4271-B63D-A30CAB61C227}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A51CE540-72B0-4271-B63D-A30CAB61C227}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A51CE540-72B0-4271-B63D-A30CAB61C227}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A51CE540-72B0-4271-B63D-A30CAB61C227}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2B8A3C64-9F4E-4CC5-9466-AFFD8E676D2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2B8A3C64-9F4E-4CC5-9466-AFFD8E676D2E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2B8A3C64-9F4E-4CC5-9466-AFFD8E676D2E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2B8A3C64-9F4E-4CC5-9466-AFFD8E676D2E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{557701A5-35D8-4CE3-BA75-D5412B4227F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{557701A5-35D8-4CE3-BA75-D5412B4227F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{557701A5-35D8-4CE3-BA75-D5412B4227F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{557701A5-35D8-4CE3-BA75-D5412B4227F5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {DE04DB59-B8CD-4305-875B-E71442345CCF}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -4,7 +4,7 @@
|
|||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:50670;http://localhost:50673",
|
||||
"applicationUrl": "https://localhost:53071;http://localhost:50673",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
|
|
|
@ -2,9 +2,12 @@
|
|||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information",
|
||||
"Microsoft.AspNetCore": "Information",
|
||||
"Microsoft": "Information",
|
||||
"Microsoft.Orleans": "Warning",
|
||||
"Orleans.Runtime": "Error"
|
||||
"Orleans.Runtime": "Error",
|
||||
"Grpc": "Information"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
|
|
|
@ -5,10 +5,9 @@ using Microsoft.AutoGen.Core;
|
|||
using Microsoft.Extensions.AI;
|
||||
namespace Microsoft.AutoGen.Agents;
|
||||
public abstract class InferenceAgent<T>(
|
||||
IAgentWorker worker,
|
||||
EventTypes typeRegistry,
|
||||
AgentsMetadata typeRegistry,
|
||||
IChatClient client)
|
||||
: Agent(worker, typeRegistry)
|
||||
: Agent(typeRegistry)
|
||||
where T : IMessage, new()
|
||||
{
|
||||
protected IChatClient ChatClient { get; } = client;
|
||||
|
|
|
@ -9,11 +9,9 @@ using Microsoft.SemanticKernel.Memory;
|
|||
|
||||
namespace Microsoft.AutoGen.Agents;
|
||||
public abstract class SKAiAgent<T>(
|
||||
IAgentWorker worker,
|
||||
ISemanticTextMemory memory,
|
||||
Kernel kernel,
|
||||
EventTypes typeRegistry) : Agent(
|
||||
worker,
|
||||
AgentsMetadata typeRegistry) : Agent(
|
||||
typeRegistry) where T : class, new()
|
||||
{
|
||||
protected AgentState<T> _state = new();
|
||||
|
|
|
@ -13,11 +13,11 @@ public abstract class ConsoleAgent : IOAgent,
|
|||
{
|
||||
|
||||
// instead of the primary constructor above, make a constructr here that still calls the base constructor
|
||||
public ConsoleAgent(IAgentWorker worker, [FromKeyedServices("EventTypes")] EventTypes typeRegistry) : base(worker, typeRegistry)
|
||||
public ConsoleAgent([FromKeyedServices("AgentsMetadata")] AgentsMetadata typeRegistry) : base(typeRegistry)
|
||||
{
|
||||
_route = "console";
|
||||
}
|
||||
public override async Task Handle(Input item)
|
||||
public override async Task Handle(Input item, CancellationToken cancellationToken)
|
||||
{
|
||||
Console.WriteLine("Please enter input:");
|
||||
string content = Console.ReadLine() ?? string.Empty;
|
||||
|
@ -31,7 +31,7 @@ public abstract class ConsoleAgent : IOAgent,
|
|||
await PublishMessageAsync(evt);
|
||||
}
|
||||
|
||||
public override async Task Handle(Output item)
|
||||
public override async Task Handle(Output item, CancellationToken cancellationToken)
|
||||
{
|
||||
// Assuming item has a property `Content` that we want to write to the console
|
||||
Console.WriteLine(item.Message);
|
||||
|
|
|
@ -10,9 +10,9 @@ namespace Microsoft.AutoGen.Agents;
|
|||
public interface IHandleConsole : IHandle<Output>, IHandle<Input>
|
||||
{
|
||||
AgentId AgentId { get; }
|
||||
ValueTask PublishMessageAsync<T>(T message, string? source = null, CancellationToken token = default) where T : IMessage;
|
||||
ValueTask PublishMessageAsync<T>(T message, CancellationToken token = default) where T : IMessage;
|
||||
|
||||
async Task IHandle<Output>.Handle(Output item)
|
||||
async Task IHandle<Output>.Handle(Output item, CancellationToken cancellationToken)
|
||||
{
|
||||
// Assuming item has a property `Message` that we want to write to the console
|
||||
Console.WriteLine(item.Message);
|
||||
|
@ -22,9 +22,9 @@ public interface IHandleConsole : IHandle<Output>, IHandle<Input>
|
|||
{
|
||||
Route = "console"
|
||||
};
|
||||
await PublishMessageAsync(evt);
|
||||
await PublishMessageAsync(evt).ConfigureAwait(false);
|
||||
}
|
||||
async Task IHandle<Input>.Handle(Input item)
|
||||
async Task IHandle<Input>.Handle(Input item, CancellationToken cancellationToken)
|
||||
{
|
||||
Console.WriteLine("Please enter input:");
|
||||
string content = Console.ReadLine() ?? string.Empty;
|
||||
|
@ -35,7 +35,7 @@ public interface IHandleConsole : IHandle<Output>, IHandle<Input>
|
|||
{
|
||||
Route = "console"
|
||||
};
|
||||
await PublishMessageAsync(evt);
|
||||
await PublishMessageAsync(evt).ConfigureAwait(false);
|
||||
}
|
||||
static Task ProcessOutput(string message)
|
||||
{
|
||||
|
|
|
@ -9,16 +9,15 @@ namespace Microsoft.AutoGen.Agents;
|
|||
|
||||
[TopicSubscription("FileIO")]
|
||||
public abstract class FileAgent(
|
||||
IAgentWorker worker,
|
||||
[FromKeyedServices("EventTypes")] EventTypes typeRegistry,
|
||||
[FromKeyedServices("AgentsMetadata")] AgentsMetadata typeRegistry,
|
||||
string inputPath = "input.txt",
|
||||
string outputPath = "output.txt"
|
||||
) : IOAgent(worker, typeRegistry),
|
||||
) : IOAgent(typeRegistry),
|
||||
IUseFiles,
|
||||
IHandle<Input>,
|
||||
IHandle<Output>
|
||||
{
|
||||
public override async Task Handle(Input item)
|
||||
public override async Task Handle(Input item, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// validate that the file exists
|
||||
if (!File.Exists(inputPath))
|
||||
|
@ -46,7 +45,7 @@ public abstract class FileAgent(
|
|||
};
|
||||
await PublishMessageAsync(evt);
|
||||
}
|
||||
public override async Task Handle(Output item)
|
||||
public override async Task Handle(Output item, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using (var writer = new StreamWriter(outputPath, append: true))
|
||||
{
|
||||
|
|
|
@ -5,11 +5,11 @@ using Microsoft.AutoGen.Contracts;
|
|||
using Microsoft.AutoGen.Core;
|
||||
namespace Microsoft.AutoGen.Agents;
|
||||
|
||||
public abstract class IOAgent(IAgentWorker worker, EventTypes eventTypes) : Agent(worker, eventTypes)
|
||||
public abstract class IOAgent(AgentsMetadata eventTypes) : Agent(eventTypes)
|
||||
{
|
||||
public string _route = "base";
|
||||
|
||||
public virtual async Task Handle(Input item)
|
||||
public virtual async Task Handle(Input item, CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
var evt = new InputProcessed
|
||||
|
@ -19,7 +19,7 @@ public abstract class IOAgent(IAgentWorker worker, EventTypes eventTypes) : Agen
|
|||
await PublishMessageAsync(evt);
|
||||
}
|
||||
|
||||
public virtual async Task Handle(Output item)
|
||||
public virtual async Task Handle(Output item, CancellationToken cancellationToken)
|
||||
{
|
||||
var evt = new OutputWritten
|
||||
{
|
||||
|
|
|
@ -18,10 +18,9 @@ public abstract class WebAPIAgent : IOAgent,
|
|||
|
||||
public WebAPIAgent(
|
||||
IAgentWorker worker,
|
||||
[FromKeyedServices("EventTypes")] EventTypes typeRegistry,
|
||||
[FromKeyedServices("AgentsMetadata")] AgentsMetadata typeRegistry,
|
||||
ILogger<WebAPIAgent> logger,
|
||||
string url = "/agents/webio") : base(
|
||||
worker,
|
||||
typeRegistry)
|
||||
{
|
||||
_url = url;
|
||||
|
@ -53,7 +52,7 @@ public abstract class WebAPIAgent : IOAgent,
|
|||
app.Run();
|
||||
}
|
||||
|
||||
public override async Task Handle(Input item)
|
||||
public override async Task Handle(Input item, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Process the input (this is a placeholder, replace with actual processing logic)
|
||||
await ProcessInput(item.Message);
|
||||
|
@ -65,7 +64,7 @@ public abstract class WebAPIAgent : IOAgent,
|
|||
await PublishMessageAsync(evt);
|
||||
}
|
||||
|
||||
public override async Task Handle(Output item)
|
||||
public override async Task Handle(Output item, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Assuming item has a property `Content` that we want to return in the response
|
||||
var evt = new OutputWritten
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// MessageExtensions.cs
|
||||
|
||||
using Google.Protobuf;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
|
||||
namespace Microsoft.AutoGen.Contracts;
|
||||
|
||||
public static class MessageExtensions
|
||||
{
|
||||
private const string PROTO_DATA_CONTENT_TYPE = "application/x-protobuf";
|
||||
public static CloudEvent ToCloudEvent<T>(this T message, string source) where T : IMessage
|
||||
{
|
||||
return new CloudEvent
|
||||
{
|
||||
ProtoData = Any.Pack(message),
|
||||
Type = message.Descriptor.FullName,
|
||||
Source = source,
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
SpecVersion = "1.0",
|
||||
Attributes = { { "datacontenttype", new CloudEvent.Types.CloudEventAttributeValue { CeString = PROTO_DATA_CONTENT_TYPE } } }
|
||||
};
|
||||
}
|
||||
public static T FromCloudEvent<T>(this CloudEvent cloudEvent) where T : IMessage, new()
|
||||
{
|
||||
return cloudEvent.ProtoData.Unpack<T>();
|
||||
}
|
||||
public static AgentState ToAgentState<T>(this T state, AgentId agentId, string eTag) where T : IMessage
|
||||
{
|
||||
return new AgentState
|
||||
{
|
||||
ProtoData = Any.Pack(state),
|
||||
AgentId = agentId,
|
||||
ETag = eTag
|
||||
};
|
||||
}
|
||||
|
||||
public static T FromAgentState<T>(this AgentState state) where T : IMessage, new()
|
||||
{
|
||||
if (state.HasTextData == true)
|
||||
{
|
||||
if (typeof(T) == typeof(AgentState))
|
||||
{
|
||||
return (T)(IMessage)state;
|
||||
}
|
||||
}
|
||||
return state.ProtoData.Unpack<T>();
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@ using Microsoft.Extensions.DependencyInjection;
|
|||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AutoGen.Core;
|
||||
namespace Microsoft.AutoGen.Core.Grpc;
|
||||
|
||||
public sealed class GrpcAgentWorker(
|
||||
AgentRpc.AgentRpcClient client,
|
||||
|
@ -24,6 +24,7 @@ public sealed class GrpcAgentWorker(
|
|||
private readonly ConcurrentDictionary<string, Type> _agentTypes = new();
|
||||
private readonly ConcurrentDictionary<(string Type, string Key), Agent> _agents = new();
|
||||
private readonly ConcurrentDictionary<string, (Agent Agent, string OriginalRequestId)> _pendingRequests = new();
|
||||
private readonly ConcurrentDictionary<string, HashSet<Type>> _agentsForEvent = new();
|
||||
private readonly Channel<(Message Message, TaskCompletionSource WriteCompletionSource)> _outboundMessagesChannel = Channel.CreateBounded<(Message, TaskCompletionSource)>(new BoundedChannelOptions(1024)
|
||||
{
|
||||
AllowSynchronousContinuations = true,
|
||||
|
@ -32,7 +33,7 @@ public sealed class GrpcAgentWorker(
|
|||
FullMode = BoundedChannelFullMode.Wait
|
||||
});
|
||||
private readonly AgentRpc.AgentRpcClient _client = client;
|
||||
private readonly IServiceProvider _serviceProvider = serviceProvider;
|
||||
public readonly IServiceProvider ServiceProvider = serviceProvider;
|
||||
private readonly IEnumerable<Tuple<string, Type>> _configuredAgentTypes = configuredAgentTypes;
|
||||
private readonly ILogger<GrpcAgentWorker> _logger = logger;
|
||||
private readonly CancellationTokenSource _shutdownCts = CancellationTokenSource.CreateLinkedTokenSource(hostApplicationLifetime.ApplicationStopping);
|
||||
|
@ -40,6 +41,7 @@ public sealed class GrpcAgentWorker(
|
|||
private Task? _readTask;
|
||||
private Task? _writeTask;
|
||||
|
||||
IServiceProvider IAgentWorker.ServiceProvider => ServiceProvider;
|
||||
public void Dispose()
|
||||
{
|
||||
_outboundMessagesChannel.Writer.TryComplete();
|
||||
|
@ -73,22 +75,6 @@ public sealed class GrpcAgentWorker(
|
|||
message.Response.RequestId = request.OriginalRequestId;
|
||||
request.Agent.ReceiveMessage(message);
|
||||
break;
|
||||
|
||||
case Message.MessageOneofCase.CloudEvent:
|
||||
|
||||
// HACK: Send the message to an instance of each agent type
|
||||
// where AgentId = (namespace: event.Namespace, name: agentType)
|
||||
// i.e, assume each agent type implicitly subscribes to each event.
|
||||
|
||||
var item = message.CloudEvent;
|
||||
|
||||
foreach (var (typeName, _) in _agentTypes)
|
||||
{
|
||||
var agent = GetOrActivateAgent(new AgentId { Type = typeName, Key = item.Source });
|
||||
agent.ReceiveMessage(message);
|
||||
}
|
||||
|
||||
break;
|
||||
case Message.MessageOneofCase.RegisterAgentTypeResponse:
|
||||
if (!message.RegisterAgentTypeResponse.Success)
|
||||
{
|
||||
|
@ -101,6 +87,24 @@ public sealed class GrpcAgentWorker(
|
|||
_logger.LogError($"Failed to add subscription '{message.AddSubscriptionResponse.Error}'");
|
||||
}
|
||||
break;
|
||||
case Message.MessageOneofCase.CloudEvent:
|
||||
var item = message.CloudEvent;
|
||||
if (!_agentsForEvent.TryGetValue(item.Type, out var agents))
|
||||
{
|
||||
_logger.LogError($"This worker can't handle the event type '{item.Type}'.");
|
||||
break;
|
||||
}
|
||||
foreach (var a in agents)
|
||||
{
|
||||
var subject = item.GetSubject();
|
||||
if (string.IsNullOrEmpty(subject))
|
||||
{
|
||||
subject = item.Source;
|
||||
}
|
||||
var agent = GetOrActivateAgent(new AgentId { Type = a.Name, Key = subject });
|
||||
agent.ReceiveMessage(message);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException($"Unexpected message '{message}'.");
|
||||
}
|
||||
|
@ -161,10 +165,15 @@ public sealed class GrpcAgentWorker(
|
|||
_logger.LogError(ex, "Error connecting to GRPC endpoint {Endpoint}.", Environment.GetEnvironmentVariable("AGENT_HOST"));
|
||||
break;
|
||||
}
|
||||
catch (RpcException ex) when (ex.StatusCode == StatusCode.OK)
|
||||
{
|
||||
_logger.LogError(ex, "Error writing to channel, continuing (Status OK). {ex}", channel.ToString());
|
||||
break;
|
||||
}
|
||||
catch (Exception ex) when (!_shutdownCts.IsCancellationRequested)
|
||||
{
|
||||
item.WriteCompletionSource?.TrySetException(ex);
|
||||
_logger.LogError(ex, "Error writing to channel.");
|
||||
_logger.LogError(ex, $"Error writing to channel.{ex}");
|
||||
channel = RecreateChannel(channel);
|
||||
continue;
|
||||
}
|
||||
|
@ -187,7 +196,8 @@ public sealed class GrpcAgentWorker(
|
|||
{
|
||||
if (_agentTypes.TryGetValue(agentId.Type, out var agentType))
|
||||
{
|
||||
agent = (Agent)ActivatorUtilities.CreateInstance(_serviceProvider, agentType, this);
|
||||
agent = (Agent)ActivatorUtilities.CreateInstance(ServiceProvider, agentType);
|
||||
Agent.Initialize(this, agent);
|
||||
_agents.TryAdd((agentId.Type, agentId.Key), agent);
|
||||
}
|
||||
else
|
||||
|
@ -206,22 +216,38 @@ public sealed class GrpcAgentWorker(
|
|||
var events = agentType.GetInterfaces()
|
||||
.Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IHandle<>))
|
||||
.Select(i => ReflectionHelper.GetMessageDescriptor(i.GetGenericArguments().First())?.FullName);
|
||||
//var state = agentType.BaseType?.GetGenericArguments().First();
|
||||
var topicTypes = agentType.GetCustomAttributes<TopicSubscriptionAttribute>().Select(t => t.Topic);
|
||||
// add the agentType to the list of agent types that handle the event
|
||||
foreach (var evt in events)
|
||||
{
|
||||
if (!_agentsForEvent.TryGetValue(evt!, out var agents))
|
||||
{
|
||||
agents = new HashSet<Type>();
|
||||
_agentsForEvent[evt!] = agents;
|
||||
}
|
||||
|
||||
//TODO: do something with the response (like retry on error)
|
||||
agents.Add(agentType);
|
||||
}
|
||||
var topicTypes = agentType.GetCustomAttributes<TopicSubscriptionAttribute>().Select(t => t.Topic).ToList();
|
||||
/* var response = await _client.RegisterAgentAsync(new RegisterAgentTypeRequest
|
||||
{
|
||||
Type = type,
|
||||
Topics = { topicTypes },
|
||||
Events = { events }
|
||||
}, null, null, cancellationToken); */
|
||||
await WriteChannelAsync(new Message
|
||||
{
|
||||
RegisterAgentTypeRequest = new RegisterAgentTypeRequest
|
||||
{
|
||||
Type = type,
|
||||
RequestId = Guid.NewGuid().ToString(),
|
||||
//TopicTypes = { topicTypes },
|
||||
//StateType = state?.Name,
|
||||
//Events = { events }
|
||||
Type = type,
|
||||
//Topics = { topicTypes }, //future
|
||||
//Events = { events } //future
|
||||
}
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (!topicTypes.Any())
|
||||
{
|
||||
topicTypes.Add(agentType.Name);
|
||||
}
|
||||
foreach (var topic in topicTypes)
|
||||
{
|
||||
var subscriptionRequest = new Message
|
||||
|
@ -239,7 +265,7 @@ public sealed class GrpcAgentWorker(
|
|||
}
|
||||
}
|
||||
};
|
||||
await WriteChannelAsync(subscriptionRequest, cancellationToken).ConfigureAwait(true);
|
||||
await _client.AddSubscriptionAsync(subscriptionRequest.AddSubscriptionRequest, null, null, cancellationToken);
|
||||
foreach (var e in events)
|
||||
{
|
||||
subscriptionRequest = new Message
|
||||
|
@ -257,7 +283,7 @@ public sealed class GrpcAgentWorker(
|
|||
}
|
||||
}
|
||||
};
|
||||
await WriteChannelAsync(subscriptionRequest, cancellationToken).ConfigureAwait(true);
|
||||
await _client.AddSubscriptionAsync(subscriptionRequest.AddSubscriptionRequest, null, null, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -351,8 +377,8 @@ public sealed class GrpcAgentWorker(
|
|||
|
||||
try
|
||||
{
|
||||
_readTask = Task.Run(RunReadPump, CancellationToken.None);
|
||||
_writeTask = Task.Run(RunWritePump, CancellationToken.None);
|
||||
_readTask = Task.Run(RunReadPump, cancellationToken);
|
||||
_writeTask = Task.Run(RunWritePump, cancellationToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
@ -408,5 +434,20 @@ public sealed class GrpcAgentWorker(
|
|||
throw new KeyNotFoundException($"Failed to read AgentState for {agentId}.");
|
||||
}
|
||||
}
|
||||
public async ValueTask<List<Subscription>> GetSubscriptionsAsync(GetSubscriptionsRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var response = await _client.GetSubscriptionsAsync(request, null, null, cancellationToken);
|
||||
return response.Subscriptions.ToList();
|
||||
}
|
||||
public ValueTask<AddSubscriptionResponse> SubscribeAsync(AddSubscriptionRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var response = _client.AddSubscription(request, null, null, cancellationToken);
|
||||
return new ValueTask<AddSubscriptionResponse>(response);
|
||||
}
|
||||
public ValueTask<RemoveSubscriptionResponse> UnsubscribeAsync(RemoveSubscriptionRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var response = _client.RemoveSubscription(request, null, null, cancellationToken);
|
||||
return new ValueTask<RemoveSubscriptionResponse>(response);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// GrpcAgentWorkerHostBuilderExtension.cs
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using Google.Protobuf;
|
||||
using Google.Protobuf.Reflection;
|
||||
using Grpc.Core;
|
||||
using Grpc.Net.Client.Configuration;
|
||||
using Microsoft.AutoGen.Contracts;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
namespace Microsoft.AutoGen.Core.Grpc;
|
||||
|
||||
public static class GrpcAgentWorkerHostBuilderExtensions
|
||||
|
@ -20,14 +22,27 @@ public static class GrpcAgentWorkerHostBuilderExtensions
|
|||
options.Address = new Uri(agentServiceAddress ?? builder.Configuration["AGENT_HOST"] ?? _defaultAgentServiceAddress);
|
||||
options.ChannelOptionsActions.Add(channelOptions =>
|
||||
{
|
||||
|
||||
channelOptions.HttpHandler = new SocketsHttpHandler
|
||||
var loggerFactory = new LoggerFactory();
|
||||
if (Debugger.IsAttached)
|
||||
{
|
||||
EnableMultipleHttp2Connections = true,
|
||||
KeepAlivePingDelay = TimeSpan.FromSeconds(20),
|
||||
KeepAlivePingTimeout = TimeSpan.FromSeconds(10),
|
||||
KeepAlivePingPolicy = HttpKeepAlivePingPolicy.WithActiveRequests
|
||||
};
|
||||
channelOptions.HttpHandler = new SocketsHttpHandler
|
||||
{
|
||||
EnableMultipleHttp2Connections = false,
|
||||
KeepAlivePingDelay = TimeSpan.FromSeconds(200),
|
||||
KeepAlivePingTimeout = TimeSpan.FromSeconds(100),
|
||||
KeepAlivePingPolicy = HttpKeepAlivePingPolicy.Always
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
channelOptions.HttpHandler = new SocketsHttpHandler
|
||||
{
|
||||
EnableMultipleHttp2Connections = true,
|
||||
KeepAlivePingDelay = TimeSpan.FromSeconds(20),
|
||||
KeepAlivePingTimeout = TimeSpan.FromSeconds(10),
|
||||
KeepAlivePingPolicy = HttpKeepAlivePingPolicy.WithActiveRequests
|
||||
};
|
||||
}
|
||||
|
||||
var methodConfig = new MethodConfig
|
||||
{
|
||||
|
@ -46,79 +61,19 @@ public static class GrpcAgentWorkerHostBuilderExtensions
|
|||
channelOptions.ThrowOperationCanceledOnCancellation = true;
|
||||
});
|
||||
});
|
||||
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
builder.Services.TryAddSingleton(DistributedContextPropagator.Current);
|
||||
builder.Services.AddSingleton<IAgentWorker, GrpcAgentWorker>();
|
||||
builder.Services.AddKeyedSingleton("EventTypes", (sp, key) =>
|
||||
{
|
||||
var interfaceType = typeof(IMessage);
|
||||
var pairs = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(assembly => assembly.GetTypes())
|
||||
.Where(type => interfaceType.IsAssignableFrom(type) && type.IsClass && !type.IsAbstract)
|
||||
.Select(t => (t, GetMessageDescriptor(t)));
|
||||
|
||||
var descriptors = pairs.Select(t => t.Item2);
|
||||
var typeRegistry = TypeRegistry.FromMessages(descriptors);
|
||||
var types = pairs.ToDictionary(item => item.Item2?.FullName ?? "", item => item.t);
|
||||
|
||||
var eventsMap = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(assembly => assembly.GetTypes())
|
||||
.Where(type => ReflectionHelper.IsSubclassOfGeneric(type, typeof(Agent)) && !type.IsAbstract)
|
||||
.Select(t => (t, t.GetInterfaces()
|
||||
.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(Agent)) && !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(Agent)) && !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);
|
||||
});
|
||||
builder.Services.AddSingleton<IHostedService>(sp => (IHostedService)sp.GetRequiredService<IAgentWorker>());
|
||||
builder.Services.AddKeyedSingleton("AgentsMetadata", (sp, key) =>
|
||||
{
|
||||
return ReflectionHelper.GetAgentsMetadata(assemblies);
|
||||
});
|
||||
builder.Services.AddSingleton((s) =>
|
||||
{
|
||||
var worker = s.GetRequiredService<IAgentWorker>();
|
||||
var client = ActivatorUtilities.CreateInstance<Client>(s);
|
||||
Agent.Initialize(worker, client);
|
||||
return client;
|
||||
});
|
||||
builder.Services.AddSingleton(new AgentApplicationBuilder(builder));
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
<ItemGroup>
|
||||
<ProjectReference Include="..\Core\Microsoft.AutoGen.Core.csproj" />
|
||||
<ProjectReference Include="..\Contracts\Microsoft.AutoGen.Contracts.csproj" />
|
||||
<ProjectReference Include="..\Runtime.Grpc\Microsoft.AutoGen.Runtime.Grpc.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ namespace Microsoft.AutoGen.Core;
|
|||
/// <summary>
|
||||
/// Represents the base class for an agent in the AutoGen system.
|
||||
/// </summary>
|
||||
public abstract class Agent : IHandle
|
||||
public abstract class Agent
|
||||
{
|
||||
private readonly object _lock = new();
|
||||
private readonly ConcurrentDictionary<string, TaskCompletionSource<RpcResponse>> _pendingRequests = [];
|
||||
|
@ -32,23 +32,25 @@ public abstract class Agent : IHandle
|
|||
public AgentId AgentId { get; private set; }
|
||||
private readonly Channel<object> _mailbox = Channel.CreateUnbounded<object>();
|
||||
protected internal ILogger<Agent> _logger;
|
||||
public AgentMessenger Messenger { get; private set; }
|
||||
public IAgentWorker Worker { get; private set; }
|
||||
private readonly ConcurrentDictionary<Type, MethodInfo> _handlersByMessageType;
|
||||
internal Task Completion { get; private set; }
|
||||
protected readonly AgentsMetadata EventTypes;
|
||||
|
||||
protected readonly EventTypes EventTypes;
|
||||
|
||||
protected Agent(IAgentWorker worker,
|
||||
EventTypes eventTypes,
|
||||
protected Agent(
|
||||
AgentsMetadata eventTypes,
|
||||
ILogger<Agent>? logger = null)
|
||||
{
|
||||
EventTypes = eventTypes;
|
||||
AgentId = new AgentId(this.GetType().Name, Guid.NewGuid().ToString()); ;
|
||||
AgentId = new AgentId(this.GetType().Name, Guid.NewGuid().ToString());
|
||||
_logger = logger ?? LoggerFactory.Create(builder => { }).CreateLogger<Agent>();
|
||||
_handlersByMessageType = new(GetType().GetHandlersLookupTable());
|
||||
Messenger = AgentMessengerFactory.Create(worker, DistributedContextPropagator.Current);
|
||||
AddImplicitSubscriptionsAsync().AsTask().Wait();
|
||||
Completion = Start();
|
||||
Worker = new UninitializedAgentWorker();
|
||||
}
|
||||
public static void Initialize(IAgentWorker worker, Agent agent)
|
||||
{
|
||||
agent.Worker = worker;
|
||||
agent.Start();
|
||||
agent.AddImplicitSubscriptionsAsync().AsTask().Wait();
|
||||
}
|
||||
|
||||
private async ValueTask AddImplicitSubscriptionsAsync()
|
||||
|
@ -74,7 +76,7 @@ public abstract class Agent : IHandle
|
|||
}
|
||||
};
|
||||
// explicitly wait for this to complete
|
||||
await Messenger.SendMessageAsync(new Message { AddSubscriptionRequest = subscriptionRequest }).ConfigureAwait(true);
|
||||
Worker.SubscribeAsync(subscriptionRequest).AsTask().Wait();
|
||||
}
|
||||
|
||||
// using reflection, find all methods that Handle<T> and subscribe to the topic T
|
||||
|
@ -82,13 +84,15 @@ public abstract class Agent : IHandle
|
|||
foreach (var method in handleMethods)
|
||||
{
|
||||
var eventType = method.GetParameters()[0].ParameterType;
|
||||
var topic = EventTypes.EventsMap.FirstOrDefault(x => x.Value.Contains(eventType.Name)).Key;
|
||||
if (topic != null)
|
||||
var topics = EventTypes.GetTopicsForAgent(GetType());
|
||||
if (topics != null)
|
||||
{
|
||||
Subscribe(nameof(topic));
|
||||
foreach (var topic in topics)
|
||||
{
|
||||
await SubscribeAsync(topic).ConfigureAwait(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -148,7 +152,7 @@ public abstract class Agent : IHandle
|
|||
{
|
||||
var activity = this.ExtractActivity(msg.CloudEvent.Type, msg.CloudEvent.Attributes);
|
||||
await this.InvokeWithActivityAsync(
|
||||
static ((Agent Agent, CloudEvent Item) state, CancellationToken _) => state.Agent.CallHandler(state.Item),
|
||||
static ((Agent Agent, CloudEvent Item) state, CancellationToken ct) => state.Agent.CallHandlerAsync(state.Item, ct),
|
||||
(this, msg.CloudEvent),
|
||||
activity,
|
||||
msg.CloudEvent.Type, cancellationToken).ConfigureAwait(false);
|
||||
|
@ -169,35 +173,66 @@ public abstract class Agent : IHandle
|
|||
break;
|
||||
}
|
||||
}
|
||||
public List<string> Subscribe(string topic)
|
||||
public async ValueTask<List<Subscription>> GetSubscriptionsAsync()
|
||||
{
|
||||
Message message = new()
|
||||
GetSubscriptionsRequest request = new();
|
||||
return await Worker.GetSubscriptionsAsync(request).ConfigureAwait(false);
|
||||
}
|
||||
public async ValueTask<AddSubscriptionResponse> SubscribeAsync(string topic)
|
||||
{
|
||||
AddSubscriptionRequest subscriptionRequest = new()
|
||||
{
|
||||
AddSubscriptionRequest = new()
|
||||
RequestId = Guid.NewGuid().ToString(),
|
||||
Subscription = new Subscription
|
||||
{
|
||||
RequestId = Guid.NewGuid().ToString(),
|
||||
Subscription = new Subscription
|
||||
TypeSubscription = new TypeSubscription
|
||||
{
|
||||
TypeSubscription = new TypeSubscription
|
||||
{
|
||||
TopicType = topic,
|
||||
AgentType = this.AgentId.Key
|
||||
}
|
||||
TopicType = topic,
|
||||
AgentType = this.AgentId.Type
|
||||
}
|
||||
}
|
||||
};
|
||||
Messenger.SendMessageAsync(message).AsTask().Wait();
|
||||
|
||||
return new List<string> { topic };
|
||||
var subscriptionResponse = await Worker.SubscribeAsync(subscriptionRequest).ConfigureAwait(true);
|
||||
if (!subscriptionResponse.Success)
|
||||
{
|
||||
_logger.LogError($"{GetType}{AgentId.Key}: Failed to unsubscribe from topic {topic}");
|
||||
}
|
||||
return subscriptionResponse;
|
||||
}
|
||||
public async ValueTask<RemoveSubscriptionResponse> UnsubscribeAsync(Guid id)
|
||||
{
|
||||
RemoveSubscriptionRequest subscriptionRequest = new()
|
||||
{
|
||||
Id = id.ToString()
|
||||
};
|
||||
var subscriptionResponse = await Worker.UnsubscribeAsync(subscriptionRequest).ConfigureAwait(true);
|
||||
if (!subscriptionResponse.Success)
|
||||
{
|
||||
_logger.LogError($"{GetType}{AgentId.Key}: Failed to unsubscribe from Subscription {id}");
|
||||
}
|
||||
return subscriptionResponse;
|
||||
}
|
||||
public async ValueTask<RemoveSubscriptionResponse> UnsubscribeAsync(string topic)
|
||||
{
|
||||
var subscriptions = await GetSubscriptionsAsync().ConfigureAwait(false);
|
||||
var subscription = subscriptions.FirstOrDefault(s => s.TypeSubscription.TopicType == topic);
|
||||
if (subscription == null)
|
||||
{
|
||||
var error = $"{GetType}{AgentId.Key}: Subscription not found for topic {topic}";
|
||||
_logger.LogError(error);
|
||||
return new RemoveSubscriptionResponse { Success = false, Error = error };
|
||||
}
|
||||
var id = Guid.Parse(subscription.Id);
|
||||
return await UnsubscribeAsync(id).ConfigureAwait(true);
|
||||
}
|
||||
public async Task StoreAsync(AgentState state, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await Messenger.StoreAsync(state, cancellationToken).ConfigureAwait(false);
|
||||
await Worker.StoreAsync(state, cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
public async Task<T> ReadAsync<T>(AgentId agentId, CancellationToken cancellationToken = default) where T : IMessage, new()
|
||||
{
|
||||
var agentstate = await Messenger.ReadAsync(agentId, cancellationToken).ConfigureAwait(false);
|
||||
var agentstate = await Worker.ReadAsync(agentId, cancellationToken).ConfigureAwait(false);
|
||||
return agentstate.FromAgentState<T>();
|
||||
}
|
||||
private void OnResponseCore(RpcResponse response)
|
||||
|
@ -226,7 +261,9 @@ public abstract class Agent : IHandle
|
|||
{
|
||||
response = new RpcResponse { Error = ex.Message };
|
||||
}
|
||||
await Messenger.SendResponseAsync(request, response, cancellationToken).ConfigureAwait(false);
|
||||
response.RequestId = request.RequestId;
|
||||
|
||||
await Worker.SendResponseAsync(response, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
protected async Task<RpcResponse> RequestAsync(AgentId target, string method, Dictionary<string, string> parameters)
|
||||
|
@ -250,7 +287,7 @@ public abstract class Agent : IHandle
|
|||
activity?.SetTag("peer.service", target.ToString());
|
||||
|
||||
var completion = new TaskCompletionSource<RpcResponse>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
Messenger!.Update(request, activity);
|
||||
IAgentWorkerExtensions.Update(this.Worker, request, activity);
|
||||
await this.InvokeWithActivityAsync(
|
||||
static async (state, ct) =>
|
||||
{
|
||||
|
@ -258,7 +295,7 @@ public abstract class Agent : IHandle
|
|||
|
||||
self._pendingRequests.AddOrUpdate(request.RequestId, _ => completion, (_, __) => completion);
|
||||
|
||||
await state.Item1.Messenger!.SendRequestAsync(state.Item1, state.request, ct).ConfigureAwait(false);
|
||||
await state.Item1.Worker!.SendRequestAsync(state.Item1, state.request, ct).ConfigureAwait(false);
|
||||
|
||||
await completion.Task.ConfigureAwait(false);
|
||||
},
|
||||
|
@ -270,101 +307,155 @@ public abstract class Agent : IHandle
|
|||
return await completion.Task.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async ValueTask PublishMessageAsync<T>(T message, string? source = null, CancellationToken token = default) where T : IMessage
|
||||
private string SetTopic(string? topic = null, string? source = null, string? key = null)
|
||||
{
|
||||
var topicTypes = this.GetType().GetCustomAttributes<TopicSubscriptionAttribute>().Select(t => t.Topic);
|
||||
if (!topicTypes.Any())
|
||||
if (string.IsNullOrWhiteSpace(topic))
|
||||
{
|
||||
topicTypes = topicTypes.Append(string.IsNullOrWhiteSpace(source) ? this.AgentId.Type + "." + this.AgentId.Key : source);
|
||||
topic = this.AgentId.Type + "." + this.AgentId.Key;
|
||||
}
|
||||
foreach (var topic in topicTypes)
|
||||
else
|
||||
{
|
||||
await PublishMessageAsync(topic, message, source, token).ConfigureAwait(false);
|
||||
topic = topic + "." + source + "." + key;
|
||||
}
|
||||
}
|
||||
public async ValueTask PublishMessageAsync<T>(string topic, T message, string? source = null, CancellationToken token = default) where T : IMessage
|
||||
{
|
||||
await PublishEventAsync(topic, message, token).ConfigureAwait(false);
|
||||
return topic;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Publishes a message asynchronously.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the message.</typeparam>
|
||||
/// <param name="token">A token to cancel the operation.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
public async ValueTask PublishMessageAsync<T>(T message, string topic, string source, string key, CancellationToken token = default) where T : IMessage
|
||||
{
|
||||
// if there are no topic types, use the agent's default topic subscription attribute and the agent's type and key
|
||||
if (string.IsNullOrWhiteSpace(topic))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(topic))
|
||||
{
|
||||
topic = this.AgentId.Type + "." + this.AgentId.Key;
|
||||
}
|
||||
else
|
||||
{
|
||||
topic = topic + "." + source + "." + key;
|
||||
}
|
||||
|
||||
var topicTypes = this.GetType().GetCustomAttributes<TopicSubscriptionAttribute>().Select(t => t.Topic);
|
||||
if (!topicTypes.Any())
|
||||
{
|
||||
topicTypes = topicTypes.Append(string.IsNullOrWhiteSpace(source) ? this.AgentId.Type + "." + this.AgentId.Key : source);
|
||||
}
|
||||
topicTypes = topicTypes.Append(SetTopic(topic, source, key));
|
||||
foreach (var t in topicTypes)
|
||||
{
|
||||
await PublishEventAsync(t, message, token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await PublishEventAsync(topic, message, token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
public async ValueTask PublishMessageAsync<T>(T message, string topic, string source, CancellationToken token = default) where T : IMessage
|
||||
{
|
||||
string key = this.AgentId.Key;
|
||||
await PublishMessageAsync(message, topic, source, key, token).ConfigureAwait(false);
|
||||
}
|
||||
public async ValueTask PublishMessageAsync<T>(T message, string topic, CancellationToken token = default) where T : IMessage
|
||||
{
|
||||
string source = this.AgentId.Type;
|
||||
string key = this.AgentId.Key;
|
||||
await PublishMessageAsync(message, topic, source, key, token).ConfigureAwait(false);
|
||||
}
|
||||
public async ValueTask PublishMessageAsync<T>(T message, CancellationToken token = default) where T : IMessage
|
||||
{
|
||||
string topic = "";
|
||||
string source = this.AgentId.Type;
|
||||
string key = this.AgentId.Key;
|
||||
await PublishMessageAsync(message, topic, source, key, token).ConfigureAwait(false);
|
||||
}
|
||||
public async ValueTask PublishEventAsync(string topic, IMessage message, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await PublishEventAsync(message.ToCloudEvent(key: GetType().Name, topic: topic), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
public async ValueTask PublishEventAsync(CloudEvent item, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var activity = s_source.StartActivity($"PublishEventAsync '{item.Type}'", ActivityKind.Client, Activity.Current?.Context ?? default);
|
||||
activity?.SetTag("peer.service", $"{item.Type}/{item.Source}");
|
||||
|
||||
// TODO: fix activity
|
||||
Messenger.Update(item, activity);
|
||||
IAgentWorkerExtensions.Update(this.Worker, item, activity);
|
||||
await this.InvokeWithActivityAsync(
|
||||
static async ((Agent Agent, CloudEvent Event) state, CancellationToken ct) =>
|
||||
{
|
||||
await state.Agent.Messenger.PublishEventAsync(state.Event).ConfigureAwait(false);
|
||||
await state.Agent.Worker.PublishEventAsync(state.Event).ConfigureAwait(false);
|
||||
},
|
||||
(this, item),
|
||||
activity,
|
||||
item.Type, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public Task CallHandler(CloudEvent item)
|
||||
public Task CallHandlerAsync(CloudEvent item, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Only send the event to the handler if the agent type is handling that type
|
||||
// foreach of the keys in the EventTypes.EventsMap[] if it contains the item.type
|
||||
foreach (var key in EventTypes.EventsMap.Keys)
|
||||
if (EventTypes.CheckIfTypeHandles(GetType(), eventName: item.Type))
|
||||
{
|
||||
if (EventTypes.EventsMap[key].Contains(item.Type))
|
||||
var payload = item.ProtoData.Unpack(EventTypes.TypeRegistry);
|
||||
var eventType = EventTypes.GetEventTypeByName(item.Type);
|
||||
if (eventType == null)
|
||||
{
|
||||
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]);
|
||||
_logger.LogError($"Event type {item.Type} not found in the registry");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
var convertedPayload = Convert.ChangeType(payload, eventType);
|
||||
var genericInterfaceType = typeof(IHandle<>).MakeGenericType(eventType);
|
||||
|
||||
MethodInfo methodInfo;
|
||||
try
|
||||
MethodInfo methodInfo;
|
||||
try
|
||||
{
|
||||
// check that our target actually implements this interface, otherwise call the default static
|
||||
if (genericInterfaceType.IsAssignableFrom(this.GetType()))
|
||||
{
|
||||
// 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.");
|
||||
}
|
||||
methodInfo = genericInterfaceType.GetMethod(nameof(IHandle<IMessage>.Handle), BindingFlags.Public | BindingFlags.Instance)
|
||||
?? throw new InvalidOperationException($"Method not found on type {genericInterfaceType.FullName}");
|
||||
return methodInfo.Invoke(this, new object[] { convertedPayload, cancellationToken }) as Task ?? Task.CompletedTask;
|
||||
}
|
||||
catch (Exception ex)
|
||||
else
|
||||
{
|
||||
_logger.LogError(ex, $"Error invoking method {nameof(IHandle<object>.Handle)}");
|
||||
throw; // TODO: ?
|
||||
// The error here is we have registered for an event that we do not have code to listen to
|
||||
throw new InvalidOperationException($"Agent Type '{GetType()}' is registered to handle this type but no handler found for event '{item.Type}'; expecting IHandle<{item.Type}> implementation.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Error invoking method {nameof(IHandle<IMessage>.Handle)}");
|
||||
throw; // TODO: ?
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<RpcResponse> HandleRequestAsync(RpcRequest request) => Task.FromResult(new RpcResponse { Error = "Not implemented" });
|
||||
|
||||
//TODO: should this be async and cancellable?
|
||||
public virtual Task HandleObject(object item)
|
||||
/// <summary>
|
||||
/// Handles a generic object
|
||||
/// </summary>
|
||||
/// <param name="item">The object to handle</param>
|
||||
/// <param name="cancellationToken">The cancellation token</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
/// TODO: this is only called from tests, should we remove it?
|
||||
public virtual async Task HandleObjectAsync(object item, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// get all Handle<T> methods
|
||||
var handleTMethods = this.GetType().GetMethods().Where(m => m.Name == "Handle" && m.GetParameters().Length == 1).ToList();
|
||||
|
||||
// get the one that matches the type of the item
|
||||
var handleTMethod = handleTMethods.FirstOrDefault(m => m.GetParameters()[0].ParameterType == item.GetType());
|
||||
|
||||
// if we found one, invoke it
|
||||
if (handleTMethod != null)
|
||||
{
|
||||
return (Task)handleTMethod.Invoke(this, [item])!;
|
||||
await (Task)handleTMethod.Invoke(this, [item])!;
|
||||
}
|
||||
|
||||
// otherwise, complain
|
||||
throw new InvalidOperationException($"No handler found for type {item.GetType().FullName}");
|
||||
}
|
||||
public async ValueTask PublishEventAsync(string topic, IMessage message, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await PublishEventAsync(message.ToCloudEvent(topic), cancellationToken).ConfigureAwait(false);
|
||||
_logger.LogError($"No handler found for type {item.GetType().FullName}");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ public static class AgentExtensions
|
|||
public static Activity? ExtractActivity(this Agent agent, string activityName, IDictionary<string, string> metadata)
|
||||
{
|
||||
Activity? activity;
|
||||
var (traceParent, traceState) = agent.Messenger.GetTraceIdAndState(metadata);
|
||||
(string? traceParent, string? traceState) = IAgentWorkerExtensions.GetTraceIdAndState(agent.Worker, metadata);
|
||||
if (!string.IsNullOrEmpty(traceParent))
|
||||
{
|
||||
if (ActivityContext.TryParse(traceParent, traceState, isRemote: true, out var parentContext))
|
||||
|
@ -43,7 +43,7 @@ public static class AgentExtensions
|
|||
activity.TraceStateString = traceState;
|
||||
}
|
||||
|
||||
var baggage = agent.Messenger.ExtractMetadata(metadata);
|
||||
var baggage = IAgentWorkerExtensions.ExtractMetadata(agent.Worker, metadata);
|
||||
|
||||
foreach (var baggageItem in baggage)
|
||||
{
|
||||
|
|
|
@ -1,123 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// AgentMessenger.cs
|
||||
|
||||
using System.Diagnostics;
|
||||
using Google.Protobuf.Collections;
|
||||
using Microsoft.AutoGen.Contracts;
|
||||
using static Microsoft.AutoGen.Contracts.CloudEvent.Types;
|
||||
|
||||
namespace Microsoft.AutoGen.Core;
|
||||
|
||||
public sealed class AgentMessenger(IAgentWorker worker, DistributedContextPropagator distributedContextPropagator)
|
||||
{
|
||||
private readonly IAgentWorker worker = worker;
|
||||
|
||||
private DistributedContextPropagator DistributedContextPropagator { get; } = distributedContextPropagator;
|
||||
public (string?, string?) GetTraceIdAndState(IDictionary<string, string> metadata)
|
||||
{
|
||||
DistributedContextPropagator.ExtractTraceIdAndState(metadata,
|
||||
static (object? carrier, string fieldName, out string? fieldValue, out IEnumerable<string>? fieldValues) =>
|
||||
{
|
||||
var metadata = (IDictionary<string, string>)carrier!;
|
||||
fieldValues = null;
|
||||
metadata.TryGetValue(fieldName, out fieldValue);
|
||||
},
|
||||
out var traceParent,
|
||||
out var traceState);
|
||||
return (traceParent, traceState);
|
||||
}
|
||||
public (string?, string?) GetTraceIdAndState(MapField<string, CloudEventAttributeValue> metadata)
|
||||
{
|
||||
DistributedContextPropagator.ExtractTraceIdAndState(metadata,
|
||||
static (object? carrier, string fieldName, out string? fieldValue, out IEnumerable<string>? fieldValues) =>
|
||||
{
|
||||
var metadata = (MapField<string, CloudEventAttributeValue>)carrier!;
|
||||
fieldValues = null;
|
||||
metadata.TryGetValue(fieldName, out var ceValue);
|
||||
fieldValue = ceValue?.CeString;
|
||||
},
|
||||
out var traceParent,
|
||||
out var traceState);
|
||||
return (traceParent, traceState);
|
||||
}
|
||||
public void Update(RpcRequest request, Activity? activity = null)
|
||||
{
|
||||
DistributedContextPropagator.Inject(activity, request.Metadata, static (carrier, key, value) =>
|
||||
{
|
||||
var metadata = (IDictionary<string, string>)carrier!;
|
||||
if (metadata.TryGetValue(key, out _))
|
||||
{
|
||||
metadata[key] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
metadata.Add(key, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
public void Update(CloudEvent cloudEvent, Activity? activity = null)
|
||||
{
|
||||
DistributedContextPropagator.Inject(activity, cloudEvent.Attributes, static (carrier, key, value) =>
|
||||
{
|
||||
var mapField = (MapField<string, CloudEventAttributeValue>)carrier!;
|
||||
if (mapField.TryGetValue(key, out var ceValue))
|
||||
{
|
||||
mapField[key] = new CloudEventAttributeValue { CeString = value };
|
||||
}
|
||||
else
|
||||
{
|
||||
mapField.Add(key, new CloudEventAttributeValue { CeString = value });
|
||||
}
|
||||
});
|
||||
}
|
||||
public async ValueTask SendResponseAsync(RpcRequest request, RpcResponse response, CancellationToken cancellationToken = default)
|
||||
{
|
||||
response.RequestId = request.RequestId;
|
||||
await worker.SendResponseAsync(response, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
public async ValueTask SendRequestAsync(Agent agent, RpcRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await worker.SendRequestAsync(agent, request, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
public async ValueTask SendMessageAsync(Message message, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await worker.SendMessageAsync(message, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
public async ValueTask PublishEventAsync(CloudEvent @event, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await worker.PublishEventAsync(@event, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
public async ValueTask StoreAsync(AgentState value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await worker.StoreAsync(value, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
public ValueTask<AgentState> ReadAsync(AgentId agentId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return worker.ReadAsync(agentId, cancellationToken);
|
||||
}
|
||||
|
||||
public IDictionary<string, string> ExtractMetadata(IDictionary<string, string> metadata)
|
||||
{
|
||||
var baggage = DistributedContextPropagator.ExtractBaggage(metadata, static (object? carrier, string fieldName, out string? fieldValue, out IEnumerable<string>? fieldValues) =>
|
||||
{
|
||||
var metadata = (IDictionary<string, string>)carrier!;
|
||||
fieldValues = null;
|
||||
metadata.TryGetValue(fieldName, out fieldValue);
|
||||
});
|
||||
|
||||
return baggage as IDictionary<string, string> ?? new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
public IDictionary<string, string> ExtractMetadata(MapField<string, CloudEventAttributeValue> metadata)
|
||||
{
|
||||
var baggage = DistributedContextPropagator.ExtractBaggage(metadata, static (object? carrier, string fieldName, out string? fieldValue, out IEnumerable<string>? fieldValues) =>
|
||||
{
|
||||
var metadata = (MapField<string, CloudEventAttributeValue>)carrier!;
|
||||
fieldValues = null;
|
||||
metadata.TryGetValue(fieldName, out var ceValue);
|
||||
fieldValue = ceValue?.CeString;
|
||||
});
|
||||
|
||||
return baggage as IDictionary<string, string> ?? new Dictionary<string, string>();
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// AgentMessengerFactory.cs
|
||||
|
||||
using System.Diagnostics;
|
||||
namespace Microsoft.AutoGen.Core;
|
||||
public class AgentMessengerFactory()
|
||||
{
|
||||
public static AgentMessenger Create(IAgentWorker worker, DistributedContextPropagator distributedContextPropagator)
|
||||
{
|
||||
return new AgentMessenger(worker, distributedContextPropagator);
|
||||
}
|
||||
}
|
|
@ -9,12 +9,19 @@ using Microsoft.Extensions.Hosting;
|
|||
|
||||
namespace Microsoft.AutoGen.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a worker that manages agents and handles messages.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Initializes a new instance of the <see cref="AgentWorker"/> class.
|
||||
/// </remarks>
|
||||
/// <param name="hostApplicationLifetime">The application lifetime.</param>
|
||||
/// <param name="serviceProvider">The service provider.</param>
|
||||
/// <param name="configuredAgentTypes">The configured agent types.</param>
|
||||
public class AgentWorker(
|
||||
IHostApplicationLifetime hostApplicationLifetime,
|
||||
IServiceProvider serviceProvider,
|
||||
[FromKeyedServices("AgentTypes")] IEnumerable<Tuple<string, Type>> configuredAgentTypes) :
|
||||
IHostedService,
|
||||
IAgentWorker
|
||||
IHostApplicationLifetime hostApplicationLifetime,
|
||||
IServiceProvider serviceProvider,
|
||||
[FromKeyedServices("AgentTypes")] IEnumerable<Tuple<string, Type>> configuredAgentTypes) : IHostedService, IAgentWorker
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, Type> _agentTypes = new();
|
||||
private readonly ConcurrentDictionary<(string Type, string Key), Agent> _agents = new();
|
||||
|
@ -22,24 +29,27 @@ IServiceProvider serviceProvider,
|
|||
private readonly ConcurrentDictionary<string, AgentState> _agentStates = new();
|
||||
private readonly ConcurrentDictionary<string, (Agent Agent, string OriginalRequestId)> _pendingClientRequests = new();
|
||||
private readonly CancellationTokenSource _shutdownCts = CancellationTokenSource.CreateLinkedTokenSource(hostApplicationLifetime.ApplicationStopping);
|
||||
private readonly IServiceProvider _serviceProvider = serviceProvider;
|
||||
public IServiceProvider ServiceProvider { get; } = serviceProvider;
|
||||
private readonly IEnumerable<Tuple<string, Type>> _configuredAgentTypes = configuredAgentTypes;
|
||||
private readonly ConcurrentDictionary<string, Subscription> _subscriptionsByAgentType = new();
|
||||
private readonly ConcurrentDictionary<string, List<Subscription>> _subscriptionsByAgentType = new();
|
||||
private readonly ConcurrentDictionary<string, List<string>> _subscriptionsByTopic = new();
|
||||
private readonly ConcurrentDictionary<Guid, IDictionary<string, string>> _subscriptionsByGuid = new();
|
||||
private readonly CancellationTokenSource _shutdownCancellationToken = new();
|
||||
private Task? _mailboxTask;
|
||||
private readonly object _channelLock = new();
|
||||
|
||||
// this is the in-memory version - we just pass the message directly to the agent(s) that handle this type of event
|
||||
/// <inheritdoc />
|
||||
public async ValueTask PublishEventAsync(CloudEvent cloudEvent, CancellationToken cancellationToken = default)
|
||||
{
|
||||
foreach (var (typeName, _) in _agentTypes)
|
||||
{
|
||||
if (typeName == nameof(Client)) { continue; }
|
||||
var agent = GetOrActivateAgent(new AgentId(typeName, cloudEvent.Source));
|
||||
var agent = GetOrActivateAgent(new AgentId { Type = typeName, Key = cloudEvent.GetSubject() });
|
||||
agent.ReceiveMessage(new Message { CloudEvent = cloudEvent });
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask SendRequestAsync(Agent agent, RpcRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var requestId = Guid.NewGuid().ToString();
|
||||
|
@ -47,21 +57,28 @@ IServiceProvider serviceProvider,
|
|||
request.RequestId = requestId;
|
||||
await _mailbox.Writer.WriteAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ValueTask SendResponseAsync(RpcResponse response, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return _mailbox.Writer.WriteAsync(new Message { Response = response }, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ValueTask SendMessageAsync(Message message, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return _mailbox.Writer.WriteAsync(message, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ValueTask StoreAsync(AgentState value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var agentId = value.AgentId ?? throw new InvalidOperationException("AgentId is required when saving AgentState.");
|
||||
// add or update _agentStates with the new state
|
||||
var response = _agentStates.AddOrUpdate(agentId.ToString(), value, (key, oldValue) => value);
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ValueTask<AgentState> ReadAsync(AgentId agentId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_agentStates.TryGetValue(agentId.ToString(), out var state);
|
||||
|
@ -74,6 +91,10 @@ IServiceProvider serviceProvider,
|
|||
throw new KeyNotFoundException($"Failed to read AgentState for {agentId}.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the message pump.
|
||||
/// </summary>
|
||||
public async Task RunMessagePump()
|
||||
{
|
||||
await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding);
|
||||
|
@ -95,7 +116,7 @@ IServiceProvider serviceProvider,
|
|||
}
|
||||
break;
|
||||
case Message msg when msg.AddSubscriptionRequest != null:
|
||||
await AddSubscriptionRequestAsync(msg.AddSubscriptionRequest).ConfigureAwait(true);
|
||||
await SubscribeAsync(msg.AddSubscriptionRequest).ConfigureAwait(true);
|
||||
break;
|
||||
case Message msg when msg.AddSubscriptionResponse != null:
|
||||
break;
|
||||
|
@ -114,24 +135,70 @@ IServiceProvider serviceProvider,
|
|||
}
|
||||
}
|
||||
}
|
||||
private async ValueTask AddSubscriptionRequestAsync(AddSubscriptionRequest subscription)
|
||||
public async ValueTask<AddSubscriptionResponse> SubscribeAsync(AddSubscriptionRequest subscription, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var topic = subscription.Subscription.TypeSubscription.TopicType;
|
||||
var agentType = subscription.Subscription.TypeSubscription.AgentType;
|
||||
_subscriptionsByAgentType[agentType] = subscription.Subscription;
|
||||
var id = Guid.NewGuid();
|
||||
subscription.Subscription.Id = id.ToString();
|
||||
var sub = new Dictionary<string, string> { { topic, agentType } };
|
||||
_subscriptionsByGuid.GetOrAdd(id, static _ => new Dictionary<string, string>()).Add(topic, agentType);
|
||||
_subscriptionsByAgentType.GetOrAdd(key: agentType, _ => []).Add(subscription.Subscription);
|
||||
_subscriptionsByTopic.GetOrAdd(topic, _ => []).Add(agentType);
|
||||
Message response = new()
|
||||
var response = new AddSubscriptionResponse
|
||||
{
|
||||
AddSubscriptionResponse = new()
|
||||
{
|
||||
RequestId = subscription.RequestId,
|
||||
Error = "",
|
||||
Success = true
|
||||
}
|
||||
RequestId = subscription.RequestId,
|
||||
Error = "",
|
||||
Success = true
|
||||
};
|
||||
await _mailbox.Writer.WriteAsync(response).ConfigureAwait(false);
|
||||
return response;
|
||||
}
|
||||
public async ValueTask<RemoveSubscriptionResponse> UnsubscribeAsync(RemoveSubscriptionRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!Guid.TryParse(request.Id, out var id))
|
||||
{
|
||||
var removeSubscriptionResponse = new RemoveSubscriptionResponse
|
||||
{
|
||||
Error = "Invalid subscription ID",
|
||||
Success = false
|
||||
};
|
||||
return removeSubscriptionResponse;
|
||||
}
|
||||
if (_subscriptionsByGuid.TryGetValue(id, out var sub))
|
||||
{
|
||||
foreach (var (topic, agentType) in sub)
|
||||
{
|
||||
if (_subscriptionsByTopic.TryGetValue(topic, out var innerAgentTypes))
|
||||
{
|
||||
while (innerAgentTypes.Remove(agentType))
|
||||
{
|
||||
//ensures all instances are removed
|
||||
}
|
||||
_subscriptionsByTopic.AddOrUpdate(topic, innerAgentTypes, (_, _) => innerAgentTypes);
|
||||
}
|
||||
var toRemove = new List<Subscription>();
|
||||
if (_subscriptionsByAgentType.TryGetValue(agentType, out var innerSubscriptions))
|
||||
{
|
||||
foreach (var subscription in innerSubscriptions)
|
||||
{
|
||||
if (subscription.Id == id.ToString())
|
||||
{
|
||||
toRemove.Add(subscription);
|
||||
}
|
||||
}
|
||||
foreach (var subscription in toRemove) { innerSubscriptions.Remove(subscription); }
|
||||
_subscriptionsByAgentType.AddOrUpdate(agentType, innerSubscriptions, (_, _) => innerSubscriptions);
|
||||
}
|
||||
}
|
||||
_subscriptionsByGuid.TryRemove(id, out _);
|
||||
}
|
||||
var response = new RemoveSubscriptionResponse
|
||||
{
|
||||
Error = "",
|
||||
Success = true
|
||||
};
|
||||
return response;
|
||||
}
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
StartCore();
|
||||
|
@ -162,6 +229,8 @@ IServiceProvider serviceProvider,
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_shutdownCts.Cancel();
|
||||
|
@ -176,14 +245,26 @@ IServiceProvider serviceProvider,
|
|||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or activates an agent.
|
||||
/// </summary>
|
||||
/// <param name="agentId">The agent ID.</param>
|
||||
/// <returns>The activated agent.</returns>
|
||||
|
||||
private Agent GetOrActivateAgent(AgentId agentId)
|
||||
{
|
||||
if (!_agents.TryGetValue((agentId.Type, agentId.Key), out var agent))
|
||||
{
|
||||
if (_agentTypes.TryGetValue(agentId.Type, out var agentType))
|
||||
{
|
||||
agent = (Agent)ActivatorUtilities.CreateInstance(_serviceProvider, agentType, this);
|
||||
_agents.TryAdd((agentId.Type, agentId.Key), agent);
|
||||
using (var scope = ServiceProvider.CreateScope())
|
||||
{
|
||||
var scopedProvider = scope.ServiceProvider;
|
||||
agent = (Agent)ActivatorUtilities.CreateInstance(scopedProvider, agentType);
|
||||
Agent.Initialize(this, agent);
|
||||
_agents.TryAdd((agentId.Type, agentId.Key), agent);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -193,4 +274,21 @@ IServiceProvider serviceProvider,
|
|||
|
||||
return agent;
|
||||
}
|
||||
public ValueTask<List<Subscription>> GetSubscriptionsAsync(Type type)
|
||||
{
|
||||
if (_subscriptionsByAgentType.TryGetValue(type.Name, out var subscriptions))
|
||||
{
|
||||
return new ValueTask<List<Subscription>>(subscriptions);
|
||||
}
|
||||
return new ValueTask<List<Subscription>>([]);
|
||||
}
|
||||
public ValueTask<List<Subscription>> GetSubscriptionsAsync(GetSubscriptionsRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var subscriptions = new List<Subscription>();
|
||||
foreach (var (_, value) in _subscriptionsByAgentType)
|
||||
{
|
||||
subscriptions.AddRange(value);
|
||||
}
|
||||
return new ValueTask<List<Subscription>>(subscriptions);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// AgentsMetadata.cs
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using Google.Protobuf.Reflection;
|
||||
|
||||
namespace Microsoft.AutoGen.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a collection of event types and their associated metadata.
|
||||
/// </summary>
|
||||
public sealed class AgentsMetadata
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AgentsMetadata"/> class.
|
||||
/// </summary>
|
||||
/// <param name="typeRegistry">The type registry containing protobuf type information.</param>
|
||||
/// <param name="types">A dictionary mapping event names to their corresponding types.</param>
|
||||
/// <param name="eventsMap">A dictionary mapping types to a set of event names associated with those types.</param>
|
||||
public AgentsMetadata(TypeRegistry typeRegistry, Dictionary<string, Type> types, Dictionary<Type, HashSet<string>> eventsMap, Dictionary<Type, HashSet<string>> topicsMap)
|
||||
{
|
||||
TypeRegistry = typeRegistry;
|
||||
_types = new(types);
|
||||
_eventsMap = new(eventsMap);
|
||||
_topicsMap = new(topicsMap);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type registry containing protobuf type information.
|
||||
/// </summary>
|
||||
public TypeRegistry TypeRegistry { get; }
|
||||
|
||||
private ConcurrentDictionary<string, Type> _types;
|
||||
|
||||
private ConcurrentDictionary<Type, HashSet<string>> _eventsMap;
|
||||
private ConcurrentDictionary<Type, HashSet<string>> _topicsMap;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a given type handles a specific event name.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to check.</param>
|
||||
/// <param name="eventName">The event name to check.</param>
|
||||
/// <returns><c>true</c> if the type handles the event name; otherwise, <c>false</c>.</returns>
|
||||
public bool CheckIfTypeHandles(Type type, string eventName)
|
||||
{
|
||||
if (_eventsMap.TryGetValue(type, out var events))
|
||||
{
|
||||
return events.Contains(eventName);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the event type by its name.
|
||||
/// </summary>
|
||||
/// <param name="type">The name of the event type.</param>
|
||||
/// <returns>The event type if found; otherwise, <c>null</c>.</returns>
|
||||
public Type? GetEventTypeByName(string type)
|
||||
{
|
||||
if (_types.TryGetValue(type, out var eventType))
|
||||
{
|
||||
return eventType;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public HashSet<string>? GetEventsForAgent(Type agent)
|
||||
{
|
||||
if (_eventsMap.TryGetValue(agent, out var events))
|
||||
{
|
||||
return events;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public HashSet<string>? GetTopicsForAgent(Type agent)
|
||||
{
|
||||
if (_topicsMap.TryGetValue(agent, out var topics))
|
||||
{
|
||||
return topics;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AutoGen.Core;
|
||||
public sealed class Client(IAgentWorker worker, [FromKeyedServices("EventTypes")] EventTypes eventTypes)
|
||||
: Agent(worker, eventTypes)
|
||||
public sealed class Client([FromKeyedServices("AgentsMetadata")] AgentsMetadata eventTypes)
|
||||
: Agent(eventTypes)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -3,9 +3,6 @@
|
|||
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using Google.Protobuf;
|
||||
using Google.Protobuf.Reflection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
@ -14,8 +11,6 @@ namespace Microsoft.AutoGen.Core;
|
|||
|
||||
public static class HostBuilderExtensions
|
||||
{
|
||||
private const string _defaultAgentServiceAddress = "https://localhost:53071";
|
||||
|
||||
public static IHostApplicationBuilder AddAgent<
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TAgent>(this IHostApplicationBuilder builder, string typeName) where TAgent : Agent
|
||||
{
|
||||
|
@ -30,90 +25,27 @@ public static class HostBuilderExtensions
|
|||
return builder;
|
||||
}
|
||||
|
||||
public static IHostApplicationBuilder AddAgentWorker(this IHostApplicationBuilder builder, string? agentServiceAddress = null)
|
||||
public static IHostApplicationBuilder AddAgentWorker(this IHostApplicationBuilder builder)
|
||||
{
|
||||
agentServiceAddress ??= builder.Configuration["AGENT_HOST"] ?? _defaultAgentServiceAddress;
|
||||
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
builder.Services.TryAddSingleton(DistributedContextPropagator.Current);
|
||||
builder.Services.AddSingleton<IAgentWorker, AgentWorker>();
|
||||
builder.Services.AddSingleton<IHostedService>(sp => (IHostedService)sp.GetRequiredService<IAgentWorker>());
|
||||
builder.Services.AddKeyedSingleton("EventTypes", (sp, key) =>
|
||||
builder.Services.AddKeyedSingleton("AgentsMetadata", (sp, key) =>
|
||||
{
|
||||
var interfaceType = typeof(IMessage);
|
||||
var pairs = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(assembly => assembly.GetTypes())
|
||||
.Where(type => interfaceType.IsAssignableFrom(type) && type.IsClass && !type.IsAbstract)
|
||||
.Select(t => (t, GetMessageDescriptor(t)));
|
||||
|
||||
var descriptors = pairs.Select(t => t.Item2);
|
||||
var typeRegistry = TypeRegistry.FromMessages(descriptors);
|
||||
var types = pairs.ToDictionary(item => item.Item2?.FullName ?? "", item => item.t);
|
||||
|
||||
var eventsMap = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(assembly => assembly.GetTypes())
|
||||
.Where(type => ReflectionHelper.IsSubclassOfGeneric(type, typeof(Agent)) && !type.IsAbstract)
|
||||
.Select(t => (t, t.GetInterfaces()
|
||||
.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(Agent)) && !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(Agent)) && !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 ReflectionHelper.GetAgentsMetadata(assemblies);
|
||||
});
|
||||
builder.Services.AddSingleton((s) =>
|
||||
{
|
||||
var worker = s.GetRequiredService<IAgentWorker>();
|
||||
var client = ActivatorUtilities.CreateInstance<Client>(s);
|
||||
Agent.Initialize(worker, client);
|
||||
return client;
|
||||
});
|
||||
builder.Services.AddSingleton<Client>();
|
||||
builder.Services.AddSingleton(new AgentApplicationBuilder(builder));
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static MessageDescriptor? GetMessageDescriptor(Type type)
|
||||
{
|
||||
var property = type.GetProperty("Descriptor", BindingFlags.Static | BindingFlags.Public);
|
||||
return property?.GetValue(null) as MessageDescriptor;
|
||||
}
|
||||
}
|
||||
public sealed class AgentApplicationBuilder(IHostApplicationBuilder builder)
|
||||
{
|
||||
|
|
|
@ -5,10 +5,14 @@ namespace Microsoft.AutoGen.Core;
|
|||
|
||||
public interface IAgentWorker
|
||||
{
|
||||
IServiceProvider ServiceProvider { get; }
|
||||
ValueTask PublishEventAsync(CloudEvent evt, CancellationToken cancellationToken = default);
|
||||
ValueTask SendRequestAsync(Agent agent, RpcRequest request, CancellationToken cancellationToken = default);
|
||||
ValueTask SendResponseAsync(RpcResponse response, CancellationToken cancellationToken = default);
|
||||
ValueTask SendMessageAsync(Message message, CancellationToken cancellationToken = default);
|
||||
ValueTask StoreAsync(AgentState value, CancellationToken cancellationToken = default);
|
||||
ValueTask<AgentState> ReadAsync(AgentId agentId, CancellationToken cancellationToken = default);
|
||||
ValueTask<AddSubscriptionResponse> SubscribeAsync(AddSubscriptionRequest request, CancellationToken cancellationToken = default);
|
||||
ValueTask<RemoveSubscriptionResponse> UnsubscribeAsync(RemoveSubscriptionRequest request, CancellationToken cancellationToken = default);
|
||||
ValueTask<List<Subscription>> GetSubscriptionsAsync(GetSubscriptionsRequest request, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// IAgentWorkerExtensions.cs
|
||||
|
||||
using System.Diagnostics;
|
||||
using Google.Protobuf.Collections;
|
||||
using Microsoft.AutoGen.Contracts;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using static Microsoft.AutoGen.Contracts.CloudEvent.Types;
|
||||
|
||||
namespace Microsoft.AutoGen.Core;
|
||||
|
||||
public static class IAgentWorkerExtensions
|
||||
{
|
||||
public static (string?, string?) GetTraceIdAndState(IAgentWorker worker, IDictionary<string, string> metadata)
|
||||
{
|
||||
var dcp = worker.ServiceProvider.GetRequiredService<DistributedContextPropagator>();
|
||||
dcp.ExtractTraceIdAndState(metadata,
|
||||
static (object? carrier, string fieldName, out string? fieldValue, out IEnumerable<string>? fieldValues) =>
|
||||
{
|
||||
var metadata = (IDictionary<string, string>)carrier!;
|
||||
fieldValues = null;
|
||||
metadata.TryGetValue(fieldName, out fieldValue);
|
||||
},
|
||||
out var traceParent,
|
||||
out var traceState);
|
||||
return (traceParent, traceState);
|
||||
}
|
||||
public static (string?, string?) GetTraceIdAndState(IAgentWorker worker, MapField<string, CloudEventAttributeValue> metadata)
|
||||
{
|
||||
var dcp = worker.ServiceProvider.GetRequiredService<DistributedContextPropagator>();
|
||||
dcp.ExtractTraceIdAndState(metadata,
|
||||
static (object? carrier, string fieldName, out string? fieldValue, out IEnumerable<string>? fieldValues) =>
|
||||
{
|
||||
var metadata = (MapField<string, CloudEventAttributeValue>)carrier!;
|
||||
fieldValues = null;
|
||||
metadata.TryGetValue(fieldName, out var ceValue);
|
||||
fieldValue = ceValue?.CeString;
|
||||
},
|
||||
out var traceParent,
|
||||
out var traceState);
|
||||
return (traceParent, traceState);
|
||||
}
|
||||
public static void Update(IAgentWorker worker, RpcRequest request, Activity? activity = null)
|
||||
{
|
||||
var dcp = worker.ServiceProvider.GetRequiredService<DistributedContextPropagator>();
|
||||
dcp.Inject(activity, request.Metadata, static (carrier, key, value) =>
|
||||
{
|
||||
var metadata = (IDictionary<string, string>)carrier!;
|
||||
if (metadata.TryGetValue(key, out _))
|
||||
{
|
||||
metadata[key] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
metadata.Add(key, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
public static void Update(IAgentWorker worker, CloudEvent cloudEvent, Activity? activity = null)
|
||||
{
|
||||
var dcp = worker.ServiceProvider.GetRequiredService<DistributedContextPropagator>();
|
||||
dcp.Inject(activity, cloudEvent.Attributes, static (carrier, key, value) =>
|
||||
{
|
||||
var mapField = (MapField<string, CloudEventAttributeValue>)carrier!;
|
||||
if (mapField.TryGetValue(key, out var ceValue))
|
||||
{
|
||||
mapField[key] = new CloudEventAttributeValue { CeString = value };
|
||||
}
|
||||
else
|
||||
{
|
||||
mapField.Add(key, new CloudEventAttributeValue { CeString = value });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static IDictionary<string, string> ExtractMetadata(IAgentWorker worker, IDictionary<string, string> metadata)
|
||||
{
|
||||
var dcp = worker.ServiceProvider.GetRequiredService<DistributedContextPropagator>();
|
||||
var baggage = dcp.ExtractBaggage(metadata, static (object? carrier, string fieldName, out string? fieldValue, out IEnumerable<string>? fieldValues) =>
|
||||
{
|
||||
var metadata = (IDictionary<string, string>)carrier!;
|
||||
fieldValues = null;
|
||||
metadata.TryGetValue(fieldName, out fieldValue);
|
||||
});
|
||||
|
||||
return baggage as IDictionary<string, string> ?? new Dictionary<string, string>();
|
||||
}
|
||||
public static IDictionary<string, string> ExtractMetadata(IAgentWorker worker, MapField<string, CloudEventAttributeValue> metadata)
|
||||
{
|
||||
var dcp = worker.ServiceProvider.GetRequiredService<DistributedContextPropagator>();
|
||||
var baggage = dcp.ExtractBaggage(metadata, static (object? carrier, string fieldName, out string? fieldValue, out IEnumerable<string>? fieldValues) =>
|
||||
{
|
||||
var metadata = (MapField<string, CloudEventAttributeValue>)carrier!;
|
||||
fieldValues = null;
|
||||
metadata.TryGetValue(fieldName, out var ceValue);
|
||||
fieldValue = ceValue?.CeString;
|
||||
});
|
||||
|
||||
return baggage as IDictionary<string, string> ?? new Dictionary<string, string>();
|
||||
}
|
||||
}
|
|
@ -1,12 +1,20 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// IHandle.cs
|
||||
namespace Microsoft.AutoGen.Core;
|
||||
public interface IHandle
|
||||
{
|
||||
Task HandleObject(object item);
|
||||
}
|
||||
|
||||
public interface IHandle<T> : IHandle
|
||||
using Google.Protobuf;
|
||||
|
||||
namespace Microsoft.AutoGen.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a handler interface for processing items of type <typeparamref name="T"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of item to be handled, which must implement <see cref="IMessage"/>.</typeparam>
|
||||
public interface IHandle<in T> where T : IMessage
|
||||
{
|
||||
Task Handle(T item);
|
||||
/// <summary>
|
||||
/// Handles the specified item asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to be handled.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
Task Handle(T item, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// MessageExtensions.cs
|
||||
|
||||
using Google.Protobuf;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using Microsoft.AutoGen.Contracts;
|
||||
|
||||
namespace Microsoft.AutoGen.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for converting messages to and from various formats.
|
||||
/// </summary>
|
||||
public static class MessageExtensions
|
||||
{
|
||||
private const string PROTO_DATA_CONTENT_TYPE = "application/x-protobuf";
|
||||
|
||||
/// <summary>
|
||||
/// Converts a message to a CloudEvent.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the message.</typeparam>
|
||||
/// <param name="message">The message to convert.</param>
|
||||
/// <param name="key">The key of the event, maps to the Topic Type</param>
|
||||
/// <param name="topic">The topic of the event, </param>
|
||||
/// <returns>A CloudEvent representing the message.</returns>
|
||||
public static CloudEvent ToCloudEvent<T>(this T message, string key, string topic) where T : IMessage
|
||||
{
|
||||
return new CloudEvent
|
||||
{
|
||||
ProtoData = Any.Pack(message),
|
||||
Type = message.Descriptor.FullName,
|
||||
Source = topic,
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
Attributes = {
|
||||
{
|
||||
"datacontenttype", new CloudEvent.Types.CloudEventAttributeValue { CeString = PROTO_DATA_CONTENT_TYPE }
|
||||
},
|
||||
{
|
||||
"subject", new CloudEvent.Types.CloudEventAttributeValue { CeString = key }
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a CloudEvent back to a message.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the message.</typeparam>
|
||||
/// <param name="cloudEvent">The CloudEvent to convert.</param>
|
||||
/// <returns>The message represented by the CloudEvent.</returns>
|
||||
public static T FromCloudEvent<T>(this CloudEvent cloudEvent) where T : IMessage, new()
|
||||
{
|
||||
return cloudEvent.ProtoData.Unpack<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
public static string GetSubject(this CloudEvent cloudEvent)
|
||||
{
|
||||
if (cloudEvent.Attributes.TryGetValue("subject", out var value))
|
||||
{
|
||||
return value.CeString;
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a state to an AgentState.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the state.</typeparam>
|
||||
/// <param name="state">The state to convert.</param>
|
||||
/// <param name="agentId">The ID of the agent.</param>
|
||||
/// <param name="eTag">The ETag of the state.</param>
|
||||
/// <returns>An AgentState representing the state.</returns>
|
||||
public static AgentState ToAgentState<T>(this T state, AgentId agentId, string eTag) where T : IMessage
|
||||
{
|
||||
return new AgentState
|
||||
{
|
||||
ProtoData = Any.Pack(state),
|
||||
AgentId = agentId,
|
||||
ETag = eTag
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an AgentState back to a state.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the state.</typeparam>
|
||||
/// <param name="state">The AgentState to convert.</param>
|
||||
/// <returns>The state represented by the AgentState.</returns>
|
||||
public static T FromAgentState<T>(this AgentState state) where T : IMessage, new()
|
||||
{
|
||||
if (state.HasTextData == true)
|
||||
{
|
||||
if (typeof(T) == typeof(AgentState))
|
||||
{
|
||||
return (T)(IMessage)state;
|
||||
}
|
||||
}
|
||||
return state.ProtoData.Unpack<T>();
|
||||
}
|
||||
}
|
|
@ -23,7 +23,7 @@ public sealed class ReflectionHelper
|
|||
}
|
||||
return false;
|
||||
}
|
||||
public static EventTypes GetAgentsMetadata(params Assembly[] assemblies)
|
||||
public static AgentsMetadata GetAgentsMetadata(params Assembly[] assemblies)
|
||||
{
|
||||
var interfaceType = typeof(IMessage);
|
||||
var pairs = assemblies
|
||||
|
@ -42,8 +42,12 @@ public sealed class ReflectionHelper
|
|||
.Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IHandle<>))
|
||||
.Select(i => GetMessageDescriptor(i.GetGenericArguments().First())?.FullName ?? "").ToHashSet()))
|
||||
.ToDictionary(item => item.t, item => item.Item2);
|
||||
|
||||
return new EventTypes(typeRegistry, types, eventsMap);
|
||||
var topicsMap = assemblies
|
||||
.SelectMany(assembly => assembly.GetTypes())
|
||||
.Where(type => IsSubclassOfGeneric(type, typeof(Agent)) && !type.IsAbstract)
|
||||
.Select(t => (t, t.GetCustomAttributes<TopicSubscriptionAttribute>().Select(a => a.Topic).ToHashSet()))
|
||||
.ToDictionary(item => item.t, item => item.Item2);
|
||||
return new AgentsMetadata(typeRegistry, types, eventsMap, topicsMap);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// TopicSubscriptionAttribute.cs
|
||||
|
||||
namespace Microsoft.AutoGen.Contracts;
|
||||
namespace Microsoft.AutoGen.Core;
|
||||
|
||||
[AttributeUsage(AttributeTargets.All)]
|
||||
public class TopicSubscriptionAttribute(string topic) : Attribute
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// UninitializedAgentWorker.cs
|
||||
|
||||
using Microsoft.AutoGen.Contracts;
|
||||
|
||||
namespace Microsoft.AutoGen.Core;
|
||||
public class UninitializedAgentWorker() : IAgentWorker
|
||||
{
|
||||
public IServiceProvider ServiceProvider => throw new AgentInitalizedIncorrectlyException(AgentNotInitializedMessage);
|
||||
internal const string AgentNotInitializedMessage = "Agent not initialized correctly. An Agent should never be directly intialized - it is always started by the AgentWorker from the Runtime (using the static Initialize() method).";
|
||||
public ValueTask PublishEventAsync(CloudEvent evt, CancellationToken cancellationToken = default) => throw new AgentInitalizedIncorrectlyException(AgentNotInitializedMessage);
|
||||
public ValueTask<AgentState> ReadAsync(AgentId agentId, CancellationToken cancellationToken = default) => throw new AgentInitalizedIncorrectlyException(AgentNotInitializedMessage);
|
||||
public ValueTask SendMessageAsync(Message message, CancellationToken cancellationToken = default) => throw new AgentInitalizedIncorrectlyException(AgentNotInitializedMessage);
|
||||
public ValueTask SendRequestAsync(Agent agent, RpcRequest request, CancellationToken cancellationToken = default) => throw new AgentInitalizedIncorrectlyException(AgentNotInitializedMessage);
|
||||
public ValueTask SendResponseAsync(RpcResponse response, CancellationToken cancellationToken = default) => throw new AgentInitalizedIncorrectlyException(AgentNotInitializedMessage);
|
||||
public ValueTask StoreAsync(AgentState value, CancellationToken cancellationToken = default) => throw new AgentInitalizedIncorrectlyException(AgentNotInitializedMessage);
|
||||
public ValueTask<List<Subscription>> GetSubscriptionsAsync(Type type) => throw new AgentInitalizedIncorrectlyException(AgentNotInitializedMessage);
|
||||
public ValueTask<List<Subscription>> GetSubscriptionsAsync(GetSubscriptionsRequest request, CancellationToken cancellationToken = default) => throw new AgentInitalizedIncorrectlyException(AgentNotInitializedMessage);
|
||||
public ValueTask<AddSubscriptionResponse> SubscribeAsync(AddSubscriptionRequest request, CancellationToken cancellationToken = default) => throw new AgentInitalizedIncorrectlyException(AgentNotInitializedMessage);
|
||||
public ValueTask<RemoveSubscriptionResponse> UnsubscribeAsync(RemoveSubscriptionRequest request, CancellationToken cancellationToken = default) => throw new AgentInitalizedIncorrectlyException(AgentNotInitializedMessage);
|
||||
public class AgentInitalizedIncorrectlyException(string message) : Exception(message)
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// IAgentGrain.cs
|
||||
|
||||
namespace Microsoft.AutoGen.Runtime.Grpc.Abstractions;
|
||||
|
||||
internal interface IAgentGrain : IGrainWithStringKey
|
||||
{
|
||||
ValueTask<Contracts.AgentState> ReadStateAsync();
|
||||
ValueTask<string> WriteStateAsync(Contracts.AgentState state, string eTag);
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// IGateway.cs
|
||||
using Microsoft.AutoGen.Contracts;
|
||||
|
||||
namespace Microsoft.AutoGen.Runtime.Grpc.Abstractions;
|
||||
|
||||
public interface IGateway : IGrainObserver
|
||||
{
|
||||
ValueTask<RpcResponse> InvokeRequestAsync(RpcRequest request);
|
||||
ValueTask BroadcastEventAsync(CloudEvent evt);
|
||||
ValueTask StoreAsync(Contracts.AgentState value);
|
||||
ValueTask<Contracts.AgentState> ReadAsync(AgentId agentId);
|
||||
ValueTask<RegisterAgentTypeResponse> RegisterAgentTypeAsync(RegisterAgentTypeRequest request);
|
||||
ValueTask<AddSubscriptionResponse> SubscribeAsync(AddSubscriptionRequest request);
|
||||
ValueTask<RemoveSubscriptionResponse> UnsubscribeAsync(RemoveSubscriptionRequest request);
|
||||
ValueTask<List<Subscription>> GetSubscriptionsAsync(GetSubscriptionsRequest request);
|
||||
Task SendMessageAsync(IConnection connection, CloudEvent cloudEvent);
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// IRegistry.cs
|
||||
|
||||
using Microsoft.AutoGen.Contracts;
|
||||
|
||||
namespace Microsoft.AutoGen.Runtime.Grpc.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for managing agent registration, placement, and subscriptions.
|
||||
/// </summary>
|
||||
public interface IRegistry
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or places an agent based on the provided agent ID.
|
||||
/// </summary>
|
||||
/// <param name="agentId">The ID of the agent.</param>
|
||||
/// <returns>A tuple containing the worker and a boolean indicating if it's a new placement.</returns>
|
||||
ValueTask<(IGateway? Worker, bool NewPlacement)> GetOrPlaceAgent(AgentId agentId);
|
||||
|
||||
/// <summary>
|
||||
/// Removes a worker from the registry.
|
||||
/// </summary>
|
||||
/// <param name="worker">The worker to remove.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
ValueTask RemoveWorker(IGateway worker);
|
||||
|
||||
/// <summary>
|
||||
/// Registers a new agent type with the specified worker.
|
||||
/// </summary>
|
||||
/// <param name="request">The request containing agent type details.</param>
|
||||
/// <param name="worker">The worker to register the agent type with.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
ValueTask RegisterAgentType(RegisterAgentTypeRequest request, IGateway worker);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new worker to the registry.
|
||||
/// </summary>
|
||||
/// <param name="worker">The worker to add.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
ValueTask AddWorker(IGateway worker);
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters an agent type from the specified worker.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of the agent to unregister.</param>
|
||||
/// <param name="worker">The worker to unregister the agent type from.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
ValueTask UnregisterAgentType(string type, IGateway worker);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a compatible worker for the specified agent type.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of the agent.</param>
|
||||
/// <returns>A task representing the asynchronous operation, with the compatible worker as the result.</returns>
|
||||
ValueTask<IGateway?> GetCompatibleWorker(string type);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of agents subscribed to and handling the specified topic and event type.
|
||||
/// </summary>
|
||||
/// <param name="topic">The topic to check subscriptions for.</param>
|
||||
/// <param name="eventType">The event type to check subscriptions for.</param>
|
||||
/// <returns>A task representing the asynchronous operation, with the list of agent IDs as the result.</returns>
|
||||
ValueTask<List<string>> GetSubscribedAndHandlingAgents(string topic, string eventType);
|
||||
|
||||
/// <summary>
|
||||
/// Subscribes an agent to a topic.
|
||||
/// </summary>
|
||||
/// <param name="request">The subscription request.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
ValueTask SubscribeAsync(AddSubscriptionRequest request);
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribes an agent from a topic.
|
||||
/// </summary>
|
||||
/// <param name="request">The unsubscription request.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
ValueTask UnsubscribeAsync(RemoveSubscriptionRequest request); // TODO: This should have its own request type.
|
||||
|
||||
/// <summary>
|
||||
/// Gets the subscriptions for a specified agent type.
|
||||
/// </summary>
|
||||
/// <returns>A task representing the asynchronous operation, with the subscriptions as the result.</returns>
|
||||
ValueTask<List<Subscription>> GetSubscriptions(GetSubscriptionsRequest request);
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// IRegistryGrain.cs
|
||||
|
||||
namespace Microsoft.AutoGen.Runtime.Grpc.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Orleans specific interface, needed to mark the key
|
||||
/// </summary>
|
||||
[Alias("Microsoft.AutoGen.Runtime.Grpc.Abstractions.IRegistryGrain")]
|
||||
public interface IRegistryGrain : IRegistry, IGrainWithIntegerKey
|
||||
{ }
|
|
@ -6,6 +6,7 @@
|
|||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Contracts\Microsoft.AutoGen.Contracts.csproj" />
|
||||
<ProjectReference Include="..\Core\Microsoft.AutoGen.Core.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Orleans.Reminders" />
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AutoGen.Core;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
@ -17,6 +18,10 @@ public static class AgentWorkerHostingExtensions
|
|||
builder.Services.TryAddSingleton(DistributedContextPropagator.Current);
|
||||
|
||||
builder.Services.AddGrpc();
|
||||
builder.Services.AddKeyedSingleton("AgentsMetadata", (sp, key) =>
|
||||
{
|
||||
return ReflectionHelper.GetAgentsMetadata(AppDomain.CurrentDomain.GetAssemblies());
|
||||
});
|
||||
builder.Services.AddSingleton<GrpcGateway>();
|
||||
builder.Services.AddSingleton<IHostedService>(sp => (IHostedService)sp.GetRequiredService<GrpcGateway>());
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
using System.Collections.Concurrent;
|
||||
using Grpc.Core;
|
||||
using Microsoft.AutoGen.Contracts;
|
||||
using Microsoft.AutoGen.Runtime.Grpc.Abstractions;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
|
@ -14,22 +15,21 @@ public sealed class GrpcGateway : BackgroundService, IGateway
|
|||
private static readonly TimeSpan s_agentResponseTimeout = TimeSpan.FromSeconds(30);
|
||||
private readonly ILogger<GrpcGateway> _logger;
|
||||
private readonly IClusterClient _clusterClient;
|
||||
private readonly ConcurrentDictionary<string, AgentState> _agentState = new();
|
||||
//private readonly ConcurrentDictionary<string, AgentState> _agentState = new();
|
||||
private readonly IRegistryGrain _gatewayRegistry;
|
||||
private readonly ISubscriptionsGrain _subscriptions;
|
||||
private readonly IGateway _reference;
|
||||
// The agents supported by each worker process.
|
||||
private readonly ConcurrentDictionary<string, List<GrpcWorkerConnection>> _supportedAgentTypes = [];
|
||||
public readonly ConcurrentDictionary<IConnection, IConnection> _workers = new();
|
||||
internal readonly ConcurrentDictionary<string, GrpcWorkerConnection> _workersByConnection = new();
|
||||
private readonly ConcurrentDictionary<string, Subscription> _subscriptionsByAgentType = new();
|
||||
private readonly ConcurrentDictionary<string, List<string>> _subscriptionsByTopic = new();
|
||||
private readonly ISubscriptionsGrain _subscriptions;
|
||||
|
||||
// The mapping from agent id to worker process.
|
||||
private readonly ConcurrentDictionary<(string Type, string Key), GrpcWorkerConnection> _agentDirectory = new();
|
||||
// RPC
|
||||
private readonly ConcurrentDictionary<(GrpcWorkerConnection, string), TaskCompletionSource<RpcResponse>> _pendingRequests = new();
|
||||
// InMemory Message Queue
|
||||
|
||||
public GrpcGateway(IClusterClient clusterClient, ILogger<GrpcGateway> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
|
@ -38,31 +38,89 @@ public sealed class GrpcGateway : BackgroundService, IGateway
|
|||
_gatewayRegistry = clusterClient.GetGrain<IRegistryGrain>(0);
|
||||
_subscriptions = clusterClient.GetGrain<ISubscriptionsGrain>(0);
|
||||
}
|
||||
public async ValueTask BroadcastEvent(CloudEvent evt)
|
||||
public async ValueTask<RpcResponse> InvokeRequestAsync(RpcRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var tasks = new List<Task>(_workers.Count);
|
||||
foreach (var (_, connection) in _supportedAgentTypes)
|
||||
var agentId = (request.Target.Type, request.Target.Key);
|
||||
if (!_agentDirectory.TryGetValue(agentId, out var connection) || connection.Completion.IsCompleted == true)
|
||||
{
|
||||
// Activate the agent on a compatible worker process.
|
||||
if (_supportedAgentTypes.TryGetValue(request.Target.Type, out var workers))
|
||||
{
|
||||
connection = workers[Random.Shared.Next(workers.Count)];
|
||||
_agentDirectory[agentId] = connection;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new(new RpcResponse { Error = "Agent not found." });
|
||||
}
|
||||
}
|
||||
// Proxy the request to the agent.
|
||||
var originalRequestId = request.RequestId;
|
||||
var newRequestId = Guid.NewGuid().ToString();
|
||||
var completion = _pendingRequests[(connection, newRequestId)] = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
request.RequestId = newRequestId;
|
||||
await connection.ResponseStream.WriteAsync(new Message { Request = request }, cancellationToken).ConfigureAwait(false);
|
||||
// Wait for the response and send it back to the caller.
|
||||
var response = await completion.Task.WaitAsync(s_agentResponseTimeout);
|
||||
response.RequestId = originalRequestId;
|
||||
return response;
|
||||
}
|
||||
public async ValueTask StoreAsync(AgentState value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_ = value.AgentId ?? throw new ArgumentNullException(nameof(value.AgentId));
|
||||
var agentState = _clusterClient.GetGrain<IAgentGrain>($"{value.AgentId.Type}:{value.AgentId.Key}");
|
||||
await agentState.WriteStateAsync(value, value.ETag);
|
||||
}
|
||||
public async ValueTask<AgentState> ReadAsync(AgentId agentId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var agentState = _clusterClient.GetGrain<IAgentGrain>($"{agentId.Type}:{agentId.Key}");
|
||||
return await agentState.ReadStateAsync();
|
||||
}
|
||||
public async ValueTask<RegisterAgentTypeResponse> RegisterAgentTypeAsync(RegisterAgentTypeRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var connection = _workersByConnection[request.RequestId];
|
||||
connection.AddSupportedType(request.Type);
|
||||
_supportedAgentTypes.GetOrAdd(request.Type, _ => []).Add(connection);
|
||||
|
||||
tasks.Add(this.SendMessageAsync((IConnection)connection[0], evt, default));
|
||||
await _gatewayRegistry.RegisterAgentType(request, _reference).ConfigureAwait(true);
|
||||
return new RegisterAgentTypeResponse
|
||||
{
|
||||
Success = true,
|
||||
RequestId = request.RequestId
|
||||
};
|
||||
}
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
}
|
||||
//intetionally not static so can be called by some methods implemented in base class
|
||||
public async Task SendMessageAsync(IConnection connection, CloudEvent cloudEvent, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var queue = (GrpcWorkerConnection)connection;
|
||||
await queue.ResponseStream.WriteAsync(new Message { CloudEvent = cloudEvent }, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
private void DispatchResponse(GrpcWorkerConnection connection, RpcResponse response)
|
||||
{
|
||||
if (!_pendingRequests.TryRemove((connection, response.RequestId), out var completion))
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning("Received response for unknown request.");
|
||||
return;
|
||||
return new RegisterAgentTypeResponse
|
||||
{
|
||||
Success = false,
|
||||
RequestId = request.RequestId,
|
||||
Error = ex.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
public async ValueTask<AddSubscriptionResponse> SubscribeAsync(AddSubscriptionRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _gatewayRegistry.SubscribeAsync(request).ConfigureAwait(true);
|
||||
return new AddSubscriptionResponse
|
||||
{
|
||||
Success = true,
|
||||
RequestId = request.RequestId
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new AddSubscriptionResponse
|
||||
{
|
||||
Success = false,
|
||||
RequestId = request.RequestId,
|
||||
Error = ex.Message
|
||||
};
|
||||
}
|
||||
// Complete the request.
|
||||
completion.SetResult(response);
|
||||
}
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
|
@ -87,8 +145,19 @@ public sealed class GrpcGateway : BackgroundService, IGateway
|
|||
_logger.LogWarning(exception, "Error removing worker from registry.");
|
||||
}
|
||||
}
|
||||
//new is intentional...
|
||||
internal async Task OnReceivedMessageAsync(GrpcWorkerConnection connection, Message message)
|
||||
internal async Task ConnectToWorkerProcess(IAsyncStreamReader<Message> requestStream, IServerStreamWriter<Message> responseStream, ServerCallContext context)
|
||||
{
|
||||
_logger.LogInformation("Received new connection from {Peer}.", context.Peer);
|
||||
var workerProcess = new GrpcWorkerConnection(this, requestStream, responseStream, context);
|
||||
_workers.GetOrAdd(workerProcess, workerProcess);
|
||||
_workersByConnection.GetOrAdd(context.Peer, workerProcess);
|
||||
await workerProcess.Connect().ConfigureAwait(false);
|
||||
}
|
||||
internal async Task SendMessageAsync(GrpcWorkerConnection connection, CloudEvent cloudEvent, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await connection.ResponseStream.WriteAsync(new Message { CloudEvent = cloudEvent }, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
internal async Task OnReceivedMessageAsync(GrpcWorkerConnection connection, Message message, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_logger.LogInformation("Received message {Message} from connection {Connection}.", message, connection);
|
||||
switch (message.MessageCase)
|
||||
|
@ -100,7 +169,7 @@ public sealed class GrpcGateway : BackgroundService, IGateway
|
|||
DispatchResponse(connection, message.Response);
|
||||
break;
|
||||
case Message.MessageOneofCase.CloudEvent:
|
||||
await DispatchEventAsync(message.CloudEvent);
|
||||
await DispatchEventAsync(message.CloudEvent, cancellationToken);
|
||||
break;
|
||||
case Message.MessageOneofCase.RegisterAgentTypeRequest:
|
||||
await RegisterAgentTypeAsync(connection, message.RegisterAgentTypeRequest);
|
||||
|
@ -114,48 +183,22 @@ public sealed class GrpcGateway : BackgroundService, IGateway
|
|||
break;
|
||||
};
|
||||
}
|
||||
private async ValueTask RespondBadRequestAsync(GrpcWorkerConnection connection, string error)
|
||||
private void DispatchResponse(GrpcWorkerConnection connection, RpcResponse response)
|
||||
{
|
||||
throw new RpcException(new Status(StatusCode.InvalidArgument, error));
|
||||
}
|
||||
|
||||
// agentype:rpc_request={requesting_agent_id}
|
||||
// {genttype}:rpc_response={request_id}
|
||||
private async ValueTask AddSubscriptionAsync(GrpcWorkerConnection connection, AddSubscriptionRequest request)
|
||||
{
|
||||
var topic = "";
|
||||
var agentType = "";
|
||||
if (request.Subscription.TypePrefixSubscription is not null)
|
||||
if (!_pendingRequests.TryRemove((connection, response.RequestId), out var completion))
|
||||
{
|
||||
topic = request.Subscription.TypePrefixSubscription.TopicTypePrefix;
|
||||
agentType = request.Subscription.TypePrefixSubscription.AgentType;
|
||||
_logger.LogWarning("Received response for unknown request id: {RequestId}.", response.RequestId);
|
||||
return;
|
||||
}
|
||||
else if (request.Subscription.TypeSubscription is not null)
|
||||
{
|
||||
topic = request.Subscription.TypeSubscription.TopicType;
|
||||
agentType = request.Subscription.TypeSubscription.AgentType;
|
||||
}
|
||||
_subscriptionsByAgentType[agentType] = request.Subscription;
|
||||
_subscriptionsByTopic.GetOrAdd(topic, _ => []).Add(agentType);
|
||||
await _subscriptions.SubscribeAsync(topic, agentType);
|
||||
//var response = new AddSubscriptionResponse { RequestId = request.RequestId, Error = "", Success = true };
|
||||
Message response = new()
|
||||
{
|
||||
AddSubscriptionResponse = new()
|
||||
{
|
||||
RequestId = request.RequestId,
|
||||
Error = "",
|
||||
Success = true
|
||||
}
|
||||
};
|
||||
await connection.ResponseStream.WriteAsync(response).ConfigureAwait(false);
|
||||
// Complete the request.
|
||||
completion.SetResult(response);
|
||||
}
|
||||
private async ValueTask RegisterAgentTypeAsync(GrpcWorkerConnection connection, RegisterAgentTypeRequest msg)
|
||||
{
|
||||
connection.AddSupportedType(msg.Type);
|
||||
_supportedAgentTypes.GetOrAdd(msg.Type, _ => []).Add(connection);
|
||||
|
||||
await _gatewayRegistry.RegisterAgentType(msg.Type, _reference).ConfigureAwait(true);
|
||||
await _gatewayRegistry.RegisterAgentType(msg, _reference).ConfigureAwait(true);
|
||||
Message response = new()
|
||||
{
|
||||
RegisterAgentTypeResponse = new()
|
||||
|
@ -167,55 +210,34 @@ public sealed class GrpcGateway : BackgroundService, IGateway
|
|||
};
|
||||
await connection.ResponseStream.WriteAsync(response).ConfigureAwait(false);
|
||||
}
|
||||
private async ValueTask DispatchEventAsync(CloudEvent evt)
|
||||
private async ValueTask DispatchEventAsync(CloudEvent evt, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// get the event type and then send to all agents that are subscribed to that event type
|
||||
var eventType = evt.Type;
|
||||
var source = evt.Source;
|
||||
var agentTypes = new List<string>();
|
||||
// ensure that we get agentTypes as an async enumerable list - try to get the value of agentTypes by topic and then cast it to an async enumerable list
|
||||
if (_subscriptionsByTopic.TryGetValue(eventType, out var agentTypesList)) { agentTypes.AddRange(agentTypesList); }
|
||||
if (_subscriptionsByTopic.TryGetValue(source, out var agentTypesList2)) { agentTypes.AddRange(agentTypesList2); }
|
||||
if (_subscriptionsByTopic.TryGetValue(source + "." + eventType, out var agentTypesList3)) { agentTypes.AddRange(agentTypesList3); }
|
||||
agentTypes = agentTypes.Distinct().ToList();
|
||||
if (agentTypes.Count > 0)
|
||||
var registry = _clusterClient.GetGrain<IRegistryGrain>(0);
|
||||
//intentionally blocking
|
||||
var targetAgentTypes = await registry.GetSubscribedAndHandlingAgents(evt.Source, evt.Type).ConfigureAwait(true);
|
||||
if (targetAgentTypes is not null && targetAgentTypes.Count > 0)
|
||||
{
|
||||
await DispatchEventToAgentsAsync(agentTypes, evt);
|
||||
}
|
||||
// instead of an exact match, we can also check for a prefix match where key starts with the eventType
|
||||
else if (_subscriptionsByTopic.Keys.Any(key => key.StartsWith(eventType)))
|
||||
{
|
||||
_subscriptionsByTopic.Where(
|
||||
kvp => kvp.Key.StartsWith(eventType))
|
||||
.SelectMany(kvp => kvp.Value)
|
||||
.Distinct()
|
||||
.ToList()
|
||||
.ForEach(async agentType =>
|
||||
targetAgentTypes = targetAgentTypes.Distinct().ToList();
|
||||
var tasks = new List<Task>(targetAgentTypes.Count);
|
||||
foreach (var agentType in targetAgentTypes)
|
||||
{
|
||||
if (_supportedAgentTypes.TryGetValue(agentType, out var connections))
|
||||
{
|
||||
await DispatchEventToAgentsAsync(new List<string> { agentType }, evt).ConfigureAwait(false);
|
||||
});
|
||||
// if the connection is alive, add it to the set, if not remove the connection from the list
|
||||
var activeConnections = connections.Where(c => c.Completion?.IsCompleted == false).ToList();
|
||||
foreach (var connection in activeConnections)
|
||||
{
|
||||
tasks.Add(this.SendMessageAsync(connection, evt, cancellationToken));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// log that no agent types were found
|
||||
_logger.LogWarning("No agent types found for event type {EventType}.", eventType);
|
||||
_logger.LogWarning("No agent types found for event type {EventType}.", evt.Type);
|
||||
}
|
||||
}
|
||||
private async ValueTask DispatchEventToAgentsAsync(IEnumerable<string> agentTypes, CloudEvent evt)
|
||||
{
|
||||
var tasks = new List<Task>(agentTypes.Count());
|
||||
foreach (var agentType in agentTypes)
|
||||
{
|
||||
if (_supportedAgentTypes.TryGetValue(agentType, out var connections))
|
||||
{
|
||||
foreach (var connection in connections)
|
||||
{
|
||||
tasks.Add(this.SendMessageAsync(connection, evt));
|
||||
}
|
||||
}
|
||||
}
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
}
|
||||
private async ValueTask DispatchRequestAsync(GrpcWorkerConnection connection, RpcRequest request)
|
||||
{
|
||||
var requestId = request.RequestId;
|
||||
|
@ -235,11 +257,9 @@ public sealed class GrpcGateway : BackgroundService, IGateway
|
|||
// TODO// Activate the worker: load state
|
||||
}
|
||||
// Forward the message to the gateway and return the result.
|
||||
return await gateway.InvokeRequest(request).ConfigureAwait(true);
|
||||
});
|
||||
//}
|
||||
return await gateway.InvokeRequestAsync(request).ConfigureAwait(true);
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async Task InvokeRequestDelegate(GrpcWorkerConnection connection, RpcRequest request, Func<RpcRequest, Task<RpcResponse>> func)
|
||||
{
|
||||
try
|
||||
|
@ -253,27 +273,6 @@ public sealed class GrpcGateway : BackgroundService, IGateway
|
|||
await connection.ResponseStream.WriteAsync(new Message { Response = new RpcResponse { RequestId = request.RequestId, Error = ex.Message } }).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
internal Task ConnectToWorkerProcess(IAsyncStreamReader<Message> requestStream, IServerStreamWriter<Message> responseStream, ServerCallContext context)
|
||||
{
|
||||
_logger.LogInformation("Received new connection from {Peer}.", context.Peer);
|
||||
var workerProcess = new GrpcWorkerConnection(this, requestStream, responseStream, context);
|
||||
_workers[workerProcess] = workerProcess;
|
||||
return workerProcess.Completion;
|
||||
}
|
||||
public async ValueTask StoreAsync(AgentState value)
|
||||
{
|
||||
var agentId = value.AgentId ?? throw new ArgumentNullException(nameof(value.AgentId));
|
||||
_agentState[agentId.Key] = value;
|
||||
}
|
||||
|
||||
public async ValueTask<AgentState> ReadAsync(AgentId agentId)
|
||||
{
|
||||
if (_agentState.TryGetValue(agentId.Key, out var state))
|
||||
{
|
||||
return state;
|
||||
}
|
||||
return new AgentState { AgentId = agentId };
|
||||
}
|
||||
internal void OnRemoveWorkerProcess(GrpcWorkerConnection workerProcess)
|
||||
{
|
||||
_workers.TryRemove(workerProcess, out _);
|
||||
|
@ -294,41 +293,128 @@ public sealed class GrpcGateway : BackgroundService, IGateway
|
|||
}
|
||||
}
|
||||
}
|
||||
public async ValueTask<RpcResponse> InvokeRequest(RpcRequest request, CancellationToken cancellationToken = default)
|
||||
private static async ValueTask RespondBadRequestAsync(GrpcWorkerConnection connection, string error)
|
||||
{
|
||||
(string Type, string Key) agentId = (request.Target.Type, request.Target.Key);
|
||||
if (!_agentDirectory.TryGetValue(agentId, out var connection) || connection.Completion.IsCompleted)
|
||||
throw new RpcException(new Status(StatusCode.InvalidArgument, error));
|
||||
}
|
||||
private async ValueTask AddSubscriptionAsync(GrpcWorkerConnection connection, AddSubscriptionRequest request)
|
||||
{
|
||||
var topic = "";
|
||||
var agentType = "";
|
||||
if (request.Subscription.TypePrefixSubscription is not null)
|
||||
{
|
||||
// Activate the agent on a compatible worker process.
|
||||
if (_supportedAgentTypes.TryGetValue(request.Target.Type, out var workers))
|
||||
topic = request.Subscription.TypePrefixSubscription.TopicTypePrefix;
|
||||
agentType = request.Subscription.TypePrefixSubscription.AgentType;
|
||||
}
|
||||
else if (request.Subscription.TypeSubscription is not null)
|
||||
{
|
||||
topic = request.Subscription.TypeSubscription.TopicType;
|
||||
agentType = request.Subscription.TypeSubscription.AgentType;
|
||||
}
|
||||
_subscriptionsByAgentType[agentType] = request.Subscription;
|
||||
_subscriptionsByTopic.GetOrAdd(topic, _ => []).Add(agentType);
|
||||
await _subscriptions.SubscribeAsync(topic, agentType);
|
||||
//var response = new SubscriptionResponse { RequestId = request.RequestId, Error = "", Success = true };
|
||||
Message response = new()
|
||||
{
|
||||
AddSubscriptionResponse = new()
|
||||
{
|
||||
connection = workers[Random.Shared.Next(workers.Count)];
|
||||
_agentDirectory[agentId] = connection;
|
||||
RequestId = request.RequestId,
|
||||
Error = "",
|
||||
Success = true
|
||||
}
|
||||
else
|
||||
};
|
||||
await connection.ResponseStream.WriteAsync(response).ConfigureAwait(false);
|
||||
}
|
||||
private async ValueTask DispatchEventToAgentsAsync(IEnumerable<string> agentTypes, CloudEvent evt)
|
||||
{
|
||||
var tasks = new List<Task>(agentTypes.Count());
|
||||
foreach (var agentType in agentTypes)
|
||||
{
|
||||
if (_supportedAgentTypes.TryGetValue(agentType, out var connections))
|
||||
{
|
||||
return new(new RpcResponse { Error = "Agent not found." });
|
||||
foreach (var connection in connections)
|
||||
{
|
||||
tasks.Add(this.SendMessageAsync(connection, evt));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Proxy the request to the agent.
|
||||
var originalRequestId = request.RequestId;
|
||||
var newRequestId = Guid.NewGuid().ToString();
|
||||
var completion = _pendingRequests[(connection, newRequestId)] = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
request.RequestId = newRequestId;
|
||||
await connection.ResponseStream.WriteAsync(new Message { Request = request }, cancellationToken).ConfigureAwait(false);
|
||||
// Wait for the response and send it back to the caller.
|
||||
var response = await completion.Task.WaitAsync(s_agentResponseTimeout);
|
||||
response.RequestId = originalRequestId;
|
||||
return response;
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
async ValueTask<RpcResponse> IGateway.InvokeRequest(RpcRequest request)
|
||||
public async ValueTask BroadcastEventAsync(CloudEvent evt, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await this.InvokeRequest(request).ConfigureAwait(false);
|
||||
}
|
||||
var tasks = new List<Task>(_workers.Count);
|
||||
foreach (var (_, connection) in _supportedAgentTypes)
|
||||
{
|
||||
|
||||
tasks.Add(this.SendMessageAsync((IConnection)connection[0], evt, default));
|
||||
}
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
}
|
||||
Task IGateway.SendMessageAsync(IConnection connection, CloudEvent cloudEvent)
|
||||
{
|
||||
return this.SendMessageAsync(connection, cloudEvent);
|
||||
return this.SendMessageAsync(connection, cloudEvent, default);
|
||||
}
|
||||
public async Task SendMessageAsync(IConnection connection, CloudEvent cloudEvent, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var queue = (GrpcWorkerConnection)connection;
|
||||
await queue.ResponseStream.WriteAsync(new Message { CloudEvent = cloudEvent }, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async ValueTask<RemoveSubscriptionResponse> UnsubscribeAsync(RemoveSubscriptionRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _gatewayRegistry.UnsubscribeAsync(request).ConfigureAwait(true);
|
||||
return new RemoveSubscriptionResponse
|
||||
|
||||
{
|
||||
Success = true,
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new RemoveSubscriptionResponse
|
||||
{
|
||||
Success = false,
|
||||
Error = ex.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
public ValueTask<List<Subscription>> GetSubscriptionsAsync(GetSubscriptionsRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return _gatewayRegistry.GetSubscriptions(request);
|
||||
}
|
||||
async ValueTask<RpcResponse> IGateway.InvokeRequestAsync(RpcRequest request)
|
||||
{
|
||||
return await InvokeRequestAsync(request, default).ConfigureAwait(false);
|
||||
}
|
||||
async ValueTask IGateway.BroadcastEventAsync(CloudEvent evt)
|
||||
{
|
||||
await BroadcastEventAsync(evt, default).ConfigureAwait(false);
|
||||
}
|
||||
ValueTask IGateway.StoreAsync(AgentState value)
|
||||
{
|
||||
return StoreAsync(value, default);
|
||||
}
|
||||
ValueTask<AgentState> IGateway.ReadAsync(AgentId agentId)
|
||||
{
|
||||
return ReadAsync(agentId, default);
|
||||
}
|
||||
ValueTask<RegisterAgentTypeResponse> IGateway.RegisterAgentTypeAsync(RegisterAgentTypeRequest request)
|
||||
{
|
||||
return RegisterAgentTypeAsync(request, default);
|
||||
}
|
||||
ValueTask<AddSubscriptionResponse> IGateway.SubscribeAsync(AddSubscriptionRequest request)
|
||||
{
|
||||
return SubscribeAsync(request, default);
|
||||
}
|
||||
ValueTask<RemoveSubscriptionResponse> IGateway.UnsubscribeAsync(RemoveSubscriptionRequest request)
|
||||
{
|
||||
return UnsubscribeAsync(request, default);
|
||||
}
|
||||
ValueTask<List<Subscription>> IGateway.GetSubscriptionsAsync(GetSubscriptionsRequest request)
|
||||
{
|
||||
return GetSubscriptionsAsync(request);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,13 +7,10 @@ using Microsoft.AutoGen.Contracts;
|
|||
namespace Microsoft.AutoGen.Runtime.Grpc;
|
||||
|
||||
// gRPC service which handles communication between the agent worker and the cluster.
|
||||
internal sealed class GrpcGatewayService : AgentRpc.AgentRpcBase
|
||||
public sealed class GrpcGatewayService(GrpcGateway gateway) : AgentRpc.AgentRpcBase
|
||||
{
|
||||
private readonly GrpcGateway Gateway;
|
||||
public GrpcGatewayService(GrpcGateway gateway)
|
||||
{
|
||||
Gateway = (GrpcGateway)gateway;
|
||||
}
|
||||
private readonly GrpcGateway Gateway = (GrpcGateway)gateway;
|
||||
|
||||
public override async Task OpenChannel(IAsyncStreamReader<Message> requestStream, IServerStreamWriter<Message> responseStream, ServerCallContext context)
|
||||
{
|
||||
try
|
||||
|
@ -34,7 +31,6 @@ internal sealed class GrpcGatewayService : AgentRpc.AgentRpcBase
|
|||
var state = await Gateway.ReadAsync(request);
|
||||
return new GetStateResponse { AgentState = state };
|
||||
}
|
||||
|
||||
public override async Task<SaveStateResponse> SaveState(AgentState request, ServerCallContext context)
|
||||
{
|
||||
await Gateway.StoreAsync(request);
|
||||
|
@ -43,4 +39,23 @@ internal sealed class GrpcGatewayService : AgentRpc.AgentRpcBase
|
|||
Success = true // TODO: Implement error handling
|
||||
};
|
||||
}
|
||||
public override async Task<AddSubscriptionResponse> AddSubscription(AddSubscriptionRequest request, ServerCallContext context)
|
||||
{
|
||||
request.RequestId = context.Peer;
|
||||
return await Gateway.SubscribeAsync(request).ConfigureAwait(true);
|
||||
}
|
||||
public override async Task<RemoveSubscriptionResponse> RemoveSubscription(RemoveSubscriptionRequest request, ServerCallContext context)
|
||||
{
|
||||
return await Gateway.UnsubscribeAsync(request).ConfigureAwait(true);
|
||||
}
|
||||
public override async Task<GetSubscriptionsResponse> GetSubscriptions(GetSubscriptionsRequest request, ServerCallContext context)
|
||||
{
|
||||
var subscriptions = await Gateway.GetSubscriptionsAsync(request);
|
||||
return new GetSubscriptionsResponse { Subscriptions = { subscriptions } };
|
||||
}
|
||||
public override async Task<RegisterAgentTypeResponse> RegisterAgent(RegisterAgentTypeRequest request, ServerCallContext context)
|
||||
{
|
||||
request.RequestId = context.Peer;
|
||||
return await Gateway.RegisterAgentTypeAsync(request).ConfigureAwait(true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,14 +10,14 @@ namespace Microsoft.AutoGen.Runtime.Grpc;
|
|||
internal sealed class GrpcWorkerConnection : IAsyncDisposable, IConnection
|
||||
{
|
||||
private static long s_nextConnectionId;
|
||||
private readonly Task _readTask;
|
||||
private readonly Task _writeTask;
|
||||
private Task _readTask = Task.CompletedTask;
|
||||
private Task _writeTask = Task.CompletedTask;
|
||||
private readonly string _connectionId = Interlocked.Increment(ref s_nextConnectionId).ToString();
|
||||
private readonly object _lock = new();
|
||||
private readonly HashSet<string> _supportedTypes = [];
|
||||
private readonly GrpcGateway _gateway;
|
||||
private readonly CancellationTokenSource _shutdownCancellationToken = new();
|
||||
|
||||
public Task Completion { get; private set; } = Task.CompletedTask;
|
||||
public GrpcWorkerConnection(GrpcGateway agentWorker, IAsyncStreamReader<Message> requestStream, IServerStreamWriter<Message> responseStream, ServerCallContext context)
|
||||
{
|
||||
_gateway = agentWorker;
|
||||
|
@ -25,7 +25,9 @@ internal sealed class GrpcWorkerConnection : IAsyncDisposable, IConnection
|
|||
ResponseStream = responseStream;
|
||||
ServerCallContext = context;
|
||||
_outboundMessages = Channel.CreateUnbounded<Message>(new UnboundedChannelOptions { AllowSynchronousContinuations = true, SingleReader = true, SingleWriter = false });
|
||||
|
||||
}
|
||||
public Task Connect()
|
||||
{
|
||||
var didSuppress = false;
|
||||
if (!ExecutionContext.IsFlowSuppressed())
|
||||
{
|
||||
|
@ -46,7 +48,7 @@ internal sealed class GrpcWorkerConnection : IAsyncDisposable, IConnection
|
|||
}
|
||||
}
|
||||
|
||||
Completion = Task.WhenAll(_readTask, _writeTask);
|
||||
return Completion = Task.WhenAll(_readTask, _writeTask);
|
||||
}
|
||||
|
||||
public IAsyncStreamReader<Message> RequestStream { get; }
|
||||
|
@ -75,9 +77,6 @@ internal sealed class GrpcWorkerConnection : IAsyncDisposable, IConnection
|
|||
{
|
||||
await _outboundMessages.Writer.WriteAsync(message).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public Task Completion { get; }
|
||||
|
||||
public async Task RunReadPump()
|
||||
{
|
||||
await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding);
|
||||
|
@ -85,9 +84,8 @@ internal sealed class GrpcWorkerConnection : IAsyncDisposable, IConnection
|
|||
{
|
||||
await foreach (var message in RequestStream.ReadAllAsync(_shutdownCancellationToken.Token))
|
||||
{
|
||||
|
||||
// Fire and forget
|
||||
_gateway.OnReceivedMessageAsync(this, message).Ignore();
|
||||
_gateway.OnReceivedMessageAsync(this, message, _shutdownCancellationToken.Token).Ignore();
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// IGateway.cs
|
||||
using Microsoft.AutoGen.Contracts;
|
||||
|
||||
namespace Microsoft.AutoGen.Runtime.Grpc;
|
||||
|
||||
public interface IGateway : IGrainObserver
|
||||
{
|
||||
ValueTask<RpcResponse> InvokeRequest(RpcRequest request);
|
||||
ValueTask BroadcastEvent(CloudEvent evt);
|
||||
ValueTask StoreAsync(AgentState value);
|
||||
ValueTask<AgentState> ReadAsync(AgentId agentId);
|
||||
Task SendMessageAsync(IConnection connection, CloudEvent cloudEvent);
|
||||
}
|
|
@ -2,10 +2,11 @@
|
|||
// AgentStateGrain.cs
|
||||
|
||||
using Microsoft.AutoGen.Contracts;
|
||||
using Microsoft.AutoGen.Runtime.Grpc.Abstractions;
|
||||
|
||||
namespace Microsoft.AutoGen.Runtime.Grpc;
|
||||
|
||||
internal sealed class AgentStateGrain([PersistentState("state", "AgentStateStore")] IPersistentState<AgentState> state) : Grain, IAgentState
|
||||
internal sealed class AgentStateGrain([PersistentState("state", "AgentStateStore")] IPersistentState<AgentState> state) : Grain, IAgentState, IAgentGrain
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async ValueTask<string> WriteStateAsync(AgentState newState, string eTag, CancellationToken cancellationToken = default)
|
||||
|
@ -33,4 +34,14 @@ internal sealed class AgentStateGrain([PersistentState("state", "AgentStateStore
|
|||
{
|
||||
return ValueTask.FromResult(state.State);
|
||||
}
|
||||
|
||||
ValueTask<AgentState> IAgentGrain.ReadStateAsync()
|
||||
{
|
||||
return ReadStateAsync();
|
||||
}
|
||||
|
||||
ValueTask<string> IAgentGrain.WriteStateAsync(AgentState state, string eTag)
|
||||
{
|
||||
return WriteStateAsync(state, eTag);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// AgentsRegistryState.cs
|
||||
|
||||
using Microsoft.AutoGen.Contracts;
|
||||
|
||||
namespace Microsoft.AutoGen.Runtime.Grpc;
|
||||
|
||||
public class AgentsRegistryState
|
||||
{
|
||||
public Dictionary<string, HashSet<string>> AgentsToEventsMap { get; set; } = [];
|
||||
public Dictionary<string, HashSet<string>> AgentsToTopicsMap { get; set; } = [];
|
||||
public Dictionary<string, HashSet<string>> TopicToAgentTypesMap { get; set; } = [];
|
||||
public Dictionary<string, HashSet<string>> EventsToAgentTypesMap { get; set; } = [];
|
||||
public Dictionary<string, HashSet<Subscription>> GuidSubscriptionsMap { get; set; } = [];
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// IRegistryGrain.cs
|
||||
using Microsoft.AutoGen.Contracts;
|
||||
|
||||
namespace Microsoft.AutoGen.Runtime.Grpc;
|
||||
|
||||
public interface IRegistryGrain : IGrainWithIntegerKey
|
||||
{
|
||||
ValueTask<(IGateway? Worker, bool NewPlacement)> GetOrPlaceAgent(AgentId agentId);
|
||||
ValueTask RemoveWorker(IGateway worker);
|
||||
ValueTask RegisterAgentType(string type, IGateway worker);
|
||||
ValueTask AddWorker(IGateway worker);
|
||||
ValueTask UnregisterAgentType(string type, IGateway worker);
|
||||
ValueTask<IGateway?> GetCompatibleWorker(string type);
|
||||
}
|
|
@ -16,7 +16,6 @@ public static class OrleansRuntimeHostingExtenions
|
|||
public static WebApplicationBuilder AddOrleans(this WebApplicationBuilder builder)
|
||||
{
|
||||
builder.Services.AddSerializer(serializer => serializer.AddProtobufSerializer());
|
||||
builder.Services.AddSingleton<IRegistryGrain, RegistryGrain>();
|
||||
|
||||
// Ensure Orleans is added before the hosted service to guarantee that it starts first.
|
||||
//TODO: make all of this configurable
|
||||
|
@ -28,6 +27,7 @@ public static class OrleansRuntimeHostingExtenions
|
|||
siloBuilder.UseLocalhostClustering()
|
||||
.AddMemoryStreams("StreamProvider")
|
||||
.AddMemoryGrainStorage("PubSubStore")
|
||||
.AddMemoryGrainStorage("AgentRegistryStore")
|
||||
.AddMemoryGrainStorage("AgentStateStore");
|
||||
|
||||
siloBuilder.UseInMemoryReminderService();
|
||||
|
@ -40,12 +40,7 @@ public static class OrleansRuntimeHostingExtenions
|
|||
var cosmosDbconnectionString = builder.Configuration.GetValue<string>("Orleans:CosmosDBConnectionString") ??
|
||||
throw new ConfigurationErrorsException(
|
||||
"Orleans:CosmosDBConnectionString is missing from configuration. This is required for persistence in production environments.");
|
||||
siloBuilder.Configure<ClusterOptions>(options =>
|
||||
{
|
||||
//TODO: make this configurable
|
||||
options.ClusterId = "AutoGen-cluster";
|
||||
options.ServiceId = "AutoGen-cluster";
|
||||
});
|
||||
|
||||
siloBuilder.Configure<SiloMessagingOptions>(options =>
|
||||
{
|
||||
options.ResponseTimeout = TimeSpan.FromMinutes(3);
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
// RegistryGrain.cs
|
||||
|
||||
using Microsoft.AutoGen.Contracts;
|
||||
using Microsoft.AutoGen.Runtime.Grpc.Abstractions;
|
||||
|
||||
namespace Microsoft.AutoGen.Runtime.Grpc;
|
||||
|
||||
internal sealed class RegistryGrain : Grain, IRegistryGrain
|
||||
internal sealed class RegistryGrain([PersistentState("state", "AgentRegistryStore")] IPersistentState<AgentsRegistryState> state) : Grain, IRegistryGrain
|
||||
{
|
||||
// TODO: use persistent state for some of these or (better) extend Orleans to implement some of this natively.
|
||||
private readonly Dictionary<IGateway, WorkerState> _workerStates = new();
|
||||
|
@ -18,9 +18,48 @@ internal sealed class RegistryGrain : Grain, IRegistryGrain
|
|||
this.RegisterGrainTimer(static state => state.PurgeInactiveWorkers(), this, TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(30));
|
||||
return base.OnActivateAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public ValueTask<List<string>> GetSubscribedAndHandlingAgents(string topic, string eventType)
|
||||
{
|
||||
List<string> agents = [];
|
||||
// get all agent types that are subscribed to the topic
|
||||
if (state.State.TopicToAgentTypesMap.TryGetValue(topic, out var subscribedAgentTypes))
|
||||
{
|
||||
/*// get all agent types that are handling the event
|
||||
if (state.State.EventsToAgentTypesMap.TryGetValue(eventType, out var handlingAgents))
|
||||
{
|
||||
agents.AddRange(subscribedAgentTypes.Intersect(handlingAgents).ToList());
|
||||
}*/
|
||||
agents.AddRange(subscribedAgentTypes.ToList());
|
||||
}
|
||||
if (state.State.TopicToAgentTypesMap.TryGetValue(eventType, out var eventHandlingAgents))
|
||||
{
|
||||
agents.AddRange(eventHandlingAgents.ToList());
|
||||
}
|
||||
if (state.State.TopicToAgentTypesMap.TryGetValue(topic + "." + eventType, out var combo))
|
||||
{
|
||||
agents.AddRange(combo.ToList());
|
||||
}
|
||||
// instead of an exact match, we can also check for a prefix match where key starts with the eventType
|
||||
if (state.State.TopicToAgentTypesMap.Keys.Any(key => key.StartsWith(eventType)))
|
||||
{
|
||||
state.State.TopicToAgentTypesMap.Where(
|
||||
kvp => kvp.Key.StartsWith(eventType))
|
||||
.SelectMany(kvp => kvp.Value)
|
||||
.Distinct()
|
||||
.ToList()
|
||||
.ForEach(async agentType =>
|
||||
{
|
||||
agents.Add(agentType);
|
||||
});
|
||||
}
|
||||
agents = agents.Distinct().ToList();
|
||||
|
||||
return new ValueTask<List<string>>(agents);
|
||||
}
|
||||
public ValueTask<(IGateway? Worker, bool NewPlacement)> GetOrPlaceAgent(AgentId agentId)
|
||||
{
|
||||
// TODO:
|
||||
// TODO: Clarify the logic
|
||||
bool isNewPlacement;
|
||||
if (!_agentDirectory.TryGetValue((agentId.Type, agentId.Key), out var worker) || !_workerStates.ContainsKey(worker))
|
||||
{
|
||||
|
@ -58,20 +97,49 @@ internal sealed class RegistryGrain : Grain, IRegistryGrain
|
|||
}
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
public ValueTask RegisterAgentType(string type, IGateway worker)
|
||||
public async ValueTask RegisterAgentType(RegisterAgentTypeRequest registration, IGateway gateway)
|
||||
{
|
||||
if (!_supportedAgentTypes.TryGetValue(type, out var supportedAgentTypes))
|
||||
if (!_supportedAgentTypes.TryGetValue(registration.Type, out var supportedAgentTypes))
|
||||
{
|
||||
supportedAgentTypes = _supportedAgentTypes[type] = [];
|
||||
supportedAgentTypes = _supportedAgentTypes[registration.Type] = [];
|
||||
}
|
||||
|
||||
if (!supportedAgentTypes.Contains(worker))
|
||||
if (!supportedAgentTypes.Contains(gateway))
|
||||
{
|
||||
supportedAgentTypes.Add(worker);
|
||||
supportedAgentTypes.Add(gateway);
|
||||
}
|
||||
var workerState = GetOrAddWorker(worker);
|
||||
workerState.SupportedTypes.Add(type);
|
||||
return ValueTask.CompletedTask;
|
||||
|
||||
var workerState = GetOrAddWorker(gateway);
|
||||
workerState.SupportedTypes.Add(registration.Type);
|
||||
/* future
|
||||
state.State.AgentsToEventsMap[registration.Type] = new HashSet<string>(registration.Events);
|
||||
state.State.AgentsToTopicsMap[registration.Type] = new HashSet<string>(registration.Topics);
|
||||
|
||||
// construct the inverse map for topics and agent types
|
||||
foreach (var topic in registration.Topics)
|
||||
{
|
||||
if (!state.State.TopicToAgentTypesMap.TryGetValue(topic, out var topicSet))
|
||||
{
|
||||
topicSet = new HashSet<string>();
|
||||
state.State.TopicToAgentTypesMap[topic] = topicSet;
|
||||
}
|
||||
|
||||
topicSet.Add(registration.Type);
|
||||
}
|
||||
|
||||
// construct the inverse map for events and agent types
|
||||
foreach (var evt in registration.Events)
|
||||
{
|
||||
if (!state.State.EventsToAgentTypesMap.TryGetValue(evt, out var eventSet))
|
||||
{
|
||||
eventSet = new HashSet<string>();
|
||||
state.State.EventsToAgentTypesMap[evt] = eventSet;
|
||||
}
|
||||
|
||||
eventSet.Add(registration.Type);
|
||||
}
|
||||
*/
|
||||
await state.WriteStateAsync().ConfigureAwait(false);
|
||||
}
|
||||
public ValueTask AddWorker(IGateway worker)
|
||||
{
|
||||
|
@ -135,9 +203,123 @@ internal sealed class RegistryGrain : Grain, IRegistryGrain
|
|||
return null;
|
||||
}
|
||||
|
||||
public async ValueTask SubscribeAsync(AddSubscriptionRequest subscription)
|
||||
{
|
||||
var guid = Guid.NewGuid().ToString();
|
||||
subscription.Subscription.Id = guid;
|
||||
switch (subscription.Subscription.SubscriptionCase)
|
||||
{
|
||||
//TODO: this doesnt look right
|
||||
case Subscription.SubscriptionOneofCase.TypePrefixSubscription:
|
||||
break;
|
||||
case Subscription.SubscriptionOneofCase.TypeSubscription:
|
||||
{
|
||||
// add the topic to the set of topics for the agent type
|
||||
state.State.AgentsToTopicsMap.TryGetValue(subscription.Subscription.TypeSubscription.AgentType, out var topics);
|
||||
if (topics is null)
|
||||
{
|
||||
topics = new HashSet<string>();
|
||||
state.State.AgentsToTopicsMap[subscription.Subscription.TypeSubscription.AgentType] = topics;
|
||||
}
|
||||
topics.Add(subscription.Subscription.TypeSubscription.TopicType);
|
||||
|
||||
// add the agent type to the set of agent types for the topic
|
||||
state.State.TopicToAgentTypesMap.TryGetValue(subscription.Subscription.TypeSubscription.TopicType, out var agents);
|
||||
if (agents is null)
|
||||
{
|
||||
agents = new HashSet<string>();
|
||||
state.State.TopicToAgentTypesMap[subscription.Subscription.TypeSubscription.TopicType] = agents;
|
||||
}
|
||||
agents.Add(subscription.Subscription.TypeSubscription.AgentType);
|
||||
|
||||
// add the subscription by Guid
|
||||
state.State.GuidSubscriptionsMap.TryGetValue(guid, out var existingSubscriptions);
|
||||
if (existingSubscriptions is null)
|
||||
{
|
||||
existingSubscriptions = new HashSet<Subscription>();
|
||||
state.State.GuidSubscriptionsMap[guid] = existingSubscriptions;
|
||||
}
|
||||
existingSubscriptions.Add(subscription.Subscription);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new InvalidOperationException("Invalid subscription type");
|
||||
}
|
||||
await state.WriteStateAsync().ConfigureAwait(false);
|
||||
}
|
||||
public async ValueTask UnsubscribeAsync(RemoveSubscriptionRequest request)
|
||||
{
|
||||
var guid = request.Id;
|
||||
// does the guid parse?
|
||||
if (!Guid.TryParse(guid, out var _))
|
||||
{
|
||||
throw new InvalidOperationException("Invalid subscription id");
|
||||
}
|
||||
if (state.State.GuidSubscriptionsMap.TryGetValue(guid, out var subscriptions))
|
||||
{
|
||||
foreach (var subscription in subscriptions)
|
||||
{
|
||||
switch (subscription.SubscriptionCase)
|
||||
{
|
||||
case Subscription.SubscriptionOneofCase.TypeSubscription:
|
||||
{
|
||||
// remove the topic from the set of topics for the agent type
|
||||
state.State.AgentsToTopicsMap.TryGetValue(subscription.TypeSubscription.AgentType, out var topics);
|
||||
topics?.Remove(subscription.TypeSubscription.TopicType);
|
||||
|
||||
// remove the agent type from the set of agent types for the topic
|
||||
state.State.TopicToAgentTypesMap.TryGetValue(subscription.TypeSubscription.TopicType, out var agents);
|
||||
agents?.Remove(subscription.TypeSubscription.AgentType);
|
||||
|
||||
//remove the subscription by Guid
|
||||
state.State.GuidSubscriptionsMap.TryGetValue(guid, out var existingSubscriptions);
|
||||
existingSubscriptions?.Remove(subscription);
|
||||
break;
|
||||
}
|
||||
case Subscription.SubscriptionOneofCase.TypePrefixSubscription:
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException("Invalid subscription type");
|
||||
}
|
||||
}
|
||||
state.State.GuidSubscriptionsMap.Remove(guid);
|
||||
}
|
||||
await state.WriteStateAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public ValueTask<List<Subscription>> GetSubscriptions(string agentType)
|
||||
{
|
||||
var subscriptions = new List<Subscription>();
|
||||
if (state.State.AgentsToTopicsMap.TryGetValue(agentType, out var topics))
|
||||
{
|
||||
foreach (var topic in topics)
|
||||
{
|
||||
subscriptions.Add(new Subscription
|
||||
{
|
||||
TypeSubscription = new TypeSubscription
|
||||
{
|
||||
AgentType = agentType,
|
||||
TopicType = topic
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return new(subscriptions);
|
||||
}
|
||||
public ValueTask<List<Subscription>> GetSubscriptions(GetSubscriptionsRequest request)
|
||||
{
|
||||
var subscriptions = new List<Subscription>();
|
||||
foreach (var kvp in state.State.GuidSubscriptionsMap)
|
||||
{
|
||||
subscriptions.AddRange(kvp.Value);
|
||||
}
|
||||
return new(subscriptions);
|
||||
}
|
||||
|
||||
private sealed class WorkerState
|
||||
{
|
||||
public HashSet<string> SupportedTypes { get; set; } = [];
|
||||
public DateTimeOffset LastSeen { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// AddSubscriptionRequestSurrogate.cs
|
||||
|
||||
using Microsoft.AutoGen.Contracts;
|
||||
|
||||
namespace Microsoft.AutoGen.Runtime.Grpc.Orleans.Surrogates;
|
||||
|
||||
[GenerateSerializer]
|
||||
public struct AddSubscriptionRequestSurrogate
|
||||
{
|
||||
[Id(0)]
|
||||
public string RequestId;
|
||||
[Id(1)]
|
||||
public Subscription Subscription;
|
||||
}
|
||||
|
||||
[RegisterConverter]
|
||||
public sealed class AddSubscriptionRequestSurrogateConverter :
|
||||
IConverter<AddSubscriptionRequest, AddSubscriptionRequestSurrogate>
|
||||
{
|
||||
public AddSubscriptionRequest ConvertFromSurrogate(
|
||||
in AddSubscriptionRequestSurrogate surrogate)
|
||||
{
|
||||
var request = new AddSubscriptionRequest()
|
||||
{
|
||||
RequestId = surrogate.RequestId,
|
||||
Subscription = surrogate.Subscription
|
||||
};
|
||||
return request;
|
||||
}
|
||||
|
||||
public AddSubscriptionRequestSurrogate ConvertToSurrogate(
|
||||
in AddSubscriptionRequest value) =>
|
||||
new AddSubscriptionRequestSurrogate
|
||||
{
|
||||
RequestId = value.RequestId,
|
||||
Subscription = value.Subscription
|
||||
};
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// AddSubscriptionResponseSurrogate.cs
|
||||
|
||||
using Microsoft.AutoGen.Contracts;
|
||||
|
||||
namespace Microsoft.AutoGen.Runtime.Grpc.Orleans.Surrogates;
|
||||
|
||||
[GenerateSerializer]
|
||||
public struct AddSubscriptionResponseSurrogate
|
||||
{
|
||||
[Id(0)]
|
||||
public string RequestId;
|
||||
[Id(1)]
|
||||
public bool Success;
|
||||
[Id(2)]
|
||||
public string Error;
|
||||
}
|
||||
|
||||
[RegisterConverter]
|
||||
public sealed class AddSubscriptionResponseSurrogateConverter :
|
||||
IConverter<AddSubscriptionResponse, AddSubscriptionResponseSurrogate>
|
||||
{
|
||||
public AddSubscriptionResponse ConvertFromSurrogate(
|
||||
in AddSubscriptionResponseSurrogate surrogate) =>
|
||||
new AddSubscriptionResponse
|
||||
{
|
||||
RequestId = surrogate.RequestId,
|
||||
Success = surrogate.Success,
|
||||
Error = surrogate.Error
|
||||
};
|
||||
|
||||
public AddSubscriptionResponseSurrogate ConvertToSurrogate(
|
||||
in AddSubscriptionResponse value) =>
|
||||
new AddSubscriptionResponseSurrogate
|
||||
{
|
||||
RequestId = value.RequestId,
|
||||
Success = value.Success,
|
||||
Error = value.Error
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// AgentIdSurrogate.cs
|
||||
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// AgentIdSurrogate.cs
|
||||
using Microsoft.AutoGen.Contracts;
|
||||
|
||||
namespace Microsoft.AutoGen.Runtime.Grpc.Orleans.Surrogates;
|
||||
|
||||
[GenerateSerializer]
|
||||
public struct AgentIdSurrogate
|
||||
{
|
||||
[Id(0)]
|
||||
public string Key;
|
||||
[Id(1)]
|
||||
public string Type;
|
||||
}
|
||||
|
||||
[RegisterConverter]
|
||||
public sealed class AgentIdSurrogateConverter :
|
||||
IConverter<AgentId, AgentIdSurrogate>
|
||||
{
|
||||
public AgentId ConvertFromSurrogate(
|
||||
in AgentIdSurrogate surrogate) =>
|
||||
new AgentId
|
||||
{
|
||||
Key = surrogate.Key,
|
||||
Type = surrogate.Type
|
||||
};
|
||||
|
||||
public AgentIdSurrogate ConvertToSurrogate(
|
||||
in AgentId value) =>
|
||||
new AgentIdSurrogate
|
||||
{
|
||||
Key = value.Key,
|
||||
Type = value.Type
|
||||
};
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue