From e467a7bd0198ceae14d8190e357b7ac48b87906d Mon Sep 17 00:00:00 2001 From: SeplzZZ Date: Fri, 10 Nov 2023 10:01:03 -0600 Subject: [PATCH 1/2] add ConversationTimeoutService --- .../Conversations/IConversationService.cs | 1 + .../Repositories/IBotSharpRepository.cs | 1 + .../BotSharp.Core/BotSharp.Core.csproj | 1 + .../Services/ConversationService.cs | 6 ++ .../ConversationTimeoutService.cs | 79 +++++++++++++++++++ .../Repository/BotSharpDbContext.cs | 5 ++ .../Repository/FileRepository.cs | 22 ++++++ .../Users/Services/UserIdentity.cs | 10 +-- .../Repository/MongoRepository.cs | 18 +++++ 9 files changed, 138 insertions(+), 5 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Core/Infrastructures/ConversationTimeoutService.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs index 4e8795ff2..ba011cbf4 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs @@ -8,6 +8,7 @@ public interface IConversationService void SetConversationId(string conversationId, List states); Task GetConversation(string id); Task> GetConversations(); + Task> GetLastConversations(); Task DeleteConversation(string id); /// diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs index 4cc6af362..8e65bf00a 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs @@ -34,6 +34,7 @@ List GetAgents(string? name = null, bool? disabled = null, bool? allowRou void UpdateConversationStates(string conversationId, List states); Conversation GetConversation(string conversationId); List GetConversations(string userId); + List GetLastConversations(); void AddExectionLogs(string conversationId, List logs); List GetExectionLogs(string conversationId); #endregion diff --git a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj index 8099a315e..34233399d 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj +++ b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj @@ -56,6 +56,7 @@ + diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs index 20bfb7cfb..b77407bfc 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs @@ -51,6 +51,12 @@ public async Task> GetConversations() return conversations.OrderByDescending(x => x.CreatedTime).ToList(); } + public async Task> GetLastConversations() + { + var db = _services.GetRequiredService(); + return db.GetLastConversations(); + } + public async Task NewConversation(Conversation sess) { var db = _services.GetRequiredService(); diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/ConversationTimeoutService.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/ConversationTimeoutService.cs new file mode 100644 index 000000000..48b71bdd6 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Infrastructures/ConversationTimeoutService.cs @@ -0,0 +1,79 @@ +using Microsoft.Extensions.Hosting; +using System.Threading; + +namespace BotSharp.Core.Infrastructures +{ + public class ConversationTimeoutService : BackgroundService + { + private readonly IServiceProvider _services; + private readonly ILogger _logger; + + public ConversationTimeoutService(IServiceProvider services, ILogger logger) + { + _services = services; + _logger = logger; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + _logger.LogInformation("Conversation Timeout Service is running."); + try + { + while (true) + { + stoppingToken.ThrowIfCancellationRequested(); + var delay = Task.Delay(TimeSpan.FromMinutes(1)); + try + { + await CloseIdleConversationsAsync(TimeSpan.FromMinutes(10)); + } + catch (Exception ex) + { + _logger.LogError(ex, $"Error occurred closing conversations."); + } + await delay; + } + } + catch (OperationCanceledException) { } + } + + public override async Task StopAsync(CancellationToken stoppingToken) + { + _logger.LogInformation("Conversation Timeout Service is stopping."); + await base.StopAsync(stoppingToken); + } + + private async Task CloseIdleConversationsAsync(TimeSpan conversationIdleTimeout) + { + using var scope = _services.CreateScope(); + var conversationService = scope.ServiceProvider.GetRequiredService(); + var hooks = scope.ServiceProvider.GetServices() + .OrderBy(x => x.Priority) + .ToList(); + var moment = DateTime.UtcNow.Add(-conversationIdleTimeout); + var conversations = + (await conversationService.GetLastConversations()) + .Where(c => c.CreatedTime <= moment); + foreach (var conversation in conversations) + { + try + { + var response = new RoleDialogModel(AgentRole.Assistant, "End the conversation due to timeout.") + { + StopCompletion = true, + FunctionName = "conversation_end" + }; + + foreach (var hook in hooks) + { + await hook.OnConversationEnding(response); + } + } + catch (Exception ex) + { + _logger.LogError(ex, $"Error occurred closing conversation #{conversation.Id}."); + } + } + } + } +} diff --git a/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs b/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs index 4b2383bc6..a06fdf301 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs @@ -131,6 +131,11 @@ public List GetConversations(string userId) throw new NotImplementedException(); } + public List GetLastConversations() + { + throw new NotImplementedException(); + } + public string GetConversationDialog(string conversationId) { throw new NotImplementedException(); diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository.cs index 517bd0f7c..440bce4d9 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository.cs @@ -735,6 +735,28 @@ public List GetConversations(string userId) return records; } + public List GetLastConversations() + { + var records = new List(); + var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir); + + foreach (var d in Directory.GetDirectories(dir)) + { + var path = Path.Combine(d, "conversation.json"); + if (!File.Exists(path)) continue; + + var json = File.ReadAllText(path); + var record = JsonSerializer.Deserialize(json, _options); + if (record != null) + { + records.Add(record); + } + } + return records.GroupBy(r => r.UserId) + .Select(g => g.OrderByDescending(x => x.CreatedTime).First()) + .ToList(); + } + public void AddExectionLogs(string conversationId, List logs) { if (string.IsNullOrEmpty(conversationId) || logs.IsNullOrEmpty()) return; diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserIdentity.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserIdentity.cs index 00958b11e..e0b4051d3 100644 --- a/src/Infrastructure/BotSharp.Core/Users/Services/UserIdentity.cs +++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserIdentity.cs @@ -6,7 +6,7 @@ namespace BotSharp.Core.Users.Services; public class UserIdentity : IUserIdentity { private readonly IHttpContextAccessor _contextAccessor; - private IEnumerable _claims => _contextAccessor.HttpContext.User.Claims; + private IEnumerable _claims => _contextAccessor.HttpContext?.User.Claims!; public UserIdentity(IHttpContextAccessor contextAccessor) { @@ -15,14 +15,14 @@ public UserIdentity(IHttpContextAccessor contextAccessor) public string Id - => _claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value; + => _claims?.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value!; public string Email - => _claims.FirstOrDefault(x => x.Type == ClaimTypes.Email)?.Value; + => _claims?.FirstOrDefault(x => x.Type == ClaimTypes.Email)?.Value!; public string FirstName - => _claims.FirstOrDefault(x => x.Type == ClaimTypes.GivenName)?.Value; + => _claims?.FirstOrDefault(x => x.Type == ClaimTypes.GivenName)?.Value!; public string LastName - => _claims.FirstOrDefault(x => x.Type == ClaimTypes.Surname)?.Value; + => _claims?.FirstOrDefault(x => x.Type == ClaimTypes.Surname)?.Value!; } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.cs index 0009e0aa3..5807dc99b 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.cs @@ -727,6 +727,24 @@ public List GetConversations(string userId) return records; } + public List GetLastConversations() + { + var records = new List(); + var conversations = _dc.Conversations.Aggregate() + .Group(c => c.UserId, + g => g.OrderByDescending(x => x.CreatedTime).First()) + .ToList(); + return conversations.Select(c => new Conversation() + { + Id = c.Id.ToString(), + AgentId = c.AgentId.ToString(), + UserId = c.UserId.ToString(), + Title = c.Title, + CreatedTime = c.CreatedTime, + UpdatedTime = c.UpdatedTime + }).ToList(); + } + public void AddExectionLogs(string conversationId, List logs) { if (string.IsNullOrEmpty(conversationId) || logs.IsNullOrEmpty()) return; From a0d8abeede67cdcff5876e03a6c1963c32169a1c Mon Sep 17 00:00:00 2001 From: SeplzZZ Date: Fri, 10 Nov 2023 21:50:11 -0600 Subject: [PATCH 2/2] relocate the service --- src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj | 1 - .../BackgroundServices}/ConversationTimeoutService.cs | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/Infrastructure/{BotSharp.Core/Infrastructures => BotSharp.OpenAPI/BackgroundServices}/ConversationTimeoutService.cs (95%) diff --git a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj index 34233399d..8099a315e 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj +++ b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj @@ -56,7 +56,6 @@ - diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/ConversationTimeoutService.cs b/src/Infrastructure/BotSharp.OpenAPI/BackgroundServices/ConversationTimeoutService.cs similarity index 95% rename from src/Infrastructure/BotSharp.Core/Infrastructures/ConversationTimeoutService.cs rename to src/Infrastructure/BotSharp.OpenAPI/BackgroundServices/ConversationTimeoutService.cs index 48b71bdd6..d624846e4 100644 --- a/src/Infrastructure/BotSharp.Core/Infrastructures/ConversationTimeoutService.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/BackgroundServices/ConversationTimeoutService.cs @@ -1,7 +1,8 @@ +using BotSharp.Abstraction.Agents.Enums; +using BotSharp.Abstraction.Conversations.Models; using Microsoft.Extensions.Hosting; -using System.Threading; -namespace BotSharp.Core.Infrastructures +namespace BotSharp.OpenAPI.BackgroundServices { public class ConversationTimeoutService : BackgroundService {