Skip to content

Commit 116b58f

Browse files
authored
Merge pull request #331 from iceljc/features/add-clean-idle-conversation
Features/add clean idle conversation
2 parents 2ce526d + 43e18a0 commit 116b58f

File tree

12 files changed

+160
-30
lines changed

12 files changed

+160
-30
lines changed

src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ public interface IConversationService
1313
Task<PagedItems<Conversation>> GetConversations(ConversationFilter filter);
1414
Task<Conversation> UpdateConversationTitle(string id, string title);
1515
Task<List<Conversation>> GetLastConversations();
16-
Task<bool> DeleteConversation(string id);
16+
Task<List<string>> GetIdleConversations(int batchSize, int messageLimit, int bufferHours);
17+
Task<bool> DeleteConversations(IEnumerable<string> ids);
1718
Task<bool> TruncateConversation(string conversationId, string messageId);
1819
Task<List<ContentLogOutputModel>> GetConversationContentLogs(string conversationId);
1920
Task<List<ConversationStateLogModel>> GetConversationStateLogs(string conversationId);

src/Infrastructure/BotSharp.Abstraction/Conversations/Settings/ConversationSetting.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,14 @@ public class ConversationSetting
1111
public bool EnableExecutionLog { get; set; }
1212
public bool EnableContentLog { get; set; }
1313
public bool EnableStateLog { get; set; }
14+
public CleanConversationSetting CleanSetting { get; set; }
15+
}
16+
17+
public class CleanConversationSetting
18+
{
19+
public bool Enable { get; set; }
20+
public int BatchSize { get; set; }
21+
public int MessageLimit { get; set; }
22+
public int BufferHours { get; set; }
23+
1424
}

src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public interface IBotSharpRepository
4848

4949
#region Conversation
5050
void CreateNewConversation(Conversation conversation);
51-
bool DeleteConversation(string conversationId);
51+
bool DeleteConversations(IEnumerable<string> conversationIds);
5252
List<DialogElement> GetConversationDialogs(string conversationId);
5353
void UpdateConversationDialogElements(string conversationId, List<DialogContentUpdateModel> updateElements);
5454
void AppendConversationDialogs(string conversationId, List<DialogElement> dialogs);
@@ -59,6 +59,7 @@ public interface IBotSharpRepository
5959
PagedItems<Conversation> GetConversations(ConversationFilter filter);
6060
void UpdateConversationTitle(string conversationId, string title);
6161
List<Conversation> GetLastConversations();
62+
List<string> GetIdleConversations(int batchSize, int messageLimit, int bufferHours);
6263
bool TruncateConversation(string conversationId, string messageId, bool cleanLog = false);
6364
#endregion
6465

src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ public ConversationService(
2929
_logger = logger;
3030
}
3131

32-
public async Task<bool> DeleteConversation(string id)
32+
public async Task<bool> DeleteConversations(IEnumerable<string> ids)
3333
{
3434
var db = _services.GetRequiredService<IBotSharpRepository>();
35-
var isDeleted = db.DeleteConversation(id);
35+
var isDeleted = db.DeleteConversations(ids);
3636
return await Task.FromResult(isDeleted);
3737
}
3838

@@ -63,6 +63,12 @@ public async Task<List<Conversation>> GetLastConversations()
6363
return db.GetLastConversations();
6464
}
6565

66+
public async Task<List<string>> GetIdleConversations(int batchSize, int messageLimit, int bufferHours)
67+
{
68+
var db = _services.GetRequiredService<IBotSharpRepository>();
69+
return db.GetIdleConversations(batchSize, messageLimit, bufferHours);
70+
}
71+
6672
public async Task<Conversation> NewConversation(Conversation sess)
6773
{
6874
var db = _services.GetRequiredService<IBotSharpRepository>();

src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
using BotSharp.Abstraction.Agents.Models;
21
using BotSharp.Abstraction.Loggers.Models;
32
using BotSharp.Abstraction.Plugins.Models;
4-
using BotSharp.Abstraction.Repositories;
5-
using BotSharp.Abstraction.Repositories.Filters;
63
using BotSharp.Abstraction.Repositories.Models;
74
using BotSharp.Abstraction.Tasks.Models;
85
using BotSharp.Abstraction.Users.Models;
@@ -164,7 +161,7 @@ public void CreateNewConversation(Conversation conversation)
164161
throw new NotImplementedException();
165162
}
166163

