Merge pull request #205 from seplz/feature/CloseIdleConversations
add ConversationTimeoutService
This commit is contained in:
commit
4f5d8fc0a1
|
|
@ -8,6 +8,7 @@ public interface IConversationService
|
||||||
void SetConversationId(string conversationId, List<string> states);
|
void SetConversationId(string conversationId, List<string> states);
|
||||||
Task<Conversation> GetConversation(string id);
|
Task<Conversation> GetConversation(string id);
|
||||||
Task<List<Conversation>> GetConversations();
|
Task<List<Conversation>> GetConversations();
|
||||||
|
Task<List<Conversation>> GetLastConversations();
|
||||||
Task DeleteConversation(string id);
|
Task DeleteConversation(string id);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ public interface IBotSharpRepository
|
||||||
void UpdateConversationStates(string conversationId, List<StateKeyValue> states);
|
void UpdateConversationStates(string conversationId, List<StateKeyValue> states);
|
||||||
Conversation GetConversation(string conversationId);
|
Conversation GetConversation(string conversationId);
|
||||||
List<Conversation> GetConversations(string userId);
|
List<Conversation> GetConversations(string userId);
|
||||||
|
List<Conversation> GetLastConversations();
|
||||||
void AddExectionLogs(string conversationId, List<string> logs);
|
void AddExectionLogs(string conversationId, List<string> logs);
|
||||||
List<string> GetExectionLogs(string conversationId);
|
List<string> GetExectionLogs(string conversationId);
|
||||||
#endregion
|
#endregion
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,12 @@ public partial class ConversationService : IConversationService
|
||||||
return conversations.OrderByDescending(x => x.CreatedTime).ToList();
|
return conversations.OrderByDescending(x => x.CreatedTime).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<List<Conversation>> GetLastConversations()
|
||||||
|
{
|
||||||
|
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||||
|
return db.GetLastConversations();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<Conversation> NewConversation(Conversation sess)
|
public async Task<Conversation> NewConversation(Conversation sess)
|
||||||
{
|
{
|
||||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,11 @@ public class BotSharpDbContext : Database, IBotSharpRepository
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Conversation> GetLastConversations()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
public string GetConversationDialog(string conversationId)
|
public string GetConversationDialog(string conversationId)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
|
|
|
||||||
|
|
@ -743,6 +743,28 @@ public class FileRepository : IBotSharpRepository
|
||||||
return records;
|
return records;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Conversation> GetLastConversations()
|
||||||
|
{
|
||||||
|
var records = new List<Conversation>();
|
||||||
|
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<Conversation>(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<string> logs)
|
public void AddExectionLogs(string conversationId, List<string> logs)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(conversationId) || logs.IsNullOrEmpty()) return;
|
if (string.IsNullOrEmpty(conversationId) || logs.IsNullOrEmpty()) return;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ namespace BotSharp.Core.Users.Services;
|
||||||
public class UserIdentity : IUserIdentity
|
public class UserIdentity : IUserIdentity
|
||||||
{
|
{
|
||||||
private readonly IHttpContextAccessor _contextAccessor;
|
private readonly IHttpContextAccessor _contextAccessor;
|
||||||
private IEnumerable<Claim> _claims => _contextAccessor.HttpContext.User.Claims;
|
private IEnumerable<Claim> _claims => _contextAccessor.HttpContext?.User.Claims!;
|
||||||
|
|
||||||
public UserIdentity(IHttpContextAccessor contextAccessor)
|
public UserIdentity(IHttpContextAccessor contextAccessor)
|
||||||
{
|
{
|
||||||
|
|
@ -15,14 +15,14 @@ public class UserIdentity : IUserIdentity
|
||||||
|
|
||||||
|
|
||||||
public string Id
|
public string Id
|
||||||
=> _claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value;
|
=> _claims?.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value!;
|
||||||
|
|
||||||
public string Email
|
public string Email
|
||||||
=> _claims.FirstOrDefault(x => x.Type == ClaimTypes.Email)?.Value;
|
=> _claims?.FirstOrDefault(x => x.Type == ClaimTypes.Email)?.Value!;
|
||||||
|
|
||||||
public string FirstName
|
public string FirstName
|
||||||
=> _claims.FirstOrDefault(x => x.Type == ClaimTypes.GivenName)?.Value;
|
=> _claims?.FirstOrDefault(x => x.Type == ClaimTypes.GivenName)?.Value!;
|
||||||
|
|
||||||
public string LastName
|
public string LastName
|
||||||
=> _claims.FirstOrDefault(x => x.Type == ClaimTypes.Surname)?.Value;
|
=> _claims?.FirstOrDefault(x => x.Type == ClaimTypes.Surname)?.Value!;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
using BotSharp.Abstraction.Agents.Enums;
|
||||||
|
using BotSharp.Abstraction.Conversations.Models;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
|
||||||
|
namespace BotSharp.OpenAPI.BackgroundServices
|
||||||
|
{
|
||||||
|
public class ConversationTimeoutService : BackgroundService
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider _services;
|
||||||
|
private readonly ILogger<ConversationTimeoutService> _logger;
|
||||||
|
|
||||||
|
public ConversationTimeoutService(IServiceProvider services, ILogger<ConversationTimeoutService> 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<IConversationService>();
|
||||||
|
var hooks = scope.ServiceProvider.GetServices<IConversationHook>()
|
||||||
|
.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}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -732,6 +732,24 @@ public class MongoRepository : IBotSharpRepository
|
||||||
return records;
|
return records;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Conversation> GetLastConversations()
|
||||||
|
{
|
||||||
|
var records = new List<Conversation>();
|
||||||
|
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<string> logs)
|
public void AddExectionLogs(string conversationId, List<string> logs)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(conversationId) || logs.IsNullOrEmpty()) return;
|
if (string.IsNullOrEmpty(conversationId) || logs.IsNullOrEmpty()) return;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue