Merge pull request #361 from iceljc/features/refine-conversation-functionality

Features/refine conversation functionality
This commit is contained in:
C. Oceania 2024-03-25 17:31:54 -05:00 committed by GitHub
commit d33bd37615
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 103 additions and 23 deletions

View file

@ -24,6 +24,8 @@ public class Conversation
public string Channel { get; set; } = ConversationChannel.OpenAPI;
public int DialogCount { get; set; }
public DateTime UpdatedTime { get; set; } = DateTime.UtcNow;
public DateTime CreatedTime { get; set; } = DateTime.UtcNow;

View file

@ -16,7 +16,7 @@ public class RichContentJsonConverter : JsonConverter<IRichMessage>
if (root.TryGetProperty("rich_type", out JsonElement element))
{
var richType = element.GetString();
res = parser.ParseRichMessage(richType, jsonText, options);
res = parser.ParseRichMessage(richType, jsonText, root, options);
}
return res;

View file

@ -16,7 +16,7 @@ public class TemplateMessageJsonConverter : JsonConverter<ITemplateMessage>
if (root.TryGetProperty("template_type", out JsonElement element))
{
var templateType = element.GetString();
res = parser.ParseTemplateMessage(templateType, jsonText, options);
res = parser.ParseTemplateMessage(templateType, jsonText, root, options);
}
return res;

View file

@ -12,7 +12,7 @@ public class MessageParser
{
}
public IRichMessage? ParseRichMessage(string richType, string jsonText, JsonSerializerOptions options)
public IRichMessage? ParseRichMessage(string richType, string jsonText, JsonElement root, JsonSerializerOptions options)
{
IRichMessage? res = null;
@ -36,11 +36,22 @@ public class MessageParser
{
res = JsonSerializer.Deserialize<TextMessage>(jsonText, options);
}
else if (richType == RichTypeEnum.GenericTemplate)
{
if (root.TryGetProperty("element_type", out var element))
{
var elementType = element.GetString();
if (elementType == typeof(GenericElement).Name)
{
res = JsonSerializer.Deserialize<GenericTemplateMessage<GenericElement>>(jsonText, options);
}
}
}
return res;
}
public ITemplateMessage? ParseTemplateMessage(string templateType, string jsonText, JsonSerializerOptions options)
public ITemplateMessage? ParseTemplateMessage(string templateType, string jsonText, JsonElement root, JsonSerializerOptions options)
{
ITemplateMessage? res = null;
@ -60,6 +71,17 @@ public class MessageParser
{
res = JsonSerializer.Deserialize<ProductTemplateMessage>(jsonText, options);
}
else if (templateType == TemplateTypeEnum.Generic)
{
if (root.TryGetProperty("element_type", out var element))
{
var elementType = element.GetString();
if (elementType == typeof(GenericElement).Name)
{
res = JsonSerializer.Deserialize<GenericTemplateMessage<GenericElement>>(jsonText, options);
}
}
}
return res;
}

View file

@ -46,7 +46,7 @@ public class ConversationStorage : IConversationStorage
AgentId = agentId,
MessageId = dialog.MessageId,
FunctionName = dialog.FunctionName,
CreateTime = dialog.CreatedAt
CreateTime = DateTime.UtcNow
};
var content = dialog.Content.RemoveNewLine();
@ -65,7 +65,7 @@ public class ConversationStorage : IConversationStorage
MessageId = dialog.MessageId,
SenderId = dialog.SenderId,
FunctionName = dialog.FunctionName,
CreateTime = dialog.CreatedAt
CreateTime = DateTime.UtcNow
};
var content = dialog.Content.RemoveNewLine();

View file