167-
public bool DeleteConversation(string conversationId)
164+
public bool DeleteConversations(IEnumerable<string> conversationIds)
168165
{
169166
throw new NotImplementedException();
170167
}
@@ -184,6 +181,11 @@ public List<Conversation> GetLastConversations()
184181
throw new NotImplementedException();
185182
}
186183

184+
public List<string> GetIdleConversations(int batchSize, int messageLimit, int bufferHours)
185+
{
186+
throw new NotImplementedException();
187+
}
188+
187189
public List<DialogElement> GetConversationDialogs(string conversationId)
188190
{
189191
throw new NotImplementedException();

src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,17 @@ public void CreateNewConversation(Conversation conversation)
4444
}
4545
}
4646

47-
public bool DeleteConversation(string conversationId)
47+
public bool DeleteConversations(IEnumerable<string> conversationIds)
4848
{
49-
if (string.IsNullOrEmpty(conversationId)) return false;
49+
if (conversationIds.IsNullOrEmpty()) return false;
5050

51-
var convDir = FindConversationDirectory(conversationId);
52-
if (string.IsNullOrEmpty(convDir)) return false;
51+
foreach (var conversationId in conversationIds)
52+
{
53+
var convDir = FindConversationDirectory(conversationId);
54+
if (string.IsNullOrEmpty(convDir)) continue;
5355

54-
Directory.Delete(convDir, true);
56+
Directory.Delete(convDir, true);
57+
}
5558
return true;
5659
}
5760

@@ -263,6 +266,46 @@ public List<Conversation> GetLastConversations()
263266
.ToList();
264267
}
265268

269+
public List<string> GetIdleConversations(int batchSize, int messageLimit, int bufferHours)
270+
{
271+
var ids = new List<string>();
272+
var batchLimit = 50;
273+
var utcNow = DateTime.UtcNow;
274+
var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir);
275+
276+
if (batchSize <= 0 || batchSize > batchLimit)
277+
{
278+
batchSize = batchLimit;
279+
}
280+
281+
foreach (var d in Directory.GetDirectories(dir))
282+
{
283+
var convFile = Path.Combine(d, CONVERSATION_FILE);
284+
if (!File.Exists(convFile))
285+
{
286+
continue;
287+
}
288+
289+
var json = File.ReadAllText(convFile);
290+
var conv = JsonSerializer.Deserialize<Conversation>(json, _options);
291+
if (conv == null || conv.CreatedTime > utcNow.AddHours(-bufferHours))
292+
{
293+
continue;
294+
}
295+
296+
var dialogs = GetConversationDialogs(conv.Id);
297+
if (dialogs.Count <= messageLimit)
298+
{
299+
ids.Add(conv.Id);
300+
if (ids.Count >= batchSize)
301+
{
302+
return ids;
303+
}
304+
}
305+
}
306+
return ids;
307+
}
308+
266309

