diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs index 907dd451..f9fafea0 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs @@ -13,7 +13,8 @@ public interface IConversationService Task> GetConversations(ConversationFilter filter); Task UpdateConversationTitle(string id, string title); Task> GetLastConversations(); - Task DeleteConversation(string id); + Task> GetIdleConversations(int batchSize, int messageLimit); + Task DeleteConversations(IEnumerable ids); Task TruncateConversation(string conversationId, string messageId); Task> GetConversationContentLogs(string conversationId); Task> GetConversationStateLogs(string conversationId); diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs index 0c06f9fa..eff4ff28 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs @@ -48,7 +48,7 @@ public interface IBotSharpRepository #region Conversation void CreateNewConversation(Conversation conversation); - bool DeleteConversation(string conversationId); + bool DeleteConversations(IEnumerable conversationIds); List GetConversationDialogs(string conversationId); void UpdateConversationDialogElements(string conversationId, List updateElements); void AppendConversationDialogs(string conversationId, List dialogs); @@ -59,6 +59,7 @@ public interface IBotSharpRepository PagedItems GetConversations(ConversationFilter filter); void UpdateConversationTitle(string conversationId, string title); List GetLastConversations(); + List GetIdleConversations(int batchSize, int messageLimit); bool TruncateConversation(string conversationId, string messageId, bool cleanLog = false); #endregion diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs index 67c75a5c..99475176 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs @@ -29,10 +29,10 @@ public partial class ConversationService : IConversationService _logger = logger; } - public async Task DeleteConversation(string id) + public async Task DeleteConversations(IEnumerable ids) { var db = _services.GetRequiredService(); - var isDeleted = db.DeleteConversation(id); + var isDeleted = db.DeleteConversations(ids); return await Task.FromResult(isDeleted); } @@ -63,6 +63,12 @@ public partial class ConversationService : IConversationService return db.GetLastConversations(); } + public async Task> GetIdleConversations(int batchSize, int messageLimit) + { + var db = _services.GetRequiredService(); + return db.GetIdleConversations(batchSize, messageLimit); + } + public async Task NewConversation(Conversation sess) { var db = _services.GetRequiredService(); diff --git a/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs b/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs index 054e9c15..535c7b95 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs @@ -164,7 +164,7 @@ public class BotSharpDbContext : Database, IBotSharpRepository throw new NotImplementedException(); } - public bool DeleteConversation(string conversationId) + public bool DeleteConversations(IEnumerable conversationIds) { throw new NotImplementedException(); } @@ -184,6 +184,11 @@ public class BotSharpDbContext : Database, IBotSharpRepository throw new NotImplementedException(); } + public List GetIdleConversations(int batchSize, int messageLimit) + { + throw new NotImplementedException(); + } + public List GetConversationDialogs(string conversationId) { throw new NotImplementedException(); diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs index a9a81949..37441a87 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs @@ -44,14 +44,17 @@ namespace BotSharp.Core.Repository } } - public bool DeleteConversation(string conversationId) + public bool DeleteConversations(IEnumerable conversationIds) { - if (string.IsNullOrEmpty(conversationId)) return false; + if (conversationIds.IsNullOrEmpty()) return false; - var convDir = FindConversationDirectory(conversationId); - if (string.IsNullOrEmpty(convDir)) return false; + foreach (var conversationId in conversationIds) + { + var convDir = FindConversationDirectory(conversationId); + if (string.IsNullOrEmpty(convDir)) continue; - Directory.Delete(convDir, true); + Directory.Delete(convDir, true); + } return true; } @@ -263,6 +266,29 @@ namespace BotSharp.Core.Repository .ToList(); } + public List GetIdleConversations(int batchSize, int messageLimit) + { + var ids = new List(); + var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir); + var count = 0; + + foreach (var d in Directory.GetDirectories(dir)) + { + var conversationId = d.Split(Path.DirectorySeparatorChar).Last(); ; + var dialogs = GetConversationDialogs(conversationId); + if (dialogs.Count <= messageLimit) + { + ids.Add(conversationId); + count++; + if (count >= batchSize) + { + return ids; + } + } + } + return ids; + } + public bool TruncateConversation(string conversationId, string messageId, bool cleanLog = false) { diff --git a/src/Infrastructure/BotSharp.OpenAPI/BackgroundServices/ConversationTimeoutService.cs b/src/Infrastructure/BotSharp.OpenAPI/BackgroundServices/ConversationTimeoutService.cs index d624846e..0dc47300 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/BackgroundServices/ConversationTimeoutService.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/BackgroundServices/ConversationTimeoutService.cs @@ -23,10 +23,12 @@ namespace BotSharp.OpenAPI.BackgroundServices while (true) { stoppingToken.ThrowIfCancellationRequested(); - var delay = Task.Delay(TimeSpan.FromMinutes(1)); + var delay = Task.Delay(TimeSpan.FromSeconds(10)); try { - await CloseIdleConversationsAsync(TimeSpan.FromMinutes(10)); + //await CloseIdleConversationsAsync(TimeSpan.FromMinutes(10)); + await CleanIdleConversationsAsync(); + } catch (Exception ex) { @@ -76,5 +78,17 @@ namespace BotSharp.OpenAPI.BackgroundServices } } } + + private async Task CleanIdleConversationsAsync(int batchSize = 50, int messageLimit = 2) + { + using var scope = _services.CreateScope(); + var conversationService = scope.ServiceProvider.GetRequiredService(); + var conversationIds = await conversationService.GetIdleConversations(batchSize, messageLimit); + + if (!conversationIds.IsNullOrEmpty()) + { + await conversationService.DeleteConversations(conversationIds); + } + } } } diff --git a/src/Infrastructure/BotSharp.OpenAPI/BotSharpOpenApiExtensions.cs b/src/Infrastructure/BotSharp.OpenAPI/BotSharpOpenApiExtensions.cs index 335015a2..0434976e 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/BotSharpOpenApiExtensions.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/BotSharpOpenApiExtensions.cs @@ -10,6 +10,7 @@ using Microsoft.IdentityModel.Tokens; using Microsoft.Net.Http.Headers; using Microsoft.OpenApi.Models; using Microsoft.IdentityModel.JsonWebTokens; +using BotSharp.OpenAPI.BackgroundServices; namespace BotSharp.OpenAPI; @@ -29,6 +30,7 @@ public static class BotSharpOpenApiExtensions bool enableValidation) { services.AddScoped(); + services.AddHostedService(); // Add bearer authentication var schema = "MIXED_SCHEME"; diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs index bda4d661..92a17d63 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs @@ -137,7 +137,7 @@ public class ConversationController : ControllerBase public async Task DeleteConversation([FromRoute] string conversationId) { var conversationService = _services.GetRequiredService(); - var response = await conversationService.DeleteConversation(conversationId); + var response = await conversationService.DeleteConversations(new List { conversationId }); return response; } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs index 98fb11e3..cf104bbe 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs @@ -55,17 +55,17 @@ public partial class MongoRepository _dc.ConversationStates.InsertOne(stateDoc); } - public bool DeleteConversation(string conversationId) + public bool DeleteConversations(IEnumerable conversationIds) { - if (string.IsNullOrEmpty(conversationId)) return false; + if (conversationIds.IsNullOrEmpty()) return false; - var filterConv = Builders.Filter.Eq(x => x.Id, conversationId); - var filterDialog = Builders.Filter.Eq(x => x.ConversationId, conversationId); - var filterSates = Builders.Filter.Eq(x => x.ConversationId, conversationId); - var filterExeLog = Builders.Filter.Eq(x => x.ConversationId, conversationId); - var filterPromptLog = Builders.Filter.Eq(x => x.ConversationId, conversationId); - var filterContentLog = Builders.Filter.Eq(x => x.ConversationId, conversationId); - var filterStateLog = Builders.Filter.Eq(x => x.ConversationId, conversationId); + var filterConv = Builders.Filter.In(x => x.Id, conversationIds); + var filterDialog = Builders.Filter.In(x => x.ConversationId, conversationIds); + var filterSates = Builders.Filter.In(x => x.ConversationId, conversationIds); + var filterExeLog = Builders.Filter.In(x => x.ConversationId, conversationIds); + var filterPromptLog = Builders.Filter.In(x => x.ConversationId, conversationIds); + var filterContentLog = Builders.Filter.In(x => x.ConversationId, conversationIds); + var filterStateLog = Builders.Filter.In(x => x.ConversationId, conversationIds); var exeLogDeleted = _dc.ExectionLogs.DeleteMany(filterExeLog); var promptLogDeleted = _dc.LlmCompletionLogs.DeleteMany(filterPromptLog); @@ -274,6 +274,15 @@ public partial class MongoRepository }).ToList(); } + public List GetIdleConversations(int batchSize, int messageLimit) + { + return _dc.ConversationDialogs.AsQueryable() + .Where(x => x.Dialogs != null && x.Dialogs.Count <= messageLimit) + .Take(batchSize) + .Select(x => x.ConversationId) + .ToList(); + } + public bool TruncateConversation(string conversationId, string messageId, bool cleanLog = false) { if (string.IsNullOrEmpty(conversationId) || string.IsNullOrEmpty(messageId)) return false;