diff --git a/AISmart.sln b/AISmart.sln index 96849365..56f71a6a 100644 --- a/AISmart.sln +++ b/AISmart.sln @@ -75,14 +75,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AISmart.GAgent.Config", "sr EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AISmart.GAgent.Core", "src\AISmart.GAgent.Core\AISmart.GAgent.Core.csproj", "{CFFF8DF8-25AC-48E7-9799-BBFC5C0EA594}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AISmart.CQRS", "src\AISmart.CQRS\AISmart.CQRS.csproj", "{4808BEAC-3B68-45DB-AED3-FA6D5BB94673}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AISmart.Cqrs.Tests", "test\AISmart.Cqrs.Tests\AISmart.Cqrs.Tests.csproj", "{2605D902-3B04-4386-9DE7-99FCBC5A4C8A}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AISmart.AuthServer", "src\AISmart.AuthServer\AISmart.AuthServer.csproj", "{1C434C04-119E-4885-A7EA-1E7C03972805}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AISmart.Rag.Contracts", "src\AISmart.Rag.Contracts\AISmart.Rag.Contracts.csproj", "{ADFE5268-C662-450B-A727-A6CC974FEE15}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AISmart.GAgent.MicroAI", "src\AISmart.GAgent.MicroAI\AISmart.GAgent.MicroAI.csproj", "{F8C887E6-77D0-47C2-8B6B-FFB51E094396}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AiSmart.GAgent.TestAgent", "src\AiSmart.GAgent.TestAgent\AiSmart.GAgent.TestAgent.csproj", "{4EBBE2C2-7C37-4D3F-9F38-916A2758BDD1}" @@ -95,6 +91,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AISmart.Core", "src\AISmart EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AiSmart.GAgent.SocialAgent", "src\AiSmart.GAgent.SocialGAgent\AiSmart.GAgent.SocialAgent.csproj", "{CC99AB12-4463-4386-BF17-CE0E26797E23}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AISmart.CQRS", "src\AISmart.CQRS\AISmart.CQRS.csproj", "{23A8822F-83B5-4ABA-B05B-BAE9670ACD68}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AISmart.Rag.Contracts", "src\AISmart.Rag.Contracts\AISmart.Rag.Contracts.csproj", "{3A58DC0A-BA95-48B9-9468-F7892A71F4FA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -221,10 +221,6 @@ Global {BC0E7140-4A81-46BF-9A35-250996F3DC53}.Debug|Any CPU.Build.0 = Debug|Any CPU {BC0E7140-4A81-46BF-9A35-250996F3DC53}.Release|Any CPU.ActiveCfg = Release|Any CPU {BC0E7140-4A81-46BF-9A35-250996F3DC53}.Release|Any CPU.Build.0 = Release|Any CPU - {4808BEAC-3B68-45DB-AED3-FA6D5BB94673}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4808BEAC-3B68-45DB-AED3-FA6D5BB94673}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4808BEAC-3B68-45DB-AED3-FA6D5BB94673}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4808BEAC-3B68-45DB-AED3-FA6D5BB94673}.Release|Any CPU.Build.0 = Release|Any CPU {1C434C04-119E-4885-A7EA-1E7C03972805}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1C434C04-119E-4885-A7EA-1E7C03972805}.Debug|Any CPU.Build.0 = Debug|Any CPU {1C434C04-119E-4885-A7EA-1E7C03972805}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -233,10 +229,6 @@ Global {CFFF8DF8-25AC-48E7-9799-BBFC5C0EA594}.Debug|Any CPU.Build.0 = Debug|Any CPU {CFFF8DF8-25AC-48E7-9799-BBFC5C0EA594}.Release|Any CPU.ActiveCfg = Release|Any CPU {CFFF8DF8-25AC-48E7-9799-BBFC5C0EA594}.Release|Any CPU.Build.0 = Release|Any CPU - {ADFE5268-C662-450B-A727-A6CC974FEE15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {ADFE5268-C662-450B-A727-A6CC974FEE15}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ADFE5268-C662-450B-A727-A6CC974FEE15}.Release|Any CPU.ActiveCfg = Release|Any CPU - {ADFE5268-C662-450B-A727-A6CC974FEE15}.Release|Any CPU.Build.0 = Release|Any CPU {89A0820D-CBB0-4061-A939-F30EF486BA93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {89A0820D-CBB0-4061-A939-F30EF486BA93}.Debug|Any CPU.Build.0 = Debug|Any CPU {89A0820D-CBB0-4061-A939-F30EF486BA93}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -249,10 +241,6 @@ Global {4EBBE2C2-7C37-4D3F-9F38-916A2758BDD1}.Debug|Any CPU.Build.0 = Debug|Any CPU {4EBBE2C2-7C37-4D3F-9F38-916A2758BDD1}.Release|Any CPU.ActiveCfg = Release|Any CPU {4EBBE2C2-7C37-4D3F-9F38-916A2758BDD1}.Release|Any CPU.Build.0 = Release|Any CPU - {4808BEAC-3B68-45DB-AED3-FA6D5BB94673}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4808BEAC-3B68-45DB-AED3-FA6D5BB94673}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4808BEAC-3B68-45DB-AED3-FA6D5BB94673}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4808BEAC-3B68-45DB-AED3-FA6D5BB94673}.Release|Any CPU.Build.0 = Release|Any CPU {2605D902-3B04-4386-9DE7-99FCBC5A4C8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2605D902-3B04-4386-9DE7-99FCBC5A4C8A}.Debug|Any CPU.Build.0 = Debug|Any CPU {2605D902-3B04-4386-9DE7-99FCBC5A4C8A}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -273,6 +261,14 @@ Global {CC99AB12-4463-4386-BF17-CE0E26797E23}.Debug|Any CPU.Build.0 = Debug|Any CPU {CC99AB12-4463-4386-BF17-CE0E26797E23}.Release|Any CPU.ActiveCfg = Release|Any CPU {CC99AB12-4463-4386-BF17-CE0E26797E23}.Release|Any CPU.Build.0 = Release|Any CPU + {23A8822F-83B5-4ABA-B05B-BAE9670ACD68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {23A8822F-83B5-4ABA-B05B-BAE9670ACD68}.Debug|Any CPU.Build.0 = Debug|Any CPU + {23A8822F-83B5-4ABA-B05B-BAE9670ACD68}.Release|Any CPU.ActiveCfg = Release|Any CPU + {23A8822F-83B5-4ABA-B05B-BAE9670ACD68}.Release|Any CPU.Build.0 = Release|Any CPU + {3A58DC0A-BA95-48B9-9468-F7892A71F4FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3A58DC0A-BA95-48B9-9468-F7892A71F4FA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3A58DC0A-BA95-48B9-9468-F7892A71F4FA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3A58DC0A-BA95-48B9-9468-F7892A71F4FA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -308,20 +304,18 @@ Global {C09D0934-CD67-47A0-9179-806C05ABFB26} = {04DBDB01-70F4-4E06-B468-8F87850B22BE} {BC0E7140-4A81-46BF-9A35-250996F3DC53} = {04DBDB01-70F4-4E06-B468-8F87850B22BE} {CFFF8DF8-25AC-48E7-9799-BBFC5C0EA594} = {CA9AC87F-097E-4F15-8393-4BC07735A5B0} - {ADFE5268-C662-450B-A727-A6CC974FEE15} = {CA9AC87F-097E-4F15-8393-4BC07735A5B0} {F8C887E6-77D0-47C2-8B6B-FFB51E094396} = {CA9AC87F-097E-4F15-8393-4BC07735A5B0} {4EBBE2C2-7C37-4D3F-9F38-916A2758BDD1} = {CA9AC87F-097E-4F15-8393-4BC07735A5B0} {1C434C04-119E-4885-A7EA-1E7C03972805} = {CA9AC87F-097E-4F15-8393-4BC07735A5B0} - {4808BEAC-3B68-45DB-AED3-FA6D5BB94673} = {CA9AC87F-097E-4F15-8393-4BC07735A5B0} {2605D902-3B04-4386-9DE7-99FCBC5A4C8A} = {04DBDB01-70F4-4E06-B468-8F87850B22BE} - {4808BEAC-3B68-45DB-AED3-FA6D5BB94673} = {CA9AC87F-097E-4F15-8393-4BC07735A5B0} - {ADFE5268-C662-450B-A727-A6CC974FEE15} = {CA9AC87F-097E-4F15-8393-4BC07735A5B0} {29300C8F-53C6-4A40-86C4-D321679D5A7C} = {CA9AC87F-097E-4F15-8393-4BC07735A5B0} {609DECCF-39E8-4182-98CF-51533A9B6156} = {CA9AC87F-097E-4F15-8393-4BC07735A5B0} {49868FC0-2F38-4EF4-9719-6578CDF9C453} = {CA9AC87F-097E-4F15-8393-4BC07735A5B0} {CC99AB12-4463-4386-BF17-CE0E26797E23} = {CA9AC87F-097E-4F15-8393-4BC07735A5B0} {482AC386-F3A8-4791-A708-414E52A8177A} = {04DBDB01-70F4-4E06-B468-8F87850B22BE} {89A0820D-CBB0-4061-A939-F30EF486BA93} = {CA9AC87F-097E-4F15-8393-4BC07735A5B0} + {23A8822F-83B5-4ABA-B05B-BAE9670ACD68} = {CA9AC87F-097E-4F15-8393-4BC07735A5B0} + {3A58DC0A-BA95-48B9-9468-F7892A71F4FA} = {CA9AC87F-097E-4F15-8393-4BC07735A5B0} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {28315BFD-90E7-4E14-A2EA-F3D23AF4126F} diff --git a/src/AISmart.Application.Contracts/Agents/EventWrapper.cs b/src/AISmart.Application.Contracts/Agents/EventWrapper.cs index c6aee59c..4b369790 100644 --- a/src/AISmart.Application.Contracts/Agents/EventWrapper.cs +++ b/src/AISmart.Application.Contracts/Agents/EventWrapper.cs @@ -11,15 +11,16 @@ public class EventWrapper : EventWrapperBase [Id(0)] public T Event { get; private set; } [Id(1)] public Guid EventId { get; private set; } [Id(2)] public GrainId GrainId { get; private set; } - [Id(3)] public GrainId? ContextGrainId { get; set; } + [Id(3)] public GrainId? ContextStorageGrainId { get; set; } // Constructor - public EventWrapper(T @event, Guid eventId, GrainId grainId) + public EventWrapper(T @event, Guid eventId, GrainId grainId, GrainId? contextStorageGrainId) { Event = @event; EventId = eventId; GrainId = grainId; - ContextGrainId = null; + ContextStorageGrainId = contextStorageGrainId; + } // Optionally, you can add methods or other functionality as needed diff --git a/src/AISmart.Application.Grains/Agents/MarketLeader/MarketLeaderGAgent.cs b/src/AISmart.Application.Grains/Agents/MarketLeader/MarketLeaderGAgent.cs index 0db2a22c..ca973488 100644 --- a/src/AISmart.Application.Grains/Agents/MarketLeader/MarketLeaderGAgent.cs +++ b/src/AISmart.Application.Grains/Agents/MarketLeader/MarketLeaderGAgent.cs @@ -24,7 +24,7 @@ public override Task GetDescriptionAsync() public async Task HandleEventAsync(SocialEvent eventData) { - Logger.LogInformation($"{this.GetType().ToString()} ExecuteAsync: Market Leader analyses content:{eventData.Content}"); + Logger.LogInformation($"{GetType()} ExecuteAsync: Market Leader analyses content:{eventData.Content}"); await PublishAsync(new SendMessageEvent { Message = "MarketLeaderGAgent Completed." diff --git a/src/AISmart.Application.Grains/Agents/X/XGAgent.cs b/src/AISmart.Application.Grains/Agents/X/XGAgent.cs index 3a137e66..c2a5ea96 100644 --- a/src/AISmart.Application.Grains/Agents/X/XGAgent.cs +++ b/src/AISmart.Application.Grains/Agents/X/XGAgent.cs @@ -44,7 +44,7 @@ public async Task HandleEventAsync(XThreadCreatedEvent eventData) var publishEvent = new SocialEvent { Content = $"X Thread {eventData.Content} has been published." - }; + }.WithContext("ThreadId", eventData.Id); await PublishAsync(publishEvent); await PublishAsync(new RequestAllSubscriptionsEvent()); } diff --git a/src/AISmart.GAgent.Core/AISmart.GAgent.Core.csproj b/src/AISmart.GAgent.Core/AISmart.GAgent.Core.csproj index f2779d02..7a0a03b1 100644 --- a/src/AISmart.GAgent.Core/AISmart.GAgent.Core.csproj +++ b/src/AISmart.GAgent.Core/AISmart.GAgent.Core.csproj @@ -12,8 +12,10 @@ + + diff --git a/src/AISmart.GAgent.Core/AISmartGAgentConstants.cs b/src/AISmart.GAgent.Core/AISmartGAgentConstants.cs index bb802890..30049c5e 100644 --- a/src/AISmart.GAgent.Core/AISmartGAgentConstants.cs +++ b/src/AISmart.GAgent.Core/AISmartGAgentConstants.cs @@ -6,4 +6,6 @@ public static class AISmartGAgentConstants public const string SubscribersStateName = "Subscribers"; public const string SubscriptionsStateName = "Subscriptions"; public const string PublishersStateName = "Publishers"; + public const string ContextStorageGrainSelfTerminateReminderName = "DeleteSelfReminder"; + public static TimeSpan DefaultContextStorageGrainSelfDeleteTime = TimeSpan.FromMinutes(10); } \ No newline at end of file diff --git a/src/AISmart.GAgent.Core/ContextStorageGrain.cs b/src/AISmart.GAgent.Core/ContextStorageGrain.cs index 728a835b..19515984 100644 --- a/src/AISmart.GAgent.Core/ContextStorageGrain.cs +++ b/src/AISmart.GAgent.Core/ContextStorageGrain.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; -using Orleans.Storage; +using Microsoft.Extensions.Logging; namespace AISmart.GAgent.Core; @@ -13,32 +12,42 @@ public interface IContextStorageGrain : IGrainWithGuidKey Task AddContext(string key, object? value); Task AddContext(Dictionary context); Task> GetContext(); + Task ResetSelfTerminateTime(TimeSpan timeSpan); } -public class ContextStorageGrain : Grain, IContextStorageGrain +public class ContextStorageGrain : Grain, IContextStorageGrain, IRemindable { private readonly IPersistentState _context; + private readonly ILogger _logger; + private IGrainReminder _reminder; - private readonly IGrainStorage _grainStorage; - - public ContextStorageGrain([PersistentState("State")] IPersistentState context) + public ContextStorageGrain([PersistentState("ContextStorage")] IPersistentState context, + ILogger logger) { _context = context; + _logger = logger; } public Task AddContext(string key, object? value) { + _logger.LogInformation($"Adding context {key}: {value}, ContextStorageGrain's GrainId: {this.GetGrainId()}"); + + _context.ReadStateAsync(); + if (_context.State.Context.IsNullOrEmpty()) { _context.State.Context = []; } _context.State.Context[key] = value; + _context.WriteStateAsync(); return Task.CompletedTask; } public Task AddContext(Dictionary context) { + _context.ReadStateAsync(); + if (_context.State.Context.IsNullOrEmpty()) { _context.State.Context = context; @@ -47,15 +56,45 @@ public Task AddContext(Dictionary context) { foreach (var keyPair in context) { + _logger.LogInformation( + $"Adding context {keyPair.Key}: {keyPair.Value}, ContextStorageGrain's GrainId: {this.GetGrainId()}"); _context.State.Context[keyPair.Key] = keyPair.Value; } } + _context.WriteStateAsync(); return Task.CompletedTask; } public Task> GetContext() { + _context.ReadStateAsync(); return Task.FromResult(_context.State.Context); } + + public async Task ReceiveReminder(string reminderName, TickStatus status) + { + if (reminderName == "DeleteSelfReminder") + { + _logger.LogInformation($"Reminder triggered for grain {this.GetGrainId()}, deleting grain."); + await ClearStateAsync(); + DeactivateOnIdle(); + } + } + + public async Task ResetSelfTerminateTime(TimeSpan timeSpan) + { + _reminder = await this.RegisterOrUpdateReminder( + AISmartGAgentConstants.ContextStorageGrainSelfTerminateReminderName, + timeSpan, timeSpan); + } + + public override async Task OnActivateAsync(CancellationToken cancellationToken) + { + _reminder = await this.RegisterOrUpdateReminder( + AISmartGAgentConstants.ContextStorageGrainSelfTerminateReminderName, + AISmartGAgentConstants.DefaultContextStorageGrainSelfDeleteTime, + AISmartGAgentConstants.DefaultContextStorageGrainSelfDeleteTime); + await base.OnActivateAsync(cancellationToken); + } } \ No newline at end of file diff --git a/src/AISmart.GAgent.Core/GAgentBase.Context.cs b/src/AISmart.GAgent.Core/GAgentBase.Context.cs new file mode 100644 index 00000000..e49a0fc2 --- /dev/null +++ b/src/AISmart.GAgent.Core/GAgentBase.Context.cs @@ -0,0 +1,60 @@ +namespace AISmart.GAgent.Core; + +public abstract partial class GAgentBase +{ + private GrainId? _contextStorageGrainId; + + protected async Task SetContextAsync(string key, object? value) + { + if (_contextStorageGrainId != null) + { + var contextStorageGrain = GrainFactory.GetGrain(_contextStorageGrainId.Value.GetGuidKey()); + await contextStorageGrain.AddContext(key, value); + } + } + + protected async Task SetContextAsync(Dictionary context) + { + if (_contextStorageGrainId != null) + { + var contextStorageGrain = GrainFactory.GetGrain(_contextStorageGrainId.Value.GetGuidKey()); + await contextStorageGrain.AddContext(context); + } + } + + protected async Task ResetContextStorageGrainTerminateTimeAsync(TimeSpan timeSpan) + { + if (_contextStorageGrainId != null) + { + var contextStorageGrain = GrainFactory.GetGrain(_contextStorageGrainId.Value.GetGuidKey()); + await contextStorageGrain.ResetSelfTerminateTime(timeSpan); + } + } + + protected async Task> GetContextAsync() + { + if (_contextStorageGrainId != null) + { + var contextStorageGrain = + GrainFactory.GetGrain(_contextStorageGrainId.Value.GetGuidKey()); + return await contextStorageGrain.GetContext(); + } + + return new Dictionary(); + } + + private void SetContextStorageGrainId(GrainId? grainId) + { + _contextStorageGrainId = grainId; + } + + private GrainId? GetContextStorageGrainId() + { + return _contextStorageGrainId; + } + + private void ClearContext() + { + _contextStorageGrainId = null; + } +} \ No newline at end of file diff --git a/src/AISmart.GAgent.Core/GAgentBase.Observers.cs b/src/AISmart.GAgent.Core/GAgentBase.Observers.cs index d55d812e..9e31f609 100644 --- a/src/AISmart.GAgent.Core/GAgentBase.Observers.cs +++ b/src/AISmart.GAgent.Core/GAgentBase.Observers.cs @@ -21,27 +21,26 @@ private Task UpdateObserverList() return; } - var eventId = (Guid)item.GetType().GetProperty(nameof(EventWrapper.EventId))?.GetValue(item)!; - var eventType = item.GetType().GetProperty(nameof(EventWrapper.Event))?.GetValue(item); + var eventId = (Guid)item.GetType().GetProperty(nameof(EventWrapper.EventId))?.GetValue(item)!; + var eventType = item.GetType().GetProperty(nameof(EventWrapper.Event))?.GetValue(item); var parameter = eventHandlerMethod.GetParameters()[0]; var contextStorageGrainIdValue = item.GetType() - .GetProperty(nameof(EventWrapper.ContextGrainId))? + .GetProperty(nameof(EventWrapper.ContextStorageGrainId))? .GetValue(item); + GrainId? contextStorageGrainId = null; if (contextStorageGrainIdValue != null) { - var contextStorageGrainId = (GrainId)contextStorageGrainIdValue; - var contextStorageGrain = GrainFactory.GetGrain(contextStorageGrainId.GetGuidKey()); - if (contextStorageGrain != null) - { - var context = await contextStorageGrain.GetContext(); - (eventType! as EventBase)!.SetContext(context); - } + contextStorageGrainId = (GrainId)contextStorageGrainIdValue; + var contextStorageGrain = GrainFactory.GetGrain(contextStorageGrainId.Value.GetGuidKey()); + var context = await contextStorageGrain.GetContext(); + (eventType! as EventBase)!.SetContext(context); } if (parameter.ParameterType == eventType!.GetType()) { - await HandleMethodInvocationAsync(eventHandlerMethod, parameter, eventType, eventId); + await HandleMethodInvocationAsync(eventHandlerMethod, parameter, eventType, eventId, + contextStorageGrainId); } if (parameter.ParameterType == typeof(EventWrapperBase)) @@ -49,7 +48,8 @@ private Task UpdateObserverList() try { var invokeParameter = - new EventWrapper((EventBase)eventType, eventId, this.GetGrainId()); + new EventWrapper((EventBase)eventType, eventId, this.GetGrainId(), + contextStorageGrainId); var result = eventHandlerMethod.Invoke(this, [invokeParameter]); await (Task)result!; } @@ -60,6 +60,8 @@ private Task UpdateObserverList() eventHandlerMethod.Name, eventType.GetType().Name); } } + + ClearContext(); }); Observers.Add(observer, new Dictionary()); @@ -92,16 +94,17 @@ private bool IsEventHandlerMethod(MethodInfo methodInfo) } private async Task HandleMethodInvocationAsync(MethodInfo method, ParameterInfo parameter, object eventType, - Guid eventId) + Guid eventId, GrainId? contextStorageGrainId) { if (IsEventWithResponse(parameter)) { - await HandleEventWithResponseAsync(method, eventType, eventId); + await HandleEventWithResponseAsync(method, eventType, eventId, contextStorageGrainId); } else if (method.ReturnType == typeof(Task)) { try { + SetContextStorageGrainId(contextStorageGrainId); var result = method.Invoke(this, [eventType]); await (Task)result!; } @@ -120,7 +123,7 @@ private bool IsEventWithResponse(ParameterInfo parameter) parameter.ParameterType.BaseType.GetGenericTypeDefinition() == typeof(EventWithResponseBase<>); } - private async Task HandleEventWithResponseAsync(MethodInfo method, object eventType, Guid eventId) + private async Task HandleEventWithResponseAsync(MethodInfo method, object eventType, Guid eventId, GrainId? contextStorageGrainId) { if (method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>)) @@ -131,7 +134,8 @@ private async Task HandleEventWithResponseAsync(MethodInfo method, object eventT try { var eventResult = await (dynamic)method.Invoke(this, [eventType])!; - var eventWrapper = new EventWrapper(eventResult, eventId, this.GetGrainId()); + var eventWrapper = new EventWrapper(eventResult, eventId, this.GetGrainId(), + contextStorageGrainId); await PublishAsync(eventWrapper); } catch (Exception ex) diff --git a/src/AISmart.GAgent.Core/GAgentBase.cs b/src/AISmart.GAgent.Core/GAgentBase.cs index 2ce40e7a..3d19c257 100644 --- a/src/AISmart.GAgent.Core/GAgentBase.cs +++ b/src/AISmart.GAgent.Core/GAgentBase.cs @@ -145,6 +145,14 @@ public async Task HandleRequestAllSubscriptionsEventAs { await LoadSubscribersAsync(); + if (_subscribers.State.IsNullOrEmpty()) + { + return new SubscribedEventListEvent + { + GAgentType = GetType() + }; + } + var gAgentList = _subscribers.State.Select(grainId => GrainFactory.GetGrain(grainId)).ToList(); if (gAgentList.IsNullOrEmpty()) @@ -193,7 +201,7 @@ public Task GetStateAsync() protected async Task PublishAsync(T @event) where T : EventBase { var eventId = Guid.NewGuid(); - var eventWrapper = new EventWrapper(@event, eventId, this.GetGrainId()); + var eventWrapper = new EventWrapper(@event, eventId, this.GetGrainId(), GetContextStorageGrainId()); await PublishAsync(eventWrapper); @@ -203,25 +211,20 @@ protected async Task PublishAsync(T @event) where T : EventBase private async Task PublishAsync(EventWrapper eventWrapper) where T : EventBase { await LoadPublishersAsync(); + if (_publishers.State.Count == 0) { return; } - var contextStorageGrain = eventWrapper.ContextGrainId == null - ? GrainFactory.GetGrain(Guid.NewGuid()) - : GrainFactory.GetGrain(eventWrapper.ContextGrainId.Value); - eventWrapper.ContextGrainId = contextStorageGrain.GetGrainId(); - - await contextStorageGrain.AddContext(eventWrapper.Event.GetContext()); - - var eventType = typeof(T); - var properties = eventType.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); - foreach (var property in properties) + var eventContext = eventWrapper.Event.GetContext(); + if (!eventContext.IsNullOrEmpty()) { - var propertyValue = property.GetValue(eventWrapper.Event); - Logger.LogInformation($"Add Context: {property.Name} - {propertyValue}"); - await contextStorageGrain.AddContext($"{eventType}.{property.Name}", propertyValue); + var contextStorageGrain = eventWrapper.ContextStorageGrainId == null + ? GrainFactory.GetGrain(Guid.NewGuid()) + : GrainFactory.GetGrain(eventWrapper.ContextStorageGrainId.Value.GetGuidKey()); + eventWrapper.ContextStorageGrainId = contextStorageGrain.GetGrainId(); + await contextStorageGrain.AddContext(eventContext); } foreach (var publisher in _publishers.State.Select(kp => kp.Value)) diff --git a/src/AISmart.Silo/Extensions/OrleansHostExtension.cs b/src/AISmart.Silo/Extensions/OrleansHostExtension.cs index 8c4248f7..2b8a347b 100644 --- a/src/AISmart.Silo/Extensions/OrleansHostExtension.cs +++ b/src/AISmart.Silo/Extensions/OrleansHostExtension.cs @@ -55,6 +55,7 @@ public static IHostBuilder UseOrleansConfiguration(this IHostBuilder hostBuilder }) .UseMongoDBReminders(options => { + options.CollectionPrefix = "Reminders"; options.DatabaseName = configSection.GetValue("DataBase"); options.CreateShardKeyForCosmos = false; }) diff --git a/test/AISmart.GAgents.Tests/GAgentBase/ChattingTests.cs b/test/AISmart.GAgents.Tests/GAgentBase/ChattingTests.cs index 182e75ab..3becef64 100644 --- a/test/AISmart.GAgents.Tests/GAgentBase/ChattingTests.cs +++ b/test/AISmart.GAgents.Tests/GAgentBase/ChattingTests.cs @@ -7,6 +7,9 @@ namespace AISmart.GAgents.Tests.GAgentBase; [Trait("Category", "BVT")] public class ChattingTests : GAgentTestKitBase { + /// + /// Temporary demo for context operation test. + /// [Fact] public async Task ChatIdRelayTest() { diff --git a/test/AISmart.GAgents.Tests/GAgentBase/ContextTests.cs b/test/AISmart.GAgents.Tests/GAgentBase/ContextTests.cs new file mode 100644 index 00000000..7639cea0 --- /dev/null +++ b/test/AISmart.GAgents.Tests/GAgentBase/ContextTests.cs @@ -0,0 +1,44 @@ +using AISmart.GAgent.Core; +using AISmart.GAgents.Tests.TestEvents; +using AISmart.GAgents.Tests.TestGAgents; +using Shouldly; + +namespace AISmart.GAgents.Tests.GAgentBase; + +[Trait("Category", "BVT")] +public class ContextTests : GAgentTestKitBase +{ + [Fact] + public async Task ContextPipelineText() + { + var contextTestGAgent1 = await Silo.CreateGrainAsync(Guid.NewGuid()); + var contextTestGAgent2 = await Silo.CreateGrainAsync(Guid.NewGuid()); + var contextTestGAgent3 = await Silo.CreateGrainAsync(Guid.NewGuid()); + var subscribeTestGAgent = await Silo.CreateGrainAsync(Guid.NewGuid()); + var groupGAgent = await CreateGroupGAgentAsync(contextTestGAgent1, contextTestGAgent2, contextTestGAgent3, + subscribeTestGAgent); + var publishingGAgent = await CreatePublishingGAgentAsync(groupGAgent); + + await publishingGAgent.PublishEventAsync(new ContextTestEvent1 + { + ExpectedContext = new Dictionary + { + ["Test1"] = "a string value" + } + }.WithContext("Test1", "a string value")); + + var contextTestGAgentState = await contextTestGAgent3.GetStateAsync(); + contextTestGAgentState.Success.ShouldBeTrue(); + + var subscribeTestGAgentState = await subscribeTestGAgent.GetStateAsync(); + (subscribeTestGAgentState.ContextTestData as string).ShouldBe("a string value"); + + var contextStorageGrain = Silo.GrainFactory.GetGrain(Guid.NewGuid()); + var reminderList = await Silo.ReminderRegistry.GetReminders(contextStorageGrain.GetGrainId()); + reminderList.Count.ShouldBe(1); + var reminder = (Orleans.TestKit.Reminders.TestReminder)reminderList.First(); + reminder.ReminderName.ShouldBe(AISmartGAgentConstants.ContextStorageGrainSelfTerminateReminderName); + reminder.DueTime.ShouldBe(TimeSpan.FromMinutes(3)); + reminder.Period.ShouldBe(TimeSpan.FromMinutes(3)); + } +} \ No newline at end of file diff --git a/test/AISmart.GAgents.Tests/TestEvents/ContextTestEvent.cs b/test/AISmart.GAgents.Tests/TestEvents/ContextTestEvent.cs new file mode 100644 index 00000000..fbc35cf6 --- /dev/null +++ b/test/AISmart.GAgents.Tests/TestEvents/ContextTestEvent.cs @@ -0,0 +1,18 @@ +using AISmart.Agents; + +namespace AISmart.GAgents.Tests.TestEvents; + +[GenerateSerializer] +public class ContextTestEventBase : EventBase +{ + [Id(0)] public Dictionary ExpectedContext { get; set; } +} + +[GenerateSerializer] +public class ContextTestEvent1 : ContextTestEventBase; + +[GenerateSerializer] +public class ContextTestEvent2 : ContextTestEventBase; + +[GenerateSerializer] +public class ContextTestEvent3 : ContextTestEventBase; \ No newline at end of file diff --git a/test/AISmart.GAgents.Tests/TestGAgents/ChatTestGAgent.cs b/test/AISmart.GAgents.Tests/TestGAgents/ChatTestGAgent.cs index d3b2b66b..5b5b722c 100644 --- a/test/AISmart.GAgents.Tests/TestGAgents/ChatTestGAgent.cs +++ b/test/AISmart.GAgents.Tests/TestGAgents/ChatTestGAgent.cs @@ -50,13 +50,17 @@ await PublishAsync(new SocialTestEvent public async Task HandleEventAsync(SendMessageTestEvent eventData) { - // Call the real API to send the message. if (State.SendMessages.IsNullOrEmpty()) { State.SendMessages = []; } - State.SendMessages.Add(eventData.ChatId, eventData.Message); + if (eventData.TryGetContext("ChatId", out var chatId) + && chatId != null) + { + State.SendMessages.Add((string)chatId, eventData.Message); + } + await Task.CompletedTask; } } \ No newline at end of file diff --git a/test/AISmart.GAgents.Tests/TestGAgents/ContextTestGAgents.cs b/test/AISmart.GAgents.Tests/TestGAgents/ContextTestGAgents.cs new file mode 100644 index 00000000..16be3bab --- /dev/null +++ b/test/AISmart.GAgents.Tests/TestGAgents/ContextTestGAgents.cs @@ -0,0 +1,108 @@ +using AISmart.Agent.GEvents; +using AISmart.Agents; +using AISmart.GAgent.Core; +using AISmart.GAgents.Tests.TestEvents; +using Microsoft.Extensions.Logging.Abstractions; + +namespace AISmart.GAgents.Tests.TestGAgents; + +[GenerateSerializer] +public class ContextTestGAgentState : StateBase +{ + [Id(0)] public Dictionary SendMessages { get; set; } + [Id(1)] public bool Success { get; set; } +} + +[GAgent] +public class ContextTestGAgentBase : GAgentBase +{ + public ContextTestGAgentBase() : base(NullLogger.Instance) + { + + } + + public override Task GetDescriptionAsync() + { + return Task.FromResult("This is a GAgent to test context passing."); + } +} + +[GAgent] +public class ContextTestGAgent1 : ContextTestGAgentBase +{ + public async Task HandleEventAsync(ContextTestEvent1 eventData) + { + await SetContextAsync("Set context1", "set context1"); + await ResetContextStorageGrainTerminateTimeAsync(TimeSpan.FromMinutes(1)); + if (eventData.TryGetContext("Test1", out var testData) + && testData != null) + { + if (eventData.ExpectedContext["Test1"] == testData) + { + await PublishAsync(new ContextTestEvent2 + { + ExpectedContext = new Dictionary + { + ["Test1"] = testData, + ["Test2"] = 123 + } + }.WithContext("Test2", 123)); + } + } + } +} + + +[GAgent] +public class ContextTestGAgent2 : ContextTestGAgentBase +{ + public async Task HandleEventAsync(ContextTestEvent2 eventData) + { + await SetContextAsync("Set context2", "set context2"); + await ResetContextStorageGrainTerminateTimeAsync(TimeSpan.FromMinutes(2)); + if (eventData.TryGetContext("Test1", out var testData) + && testData != null + && eventData.TryGetContext("Test2", out var testData2) + && testData2 != null) + { + if (eventData.ExpectedContext["Test1"]!.Equals(testData) && eventData.ExpectedContext["Test2"]!.Equals(testData2)) + { + await PublishAsync(new ContextTestEvent3 + { + ExpectedContext = new Dictionary + { + ["Test1"] = testData, + ["Test2"] = testData2, + ["Test3"] = new Dictionary { { "testKey", "testValue" } } + } + }.WithContext("Test3", new Dictionary { { "testKey", "testValue" } })); + } + } + } +} + + +[GAgent] +public class ContextTestGAgent3 : ContextTestGAgentBase +{ + public async Task HandleEventAsync(ContextTestEvent3 eventData) + { + var getContext = await GetContextAsync(); + await ResetContextStorageGrainTerminateTimeAsync(TimeSpan.FromMinutes(3)); + if (eventData.TryGetContext("Test1", out var testData) + && testData != null + && eventData.TryGetContext("Test2", out var testData2) + && testData2 != null + && eventData.TryGetContext("Test3", out var testData3) + && testData3 != null) + { + if (eventData.ExpectedContext["Test1"]!.Equals(testData) + && eventData.ExpectedContext["Test2"]!.Equals(testData2) + && ((Dictionary)eventData.ExpectedContext["Test3"]!)["testKey"].Equals("testValue")) + { + State.Success = true; + await PublishAsync(new RequestAllSubscriptionsEvent()); + } + } + } +} \ No newline at end of file diff --git a/test/AISmart.GAgents.Tests/TestGAgents/RelayTestGAgent.cs b/test/AISmart.GAgents.Tests/TestGAgents/RelayTestGAgent.cs index 60f71b6d..00ef0960 100644 --- a/test/AISmart.GAgents.Tests/TestGAgents/RelayTestGAgent.cs +++ b/test/AISmart.GAgents.Tests/TestGAgents/RelayTestGAgent.cs @@ -18,13 +18,12 @@ public override Task GetDescriptionAsync() public async Task HandleEventAsync(SocialTestEvent eventData) { - var chatIdKey = $"{typeof(ReceiveMessageTestEvent).FullName}.ChatId"; if (eventData.TryGetContext("ChatId", out var chatId) && chatId != null) { await PublishAsync(new SendMessageTestEvent { - ChatId = (string)chatId!, + ChatId = (string)chatId, Message = "I handled a social event: " + eventData.Message }); } diff --git a/test/AISmart.GAgents.Tests/TestGAgents/SubscribeTestGAgent.cs b/test/AISmart.GAgents.Tests/TestGAgents/SubscribeTestGAgent.cs index bf1b062a..65565109 100644 --- a/test/AISmart.GAgents.Tests/TestGAgents/SubscribeTestGAgent.cs +++ b/test/AISmart.GAgents.Tests/TestGAgents/SubscribeTestGAgent.cs @@ -9,6 +9,7 @@ namespace AISmart.GAgents.Tests.TestGAgents; public class SubscribeTestGAgentState : StateBase { [Id(0)] public Dictionary> SubscriptionInfo { get; set; } + [Id(1)] public object? ContextTestData { get; set; } } public class SubscribeTestGEvent : GEventBase; @@ -31,5 +32,11 @@ public async Task HandleEventAsync(SubscribedEventListEvent eventData) { State.SubscriptionInfo = eventData.Value; } + + if (eventData.TryGetContext("Test1", out var testData) + && testData != null) + { + State.ContextTestData = testData; + } } } \ No newline at end of file