Skip to content

Commit 4f5d8fc

Browse files
authored
Merge pull request #205 from seplz/feature/CloseIdleConversations
add ConversationTimeoutService
2 parents 3eb2e76 + a0d8abe commit 4f5d8fc

File tree

8 files changed

+138
-5
lines changed

8 files changed

+138
-5
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ public interface IConversationService
88
void SetConversationId(string conversationId, List<string> states);
99
Task<Conversation> GetConversation(string id);
1010
Task<List<Conversation>> GetConversations();
11+
Task<List<Conversation>> GetLastConversations();
1112
Task DeleteConversation(string id);
1213

1314
/// <summary>

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ List<Agent> GetAgents(string? name = null, bool? disabled = null, bool? allowRou
3434
void UpdateConversationStates(string conversationId, List<StateKeyValue> states);
3535
Conversation GetConversation(string conversationId);
3636
List<Conversation> GetConversations(string userId);
37+
List<Conversation> GetLastConversations();
3738
void AddExectionLogs(string conversationId, List<string> logs);
3839
List<string> GetExectionLogs(string conversationId);
3940
#endregion

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ public async Task<List<Conversation>> GetConversations()
5252
return conversations.OrderByDescending(x => x.CreatedTime).ToList();
5353
}
5454

55+
public async Task<List<Conversation>> GetLastConversations()
56+
{
57+
var db = _services.GetRequiredService<IBotSharpRepository>();
58+
return db.GetLastConversations();
59+
}
60+
5561
public async Task<Conversation> NewConversation(Conversation sess)
5662
{
5763
var db = _services.GetRequiredService<IBotSharpRepository>();

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,11 @@ public List<Conversation> GetConversations(string userId)
131131
throw new NotImplementedException();
132132
}
133133

134+
public List<Conversation> GetLastConversations()
135+
{
136+
throw new NotImplementedException();
137+
}
138+
134139
public string GetConversationDialog(string conversationId)
135140
{
136141
throw new NotImplementedException();

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,28 @@ public List<Conversation> GetConversations(string userId)
743743
return records;
744744
}
745745