267310
public bool TruncateConversation(string conversationId, string messageId, bool cleanLog = false)
268311
{

src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
using BotSharp.Abstraction.Repositories;
21
using System.IO;
32
using FunctionDef = BotSharp.Abstraction.Functions.Models.FunctionDef;
43
using BotSharp.Abstraction.Users.Models;
5-
using BotSharp.Abstraction.Agents.Models;
64
using MongoDB.Driver;
75
using System.Text.Encodings.Web;
86
using BotSharp.Abstraction.Plugins.Models;

src/Infrastructure/BotSharp.OpenAPI/BackgroundServices/ConversationTimeoutService.cs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
using BotSharp.Abstraction.Agents.Enums;
2-
using BotSharp.Abstraction.Conversations.Models;
31
using Microsoft.Extensions.Hosting;
42

53
namespace BotSharp.OpenAPI.BackgroundServices
@@ -23,10 +21,12 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
2321
while (true)
2422
{
2523
stoppingToken.ThrowIfCancellationRequested();
26-
var delay = Task.Delay(TimeSpan.FromMinutes(1));
24+
var delay = Task.Delay(TimeSpan.FromHours(1));
2725
try
2826
{
2927
await CloseIdleConversationsAsync(TimeSpan.FromMinutes(10));
28+
await CleanIdleConversationsAsync();
29+
3030
}
3131
catch (Exception ex)
3232
{
@@ -76,5 +76,22 @@ private async Task CloseIdleConversationsAsync(TimeSpan conversationIdleTimeout)
7676
}
7777
}
7878
}
79+
80+
private async Task CleanIdleConversationsAsync()
81+
{
82+
using var scope = _services.CreateScope();
83+
var settings = scope.ServiceProvider.GetRequiredService<ConversationSetting>();
84+
var cleanSetting = settings.CleanSetting;
85+
86+
if (cleanSetting == null || !cleanSetting.Enable) return;
87+
88+
var conversationService = scope.ServiceProvider.GetRequiredService<IConversationService>();
89+
var conversationIds = await conversationService.GetIdleConversations(cleanSetting.BatchSize, cleanSetting.MessageLimit, cleanSetting.BufferHours);
90+
91+
if (!conversationIds.IsNullOrEmpty())
92+
{
93+
await conversationService.DeleteConversations(conversationIds);
94+
}
95+
}
7996
}
8097
}

src/Infrastructure/BotSharp.OpenAPI/BotSharpOpenApiExtensions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using Microsoft.Net.Http.Headers;
1111
using Microsoft.OpenApi.Models;
1212
using Microsoft.IdentityModel.JsonWebTokens;
13+
using BotSharp.OpenAPI.BackgroundServices;
1314

1415
namespace BotSharp.OpenAPI;
1516

@@ -29,6 +30,7 @@ public static IServiceCollection AddBotSharpOpenAPI(this IServiceCollection serv
2930
bool enableValidation)
3031
{
3132
services.AddScoped<IUserIdentity, UserIdentity>();
33+
services.AddHostedService<ConversationTimeoutService>();
3234

3335
// Add bearer authentication
3436
var schema = "MIXED_SCHEME";

src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ public async Task<ConversationViewModel> GetConversation([FromRoute] string conv
137137
public async Task<bool> DeleteConversation([FromRoute] string conversationId)
138138
{
139139
var conversationService = _services.GetRequiredService<IConversationService>();
140-
var response = await conversationService.DeleteConversation(conversationId);
140+
var response = await conversationService.DeleteConversations(new List<string> { conversationId });
141141
return response;
142142
}
143143

src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
using BotSharp.Abstraction.Repositories.Models;
44
using BotSharp.Plugin.MongoStorage.Collections;
55
using BotSharp.Plugin.MongoStorage.Models;
6-
using MongoDB.Driver;
76

87
namespace BotSharp.Plugin.MongoStorage.Repository;
98

@@ -55,17 +54,17 @@ public void CreateNewConversation(Conversation conversation)
5554
_dc.ConversationStates.InsertOne(stateDoc);
5655
}
5756

58-
public bool DeleteConversation(string conversationId)
57+
public bool DeleteConversations(IEnumerable<string> conversationIds)
5958
{
60-
if (string.IsNullOrEmpty(conversationId)) return false;
59+
if (conversationIds.IsNullOrEmpty()) return false;
6160

62-
var filterConv = Builders<ConversationDocument>.Filter.Eq(x => x.Id, conversationId);
63-
var filterDialog = Builders<ConversationDialogDocument>.Filter.Eq(x => x.ConversationId, conversationId);
64-
var filterSates = Builders<ConversationStateDocument>.Filter.Eq(x => x.ConversationId, conversationId);
65-
var filterExeLog = Builders<ExecutionLogDocument>.Filter.Eq(x => x.ConversationId, conversationId);
66-
var filterPromptLog = Builders<LlmCompletionLogDocument>.Filter.Eq(x => x.ConversationId, conversationId);
67-
var filterContentLog = Builders<ConversationContentLogDocument>.Filter.Eq(x => x.ConversationId, conversationId);
68-
var filterStateLog = Builders<ConversationStateLogDocument>.Filter.Eq(x => x.ConversationId, conversationId);
61+
var filterConv = Builders<ConversationDocument>.Filter.In(x => x.Id, conversationIds);
62+
var filterDialog = Builders<ConversationDialogDocument>.Filter.In(x => x.ConversationId, conversationIds);
63+
var filterSates = Builders<ConversationStateDocument>.Filter.In(x => x.ConversationId, conversationIds);
64+
var filterExeLog = Builders<ExecutionLogDocument>.Filter.In(x => x.ConversationId, conversationIds);
65+
var filterPromptLog = Builders<LlmCompletionLogDocument>.Filter.In(x => x.ConversationId, conversationIds);
66+
var filterContentLog = Builders<ConversationContentLogDocument>.Filter.In(x => x.ConversationId, conversationIds);
67+
var filterStateLog = Builders<ConversationStateLogDocument>.Filter.In(x => x.ConversationId, conversationIds);
6968

7069
var exeLogDeleted = _dc.ExectionLogs.DeleteMany(filterExeLog);
7170
var promptLogDeleted = _dc.LlmCompletionLogs.DeleteMany(filterPromptLog);
@@ -274,6 +273,51 @@ public List<Conversation> GetLastConversations()
274273
}).ToList();
275274
}
276275