@ -117,6 +117,7 @@ namespace BotSharp.Core.Repository
var conv = JsonSerializer.Deserialize<Conversation>(json, _options);
if (conv != null)
{
conv.DialogCount += dialogs.Count();
conv.UpdatedTime = DateTime.UtcNow;
File.WriteAllText(convFile, JsonSerializer.Serialize(conv, _options));
}
@ -353,23 +354,40 @@ namespace BotSharp.Core.Repository
batchSize = batchLimit;
}
if (bufferHours <= 0)
{
bufferHours = 12;
}
if (messageLimit <= 0)
{
messageLimit = 2;
}
foreach (var d in Directory.GetDirectories(dir))
{
var convFile = Path.Combine(d, CONVERSATION_FILE);
if (!File.Exists(convFile))
{
Directory.Delete(d, true);
continue;
}
var json = File.ReadAllText(convFile);
var conv = JsonSerializer.Deserialize<Conversation>(json, _options);
if (conv == null || conv.UpdatedTime > utcNow.AddHours(-bufferHours))
if (conv == null)
{
Directory.Delete(d, true);
continue;
}
if (conv.UpdatedTime > utcNow.AddHours(-bufferHours))
{
continue;
}
var dialogs = GetConversationDialogs(conv.Id);
if (dialogs.Count <= messageLimit)
if (conv.DialogCount <= messageLimit)
{
ids.Add(conv.Id);
if (ids.Count >= batchSize)
@ -398,7 +416,7 @@ namespace BotSharp.Core.Repository
if (foundIdx < 0) return false;
// Handle truncated dialogs
var isSaved = HandleTruncatedDialogs(dialogDir, dialogs, foundIdx);
var isSaved = HandleTruncatedDialogs(convDir, dialogDir, dialogs, foundIdx);
if (!isSaved) return false;
// Handle truncated states
@ -489,10 +507,18 @@ namespace BotSharp.Core.Repository
return states ?? new List<StateKeyValue>();
}
private bool HandleTruncatedDialogs(string dialogDir, List<DialogElement> dialogs, int foundIdx)
private bool HandleTruncatedDialogs(string convDir, string dialogDir, List<DialogElement> dialogs, int foundIdx)
{
var truncatedDialogs = dialogs.Where((x, idx) => idx < foundIdx).ToList();
var isSaved = SaveTruncatedDialogs(dialogDir, truncatedDialogs);
var convFile = Path.Combine(convDir, CONVERSATION_FILE);
var convJson = File.ReadAllText(convFile);
var conv = JsonSerializer.Deserialize<Conversation>(convJson, _options);
if (conv != null)
{
conv.DialogCount = truncatedDialogs.Count;
File.WriteAllText(convFile, JsonSerializer.Serialize(conv, _options));
}
return isSaved;
}

View file

@ -15,7 +15,18 @@ namespace BotSharp.OpenAPI.BackgroundServices
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Conversation Timeout Service is running.");
_logger.LogInformation("Conversation Timeout Service is running...");
_ = Task.Run(async () =>
{
await DoWork(stoppingToken);
});
}
private async Task DoWork(CancellationToken stoppingToken)
{
_logger.LogInformation("Conversation Timeout Service is doing work...");
try
{
while (true)

View file

@ -8,6 +8,7 @@ public class ConversationDocument : MongoBase
public string Title { get; set; }
public string Channel { get; set; }
public string Status { get; set; }
public int DialogCount { get; set; }
public DateTime CreatedTime { get; set; }
public DateTime UpdatedTime { get; set; }
public DateTime Breakpoint { get; set; }

View file

@ -123,7 +123,8 @@ public partial class MongoRepository
var filterDialog = Builders<ConversationDialogDocument>.Filter.Eq(x => x.ConversationId, conversationId);
var dialogElements = dialogs.Select(x => DialogMongoElement.ToMongoElement(x)).ToList();
var updateDialog = Builders<ConversationDialogDocument>.Update.PushEach(x => x.Dialogs, dialogElements);
var updateConv = Builders<ConversationDocument>.Update.Set(x => x.UpdatedTime, DateTime.UtcNow);
var updateConv = Builders<ConversationDocument>.Update.Set(x => x.UpdatedTime, DateTime.UtcNow)
.Inc(x => x.DialogCount, dialogs.Count);
_dc.ConversationDialogs.UpdateOne(filterDialog, updateDialog);
_dc.Conversations.UpdateOne(filterConv, updateConv);
@ -168,13 +169,16 @@ public partial class MongoRepository
public void UpdateConversationStates(string conversationId, List<StateKeyValue> states)
{
if (string.IsNullOrEmpty(conversationId) || states.IsNullOrEmpty()) return;
if (string.IsNullOrEmpty(conversationId) || states == null) return;
var filterConv = Builders<ConversationDocument>.Filter.Eq(x => x.Id, conversationId);
var filterStates = Builders<ConversationStateDocument>.Filter.Eq(x => x.ConversationId, conversationId);
var saveStates = states.Select(x => StateMongoElement.ToMongoElement(x)).ToList();
var updateStates = Builders<ConversationStateDocument>.Update.Set(x => x.States, saveStates);
var updateConv = Builders<ConversationDocument>.Update.Set(x => x.UpdatedTime, DateTime.UtcNow);
_dc.ConversationStates.UpdateOne(filterStates, updateStates);
_dc.Conversations.UpdateOne(filterConv, updateConv);
}
public void UpdateConversationStatus(string conversationId, string status)
@ -220,6 +224,7 @@ public partial class MongoRepository
Status = conv.Status,
Dialogs = dialogElements,
States = curStates,
DialogCount = conv.DialogCount,
CreatedTime = conv.CreatedTime,
UpdatedTime = conv.UpdatedTime,
Breakpoint = conv.Breakpoint
@ -308,6 +313,7 @@ public partial class MongoRepository
Title = conv.Title,
Channel = conv.Channel,
Status = conv.Status,
DialogCount = conv.DialogCount,
CreatedTime = conv.CreatedTime,
UpdatedTime = conv.UpdatedTime,
Breakpoint = conv.Breakpoint
@ -335,6 +341,7 @@ public partial class MongoRepository
Title = c.Title,
Channel = c.Channel,
Status = c.Status,
DialogCount = c.DialogCount,
CreatedTime = c.CreatedTime,
UpdatedTime = c.UpdatedTime,
Breakpoint = c.Breakpoint
@ -353,11 +360,21 @@ public partial class MongoRepository
batchSize = batchLimit;
}
if (bufferHours <= 0)
{
bufferHours = 12;
}
if (messageLimit <= 0)
{
messageLimit = 2;
}
while (true)
{
var skip = (page - 1) * batchSize;
var candidates = _dc.Conversations.AsQueryable()
.Where(x => x.UpdatedTime <= utcNow.AddHours(-bufferHours))
.Where(x => (x.DialogCount <= messageLimit) && x.UpdatedTime <= utcNow.AddHours(-bufferHours))
.Skip(skip)
.Take(batchSize)
.Select(x => x.Id)
@ -367,13 +384,8 @@ public partial class MongoRepository
{
break;
}
var targets = _dc.ConversationDialogs.AsQueryable()
.Where(x => candidates.Contains(x.ConversationId) && x.Dialogs != null && x.Dialogs.Count <= messageLimit)
.Select(x => x.ConversationId)
.ToList();
conversationIds = conversationIds.Concat(targets).ToList();
conversationIds = conversationIds.Concat(candidates).Distinct().ToList();
if (conversationIds.Count >= batchSize)
{
break;
@ -420,10 +432,16 @@ public partial class MongoRepository
_dc.ConversationStates.ReplaceOne(stateFilter, foundStates);
}
// Save
// Save dialogs
foundDialog.Dialogs = truncatedDialogs;
_dc.ConversationDialogs.ReplaceOne(dialogFilter, foundDialog);
// Update conversation
var convFilter = Builders<ConversationDocument>.Filter.Eq(x => x.Id, conversationId);
var updateConv = Builders<ConversationDocument>.Update.Set(x => x.UpdatedTime, DateTime.UtcNow)
.Set(x => x.DialogCount, truncatedDialogs.Count);
_dc.Conversations.UpdateOne(convFilter, updateConv);
// Remove logs
if (cleanLog)
{