746+
public List<Conversation> GetLastConversations()
747+
{
748+
var records = new List<Conversation>();
749+
var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir);
750+
751+
foreach (var d in Directory.GetDirectories(dir))
752+
{
753+
var path = Path.Combine(d, "conversation.json");
754+
if (!File.Exists(path)) continue;
755+
756+
var json = File.ReadAllText(path);
757+
var record = JsonSerializer.Deserialize<Conversation>(json, _options);
758+
if (record != null)
759+
{
760+
records.Add(record);
761+
}
762+
}
763+
return records.GroupBy(r => r.UserId)
764+
.Select(g => g.OrderByDescending(x => x.CreatedTime).First())
765+
.ToList();
766+
}
767+
746768
public void AddExectionLogs(string conversationId, List<string> logs)
747769
{
748770
if (string.IsNullOrEmpty(conversationId) || logs.IsNullOrEmpty()) return;

src/Infrastructure/BotSharp.Core/Users/Services/UserIdentity.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace BotSharp.Core.Users.Services;
66
public class UserIdentity : IUserIdentity
77
{
88
private readonly IHttpContextAccessor _contextAccessor;
9-
private IEnumerable<Claim> _claims => _contextAccessor.HttpContext.User.Claims;
9+
private IEnumerable<Claim> _claims => _contextAccessor.HttpContext?.User.Claims!;
1010

1111
public UserIdentity(IHttpContextAccessor contextAccessor)
1212
{
@@ -15,14 +15,14 @@ public UserIdentity(IHttpContextAccessor contextAccessor)
1515

1616

1717
public string Id
18-
=> _claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value;
18+
=> _claims?.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value!;
1919

2020
public string Email
21-
=> _claims.FirstOrDefault(x => x.Type == ClaimTypes.Email)?.Value;
21+
=> _claims?.FirstOrDefault(x => x.Type == ClaimTypes.Email)?.Value!;
2222

2323
public string FirstName
24-
=> _claims.FirstOrDefault(x => x.Type == ClaimTypes.GivenName)?.Value;
24+
=> _claims?.FirstOrDefault(x => x.Type == ClaimTypes.GivenName)?.Value!;
2525

2626
public string LastName
27-
=> _claims.FirstOrDefault(x => x.Type == ClaimTypes.Surname)?.Value;
27+
=> _claims?.FirstOrDefault(x => x.Type == ClaimTypes.Surname)?.Value!;
2828
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using BotSharp.Abstraction.Agents.Enums;
2+
using BotSharp.Abstraction.Conversations.Models;
3+
using Microsoft.Extensions.Hosting;
4+
5+
namespace BotSharp.OpenAPI.BackgroundServices
6+
{
7+
public class ConversationTimeoutService : BackgroundService
8+
{
9+
private readonly IServiceProvider _services;
10+
private readonly ILogger<ConversationTimeoutService> _logger;
11+
12+
public ConversationTimeoutService(IServiceProvider services, ILogger<ConversationTimeoutService> logger)
13+
{
14+
_services = services;
15+
_logger = logger;
16+
}
17+
18+
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
19+
{
20+
_logger.LogInformation("Conversation Timeout Service is running.");
21+
try
22+
{
23+
while (true)
24+
{
25+
stoppingToken.ThrowIfCancellationRequested();
26+
var delay = Task.Delay(TimeSpan.FromMinutes(1));
27+
try
28+
{
29+
await CloseIdleConversationsAsync(TimeSpan.FromMinutes(10));
30+
}
31+
catch (Exception ex)
32+
{
33+
_logger.LogError(ex, $"Error occurred closing conversations.");
34+
}
35+
await delay;
36+
}
37+
}
38+
catch (OperationCanceledException) { }
39+
}
40+
41+
public override async Task StopAsync(CancellationToken stoppingToken)
42+
{
43+
_logger.LogInformation("Conversation Timeout Service is stopping.");
44+
await base.StopAsync(stoppingToken);
45+
}
46+
47+
private async Task CloseIdleConversationsAsync(TimeSpan conversationIdleTimeout)
48+
{
49+
using var scope = _services.CreateScope();
50+
var conversationService = scope.ServiceProvider.GetRequiredService<IConversationService>();
51+
var hooks = scope.ServiceProvider.GetServices<IConversationHook>()
52+
.OrderBy(x => x.Priority)
53+
.ToList();
54+
var moment = DateTime.UtcNow.Add(-conversationIdleTimeout);
55+
var conversations =
56+
(await conversationService.GetLastConversations())
57+
.Where(c => c.CreatedTime <= moment);
58+
foreach (var conversation in conversations)
59+
{
60+
try
61+
{
62+
var response = new RoleDialogModel(AgentRole.Assistant, "End the conversation due to timeout.")
63+
{
64+
StopCompletion = true,
65+
FunctionName = "conversation_end"
66+
};
67+
68+
foreach (var hook in hooks)
69+
{
70+
await hook.OnConversationEnding(response);
71+
}
72+
}
73+
catch (Exception ex)
74+
{
75+
_logger.LogError(ex, $"Error occurred closing conversation #{conversation.Id}.");
76+
}
77+
}
78+
}
79+
}
80+
}

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -732,6 +732,24 @@ public List<Conversation> GetConversations(string userId)
732732
return records;
733733
}
734734

735+
public List<Conversation> GetLastConversations()
736+
{
737+
var records = new List<Conversation>();
738+
var conversations = _dc.Conversations.Aggregate()
739+
.Group(c => c.UserId,
740+
g => g.OrderByDescending(x => x.CreatedTime).First())
741+
.ToList();
742+
return conversations.Select(c => new Conversation()
743+
{
744+
Id = c.Id.ToString(),
745+
AgentId = c.AgentId.ToString(),
746+
UserId = c.UserId.ToString(),
747+
Title = c.Title,
748+
CreatedTime = c.CreatedTime,
749+
UpdatedTime = c.UpdatedTime
750+
}).ToList();
751+
}
752+
735753
public void AddExectionLogs(string conversationId, List<string> logs)
736754
{
737755
if (string.IsNullOrEmpty(conversationId) || logs.IsNullOrEmpty()) return;

0 commit comments

Comments
 (0)