276+
public List<string> GetIdleConversations(int batchSize, int messageLimit, int bufferHours)
277+
{
278+
var page = 1;
279+
var pageLimit = 10;
280+
var batchLimit = 50;
281+
var utcNow = DateTime.UtcNow;
282+
var conversationIds = new List<string>();
283+
284+
if (batchSize <= 0 || batchSize > batchLimit)
285+
{
286+
batchSize = batchLimit;
287+
}
288+
289+
while (true && page < pageLimit)
290+
{
291+
var skip = (page - 1) * batchSize;
292+
var candidates = _dc.Conversations.AsQueryable()
293+
.Where(x => x.CreatedTime <= utcNow.AddHours(-bufferHours))
294+
.Skip(skip)
295+
.Take(batchSize)
296+
.Select(x => x.Id)
297+
.ToList();
298+
299+
if (candidates.IsNullOrEmpty())
300+
{
301+
break;
302+
}
303+
304+
var targets = _dc.ConversationDialogs.AsQueryable()
305+
.Where(x => candidates.Contains(x.ConversationId) && x.Dialogs != null && x.Dialogs.Count <= messageLimit)
306+
.Select(x => x.ConversationId)
307+
.ToList();
308+
309+
conversationIds = conversationIds.Concat(targets).ToList();
310+
if (conversationIds.Count >= batchSize)
311+
{
312+
break;
313+
}
314+
315+
page++;
316+
}
317+
318+
return conversationIds.Take(batchSize).ToList();
319+
}
320+
277321
public bool TruncateConversation(string conversationId, string messageId, bool cleanLog = false)
278322
{
279323
if (string.IsNullOrEmpty(conversationId) || string.IsNullOrEmpty(messageId)) return false;

src/WebStarter/appsettings.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,13 @@
108108
"EnableLlmCompletionLog": false,
109109
"EnableExecutionLog": true,
110110
"EnableContentLog": true,
111-
"EnableStateLog": true
111+
"EnableStateLog": true,
112+
"CleanSetting": {
113+
"Enable": true,
114+
"BatchSize": 50,
115+
"MessageLimit": 2,
116+
"BufferHours": 12
117+
}
112118
},
113119

114120
"Statistics": {

0 commit comments

Comments
 (0)