diff --git a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/ObserveDataBase.cs b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/ObserveDataBase.cs index 62c2950d..c881b49d 100644 --- a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/ObserveDataBase.cs +++ b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/ObserveDataBase.cs @@ -1,6 +1,7 @@ namespace BotSharp.Abstraction.MessageHub.Models; -public abstract class ObserveDataBase +public class ObserveDataBase { public IServiceProvider ServiceProvider { get; set; } = null!; + public string RefId { get; set; } = null!; } diff --git a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/ObserverSubscription.cs b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/ObserverSubscription.cs new file mode 100644 index 00000000..ee8345cd --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/ObserverSubscription.cs @@ -0,0 +1,22 @@ +using BotSharp.Abstraction.MessageHub.Observers; + +namespace BotSharp.Abstraction.MessageHub.Models; + +public class ObserverSubscription +{ + public IBotSharpObserver Observer { get; set; } + public IDisposable Subscription { get; set; } + + public ObserverSubscription() + { + + } + + public ObserverSubscription( + IBotSharpObserver observer, + IDisposable subscription) + { + Observer = observer; + Subscription = subscription; + } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Observers/BotSharpObserverBase.cs b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Observers/BotSharpObserverBase.cs new file mode 100644 index 00000000..7b824075 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Observers/BotSharpObserverBase.cs @@ -0,0 +1,9 @@ +namespace BotSharp.Abstraction.MessageHub.Observers; + +public abstract class BotSharpObserverBase +{ + protected BotSharpObserverBase() + { + + } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Observers/IBotSharpObserver.cs b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Observers/IBotSharpObserver.cs new file mode 100644 index 00000000..edbb4df2 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Observers/IBotSharpObserver.cs @@ -0,0 +1,9 @@ +namespace BotSharp.Abstraction.MessageHub.Observers; + +public interface IBotSharpObserver : IObserver +{ + //string Name { get; } + bool IsActive { get; } + void Activate(); + void Deactivate(); +} diff --git a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Services/IObserverService.cs b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Services/IObserverService.cs new file mode 100644 index 00000000..337f2bbf --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Services/IObserverService.cs @@ -0,0 +1,8 @@ +using BotSharp.Abstraction.MessageHub.Models; + +namespace BotSharp.Abstraction.MessageHub.Services; + +public interface IObserverService +{ + ObserverSubscriptionContainer RegisterObservers(string refId) where T : ObserveDataBase; +} diff --git a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Services/ObserverSubscriptionContainer.cs b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Services/ObserverSubscriptionContainer.cs new file mode 100644 index 00000000..860da06d --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Services/ObserverSubscriptionContainer.cs @@ -0,0 +1,49 @@ +using BotSharp.Abstraction.MessageHub.Models; + +namespace BotSharp.Abstraction.MessageHub.Services; + +public class ObserverSubscriptionContainer : IDisposable +{ + private IList> _subscriptions = []; + private bool _disposed = false; + + public ObserverSubscriptionContainer() + { + + } + + public ObserverSubscriptionContainer( + IList> subscriptions) + { + _subscriptions = subscriptions; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { +#if DEBUG + Console.WriteLine($"Start disposing subscriptions..."); +#endif + // Unregister all observers + foreach (var item in _subscriptions) + { + item.Observer.Deactivate(); + item.Subscription.Dispose(); + } + _subscriptions.Clear(); +#if DEBUG + Console.WriteLine($"End disposing subscriptions..."); +#endif + } + _disposed = true; + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs b/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs index 1a36875c..d3797466 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs @@ -1,5 +1,8 @@ using BotSharp.Abstraction.Google.Settings; using BotSharp.Abstraction.Instructs; +using BotSharp.Abstraction.MessageHub; +using BotSharp.Abstraction.MessageHub.Observers; +using BotSharp.Abstraction.MessageHub.Services; using BotSharp.Abstraction.Messaging; using BotSharp.Abstraction.Planning; using BotSharp.Abstraction.Plugins.Models; @@ -8,6 +11,7 @@ using BotSharp.Abstraction.Templating; using BotSharp.Core.Instructs; using BotSharp.Core.MessageHub; using BotSharp.Core.MessageHub.Observers; +using BotSharp.Core.MessageHub.Services; using BotSharp.Core.Messaging; using BotSharp.Core.Routing.Reasoning; using BotSharp.Core.Templating; @@ -45,6 +49,8 @@ public class ConversationPlugin : IBotSharpPlugin, IBotSharpAppPlugin }); services.AddSingleton>>(); + //services.AddScoped>, ConversationObserver>(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -71,9 +77,8 @@ public class ConversationPlugin : IBotSharpPlugin, IBotSharpAppPlugin public void Configure(IApplicationBuilder app) { - var services = app.ApplicationServices; - var queue = services.GetRequiredService>>(); - var logger = services.GetRequiredService>>>(); - queue.Events.Subscribe(new ConversationObserver(logger)); + //var services = app.ApplicationServices; + //var queue = services.GetRequiredService>>(); + //var logger = services.GetRequiredService>>>(); } } diff --git a/src/Infrastructure/BotSharp.Core/Demo/Functions/GetWeatherFn.cs b/src/Infrastructure/BotSharp.Core/Demo/Functions/GetWeatherFn.cs index a22f5cec..41b96399 100644 --- a/src/Infrastructure/BotSharp.Core/Demo/Functions/GetWeatherFn.cs +++ b/src/Infrastructure/BotSharp.Core/Demo/Functions/GetWeatherFn.cs @@ -1,5 +1,6 @@ using BotSharp.Abstraction.Functions; using BotSharp.Core.MessageHub; +using System.Text.Json.Serialization; namespace BotSharp.Core.Demo.Functions; @@ -17,26 +18,29 @@ public class GetWeatherFn : IFunctionCallback public async Task Execute(RoleDialogModel message) { + var args = JsonSerializer.Deserialize(message.FunctionArgs); var conv = _services.GetRequiredService(); var messageHub = _services.GetRequiredService>>(); await Task.Delay(1000); - message.Indication = "Start querying weather data"; + message.Indication = $"Start querying weather data in {args?.City}"; messageHub.Push(new() { EventName = ChatEvent.OnIndicationReceived, Data = message, + RefId = conv.ConversationId, ServiceProvider = _services }); await Task.Delay(1500); - message.Indication = "Still working on it"; + message.Indication = $"Still working on it, {args?.City}"; messageHub.Push(new() { EventName = ChatEvent.OnIndicationReceived, Data = message, + RefId = conv.ConversationId, ServiceProvider = _services }); @@ -46,4 +50,10 @@ public class GetWeatherFn : IFunctionCallback message.StopCompletion = false; return true; } +} + +class WeatherLocation +{ + [JsonPropertyName("city")] + public string City { get; set; } } \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Core/MessageHub/MessageHub.cs b/src/Infrastructure/BotSharp.Core/MessageHub/MessageHub.cs index 8e99f898..8f01612d 100644 --- a/src/Infrastructure/BotSharp.Core/MessageHub/MessageHub.cs +++ b/src/Infrastructure/BotSharp.Core/MessageHub/MessageHub.cs @@ -2,7 +2,7 @@ using System.Reactive.Subjects; namespace BotSharp.Core.MessageHub; -public class MessageHub where T : class, new() +public class MessageHub where T : ObserveDataBase { private readonly ILogger> _logger; private readonly ISubject _observable = Subject.Synchronize(new Subject()); diff --git a/src/Infrastructure/BotSharp.Core/MessageHub/Observers/ConversationObserver.cs b/src/Infrastructure/BotSharp.Core/MessageHub/Observers/ConversationObserver.cs index 15b44157..6c22f753 100644 --- a/src/Infrastructure/BotSharp.Core/MessageHub/Observers/ConversationObserver.cs +++ b/src/Infrastructure/BotSharp.Core/MessageHub/Observers/ConversationObserver.cs @@ -1,15 +1,31 @@ +using BotSharp.Abstraction.MessageHub.Observers; + namespace BotSharp.Core.MessageHub.Observers; -public class ConversationObserver : IObserver> +public class ConversationObserver : IBotSharpObserver> { - private readonly ILogger _logger; + private readonly ILogger _logger; private IServiceProvider _services; + private bool _isActive; - public ConversationObserver(ILogger logger) + public ConversationObserver( + ILogger logger) { _logger = logger; } + public bool IsActive => _isActive; + + public void Activate() + { + _isActive = true; + } + + public void Deactivate() + { + _isActive = false; + } + public void OnCompleted() { _logger.LogWarning($"{nameof(ConversationObserver)} receives complete notification."); @@ -23,15 +39,14 @@ public class ConversationObserver : IObserver> public void OnNext(HubObserveData value) { _services = value.ServiceProvider; - var progress = _services.GetRequiredService(); + //var progress = _services.GetRequiredService(); - if (value.EventName == ChatEvent.OnIndicationReceived - && progress.OnFunctionExecuting != null) + if (value.EventName == ChatEvent.OnIndicationReceived) { -#if DEBUG - _logger.LogCritical($"Receiving {value.EventName} in {nameof(ConversationObserver)}"); +#if !DEBUG + _logger.LogCritical($"Receiving {value.EventName} ({value.Data.Indication}) in {nameof(ConversationObserver)}"); #endif - progress.OnFunctionExecuting(value.Data).ConfigureAwait(false).GetAwaiter().GetResult(); + //progress.OnFunctionExecuting(value.Data).ConfigureAwait(false).GetAwaiter().GetResult(); } } } diff --git a/src/Infrastructure/BotSharp.Core/MessageHub/Services/ObserverService.cs b/src/Infrastructure/BotSharp.Core/MessageHub/Services/ObserverService.cs new file mode 100644 index 00000000..c2165fc6 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/MessageHub/Services/ObserverService.cs @@ -0,0 +1,43 @@ +using BotSharp.Abstraction.MessageHub.Observers; +using BotSharp.Abstraction.MessageHub.Services; +using System.Reactive.Linq; + +namespace BotSharp.Core.MessageHub.Services; + +public class ObserverService : IObserverService +{ + private readonly IServiceProvider _services; + + public ObserverService( + IServiceProvider services) + { + _services = services; + } + + public ObserverSubscriptionContainer RegisterObservers(string refId) where T : ObserveDataBase + { + var subscriptions = new List>(); + var observers = _services.GetServices>() + .Where(x => !x.IsActive) + .ToList(); + + if (observers.IsNullOrEmpty()) + { + return new(); + } + + var messageHub = _services.GetRequiredService>(); + foreach (var observer in observers) + { + observer.Activate(); + var sub = messageHub.Events.Where(x => x.RefId == refId).Subscribe(observer); + subscriptions.Add(new() + { + Observer = observer, + Subscription = sub + }); + } + + return new(subscriptions); + } +} diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs index f693f4dd..a009b88f 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs @@ -25,11 +25,13 @@ public partial class RoutingService clonedMessage.FunctionName = name; clonedMessage.Indication = await funcExecutor.GetIndicatorAsync(message); + var conv = _services.GetRequiredService(); var messageHub = _services.GetRequiredService>>(); messageHub.Push(new() { EventName = ChatEvent.OnIndicationReceived, Data = clonedMessage, + RefId = conv.ConversationId, ServiceProvider = _services }); diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs index bd131ca1..345b926d 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs @@ -1,10 +1,13 @@ using BotSharp.Abstraction.Files.Constants; using BotSharp.Abstraction.Files.Enums; using BotSharp.Abstraction.Files.Utilities; +using BotSharp.Abstraction.MessageHub.Models; +using BotSharp.Abstraction.MessageHub.Services; using BotSharp.Abstraction.Options; using BotSharp.Abstraction.Routing; using BotSharp.Abstraction.Users.Dtos; using BotSharp.Core.Infrastructures; +using System.ComponentModel; namespace BotSharp.OpenAPI.Controllers; @@ -343,6 +346,9 @@ public class ConversationController : ControllerBase [FromRoute] string conversationId, [FromBody] NewMessageModel input) { + var observerService = _services.GetRequiredService(); + using var container = observerService.RegisterObservers>(conversationId); + var conv = _services.GetRequiredService(); var inputMsg = new RoleDialogModel(AgentRole.User, input.Text) { diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs b/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs index 38bd936c..4c48ef74 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs @@ -1,6 +1,8 @@ using BotSharp.Abstraction.Crontab; using BotSharp.Abstraction.MessageHub.Models; +using BotSharp.Abstraction.MessageHub.Observers; using BotSharp.Core.MessageHub; +using BotSharp.Core.MessageHub.Observers; using BotSharp.Plugin.ChatHub.Hooks; using BotSharp.Plugin.ChatHub.Observers; using Microsoft.AspNetCore.Builder; @@ -24,6 +26,8 @@ public class ChatHubPlugin : IBotSharpPlugin, IBotSharpAppPlugin config.Bind("ChatHub", settings); services.AddSingleton(x => settings); + services.AddScoped>, ChatHubObserver>(); + // Register hooks services.AddScoped(); services.AddScoped(); @@ -35,9 +39,8 @@ public class ChatHubPlugin : IBotSharpPlugin, IBotSharpAppPlugin public void Configure(IApplicationBuilder app) { - var services = app.ApplicationServices; - var queue = services.GetRequiredService>>(); - var logger = services.GetRequiredService>>>(); - queue.Events.Subscribe(new ChatHubObserver(logger)); + //var services = app.ApplicationServices; + //var queue = services.GetRequiredService>>(); + //var logger = services.GetRequiredService>>>(); } } diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs index 75aa0cd3..bf2b1ed8 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs @@ -1,21 +1,37 @@ using BotSharp.Abstraction.Conversations.Dtos; using BotSharp.Abstraction.Conversations.Enums; using BotSharp.Abstraction.MessageHub.Models; +using BotSharp.Abstraction.MessageHub.Observers; using BotSharp.Abstraction.SideCar; +using BotSharp.Core.MessageHub.Observers; using System.Runtime.CompilerServices; namespace BotSharp.Plugin.ChatHub.Observers; -public class ChatHubObserver : IObserver> +public class ChatHubObserver : IBotSharpObserver> { private readonly ILogger _logger; private IServiceProvider _services; + private bool _isActive = false; - public ChatHubObserver(ILogger logger) + public ChatHubObserver( + ILogger logger) { _logger = logger; } + public bool IsActive => _isActive; + + public void Activate() + { + _isActive = true; + } + + public void Deactivate() + { + _isActive = false; + } + public void OnCompleted() { _logger.LogWarning($"{nameof(ChatHubObserver)} receives complete notification."); @@ -117,6 +133,8 @@ public class ChatHubObserver : IObserver> Role = AgentRole.Assistant } }; + + _logger.LogCritical($"Receiving {value.EventName} ({value.Data.Indication}) in {nameof(ChatHubObserver)} - {conv.ConversationId}"); break; }