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

381 lines
16 KiB
C#
Raw Normal View History

2024-01-13 02:13:38 +00:00
using BotSharp.Abstraction.Conversations.Models;
using BotSharp.Abstraction.Repositories.Filters;
using BotSharp.Abstraction.Repositories.Models;
using BotSharp.Plugin.MongoStorage.Collections;
using BotSharp.Plugin.MongoStorage.Models;
namespace BotSharp.Plugin.MongoStorage.Repository;
public partial class MongoRepository
{
public void CreateNewConversation(Conversation conversation)
{
if (conversation == null) return;
var convDoc = new ConversationDocument
{
Id = !string.IsNullOrEmpty(conversation.Id) ? conversation.Id : Guid.NewGuid().ToString(),
AgentId = conversation.AgentId,
UserId = !string.IsNullOrEmpty(conversation.UserId) ? conversation.UserId : string.Empty,
Title = conversation.Title,
Channel = conversation.Channel,
2024-02-12 15:14:33 +00:00
TaskId = conversation.TaskId,
2024-01-13 02:13:38 +00:00
Status = conversation.Status,
CreatedTime = DateTime.UtcNow,
UpdatedTime = DateTime.UtcNow,
};
var dialogDoc = new ConversationDialogDocument
{
Id = Guid.NewGuid().ToString(),
ConversationId = convDoc.Id,
Dialogs = new List<DialogMongoElement>()
};
var states = conversation.States ?? new Dictionary<string, string>();
var initialStates = states.Select(x => new StateMongoElement
{
Key = x.Key,
Values = new List<StateValueMongoElement>
{
new StateValueMongoElement { Data = x.Value, UpdateTime = DateTime.UtcNow }
}
}).ToList();
var stateDoc = new ConversationStateDocument
{
Id = Guid.NewGuid().ToString(),
ConversationId = convDoc.Id,
States = initialStates
};
_dc.Conversations.InsertOne(convDoc);
_dc.ConversationDialogs.InsertOne(dialogDoc);
_dc.ConversationStates.InsertOne(stateDoc);
}
public bool DeleteConversations(IEnumerable<string> conversationIds)
2024-01-13 02:13:38 +00:00
{
if (conversationIds.IsNullOrEmpty()) return false;
2024-01-13 02:13:38 +00:00
var filterConv = Builders<ConversationDocument>.Filter.In(x => x.Id, conversationIds);
var filterDialog = Builders<ConversationDialogDocument>.Filter.In(x => x.ConversationId, conversationIds);
var filterSates = Builders<ConversationStateDocument>.Filter.In(x => x.ConversationId, conversationIds);
var filterExeLog = Builders<ExecutionLogDocument>.Filter.In(x => x.ConversationId, conversationIds);
var filterPromptLog = Builders<LlmCompletionLogDocument>.Filter.In(x => x.ConversationId, conversationIds);
var filterContentLog = Builders<ConversationContentLogDocument>.Filter.In(x => x.ConversationId, conversationIds);
var filterStateLog = Builders<ConversationStateLogDocument>.Filter.In(x => x.ConversationId, conversationIds);
2024-01-13 02:13:38 +00:00
var exeLogDeleted = _dc.ExectionLogs.DeleteMany(filterExeLog);
var promptLogDeleted = _dc.LlmCompletionLogs.DeleteMany(filterPromptLog);
2024-02-15 20:43:36 +00:00
var contentLogDeleted = _dc.ContentLogs.DeleteMany(filterContentLog);
var stateLogDeleted = _dc.StateLogs.DeleteMany(filterStateLog);
2024-01-13 02:13:38 +00:00
var statesDeleted = _dc.ConversationStates.DeleteMany(filterSates);
var dialogDeleted = _dc.ConversationDialogs.DeleteMany(filterDialog);
var convDeleted = _dc.Conversations.DeleteMany(filterConv);
2024-02-15 20:43:36 +00:00
2024-01-13 02:13:38 +00:00
return convDeleted.DeletedCount > 0 || dialogDeleted.DeletedCount > 0 || statesDeleted.DeletedCount > 0
2024-02-15 20:43:36 +00:00
|| exeLogDeleted.DeletedCount > 0 || promptLogDeleted.DeletedCount > 0
|| contentLogDeleted.DeletedCount > 0 || stateLogDeleted.DeletedCount > 0;
2024-01-13 02:13:38 +00:00
}
public List<DialogElement> GetConversationDialogs(string conversationId)
{
var dialogs = new List<DialogElement>();
if (string.IsNullOrEmpty(conversationId)) return dialogs;
var filter = Builders<ConversationDialogDocument>.Filter.Eq(x => x.ConversationId, conversationId);
var foundDialog = _dc.ConversationDialogs.Find(filter).FirstOrDefault();
if (foundDialog == null) return dialogs;
var formattedDialog = foundDialog.Dialogs?.Select(x => DialogMongoElement.ToDomainElement(x))?.ToList();
return formattedDialog ?? new List<DialogElement>();
}
public void UpdateConversationDialogElements(string conversationId, List<DialogContentUpdateModel> updateElements)
{
if (string.IsNullOrEmpty(conversationId) || updateElements.IsNullOrEmpty()) return;
var filterDialog = Builders<ConversationDialogDocument>.Filter.Eq(x => x.ConversationId, conversationId);
var foundDialog = _dc.ConversationDialogs.Find(filterDialog).FirstOrDefault();
if (foundDialog == null || foundDialog.Dialogs.IsNullOrEmpty()) return;
foundDialog.Dialogs = foundDialog.Dialogs.Select((x, idx) =>
{
var found = updateElements.FirstOrDefault(e => e.Index == idx);
if (found != null)
{
x.Content = found.UpdateContent;
}
return x;
}).ToList();
_dc.ConversationDialogs.ReplaceOne(filterDialog, foundDialog);
}
public void AppendConversationDialogs(string conversationId, List<DialogElement> dialogs)
{
if (string.IsNullOrEmpty(conversationId)) return;
var filterConv = Builders<ConversationDocument>.Filter.Eq(x => x.Id, conversationId);
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);
_dc.ConversationDialogs.UpdateOne(filterDialog, updateDialog);
_dc.Conversations.UpdateOne(filterConv, updateConv);
}
public void UpdateConversationTitle(string conversationId, string title)
{
if (string.IsNullOrEmpty(conversationId)) return;
var filterConv = Builders<ConversationDocument>.Filter.Eq(x => x.Id, conversationId);
var updateConv = Builders<ConversationDocument>.Update
.Set(x => x.UpdatedTime, DateTime.UtcNow)
.Set(x => x.Title, title);
_dc.Conversations.UpdateOne(filterConv, updateConv);
}
public ConversationState GetConversationStates(string conversationId)
{
var states = new ConversationState();
if (string.IsNullOrEmpty(conversationId)) return states;
var filter = Builders<ConversationStateDocument>.Filter.Eq(x => x.ConversationId, conversationId);
var foundStates = _dc.ConversationStates.Find(filter).FirstOrDefault();
if (foundStates == null || foundStates.States.IsNullOrEmpty()) return states;
var savedStates = foundStates.States.Select(x => StateMongoElement.ToDomainElement(x)).ToList();
return new ConversationState(savedStates);
}
public void UpdateConversationStates(string conversationId, List<StateKeyValue> states)
{
if (string.IsNullOrEmpty(conversationId) || states.IsNullOrEmpty()) return;
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);
_dc.ConversationStates.UpdateOne(filterStates, updateStates);
}
public void UpdateConversationStatus(string conversationId, string status)
{
if (string.IsNullOrEmpty(conversationId) || string.IsNullOrEmpty(status)) return;
var filter = Builders<ConversationDocument>.Filter.Eq(x => x.Id, conversationId);
var update = Builders<ConversationDocument>.Update
.Set(x => x.Status, status)
.Set(x => x.UpdatedTime, DateTime.UtcNow);
_dc.Conversations.UpdateOne(filter, update);
}
public Conversation GetConversation(string conversationId)
{
if (string.IsNullOrEmpty(conversationId)) return null;
var filterConv = Builders<ConversationDocument>.Filter.Eq(x => x.Id, conversationId);
var filterDialog = Builders<ConversationDialogDocument>.Filter.Eq(x => x.ConversationId, conversationId);
var filterState = Builders<ConversationStateDocument>.Filter.Eq(x => x.ConversationId, conversationId);
var conv = _dc.Conversations.Find(filterConv).FirstOrDefault();
var dialog = _dc.ConversationDialogs.Find(filterDialog).FirstOrDefault();
var states = _dc.ConversationStates.Find(filterState).FirstOrDefault();
if (conv == null) return null;
var dialogElements = dialog?.Dialogs?.Select(x => DialogMongoElement.ToDomainElement(x))?.ToList() ?? new List<DialogElement>();
var curStates = new Dictionary<string, string>();
states.States.ForEach(x =>
{
curStates[x.Key] = x.Values?.LastOrDefault()?.Data ?? string.Empty;
});
return new Conversation
{
Id = conv.Id.ToString(),
AgentId = conv.AgentId.ToString(),
UserId = conv.UserId.ToString(),
Title = conv.Title,
Channel = conv.Channel,
Status = conv.Status,
Dialogs = dialogElements,
States = curStates,
CreatedTime = conv.CreatedTime,
UpdatedTime = conv.UpdatedTime
};
}
2024-01-18 05:23:20 +00:00
public PagedItems<Conversation> GetConversations(ConversationFilter filter)
2024-01-13 02:13:38 +00:00
{
2024-01-16 01:51:56 +00:00
var conversations = new List<Conversation>();
2024-01-13 02:13:38 +00:00
var builder = Builders<ConversationDocument>.Filter;
2024-01-16 01:51:56 +00:00
var filters = new List<FilterDefinition<ConversationDocument>>() { builder.Empty };
2024-01-13 02:13:38 +00:00
if (!string.IsNullOrEmpty(filter.Id)) filters.Add(builder.Eq(x => x.Id, filter.Id));
2024-01-13 02:13:38 +00:00
if (!string.IsNullOrEmpty(filter.AgentId)) filters.Add(builder.Eq(x => x.AgentId, filter.AgentId));
if (!string.IsNullOrEmpty(filter.Status)) filters.Add(builder.Eq(x => x.Status, filter.Status));
if (!string.IsNullOrEmpty(filter.Channel)) filters.Add(builder.Eq(x => x.Channel, filter.Channel));
if (!string.IsNullOrEmpty(filter.UserId)) filters.Add(builder.Eq(x => x.UserId, filter.UserId));
2024-02-12 15:14:33 +00:00
if (!string.IsNullOrEmpty(filter.TaskId)) filters.Add(builder.Eq(x => x.TaskId, filter.TaskId));
2024-01-13 02:13:38 +00:00
2024-01-18 05:23:20 +00:00
var filterDef = builder.And(filters);
2024-02-04 05:42:13 +00:00
var sortDef = Builders<ConversationDocument>.Sort.Descending(x => x.CreatedTime);
2024-01-23 05:04:06 +00:00
var pager = filter?.Pager ?? new Pagination();
2024-02-04 05:42:13 +00:00
var conversationDocs = _dc.Conversations.Find(filterDef).Sort(sortDef).Skip(pager.Offset).Limit(pager.Size).ToList();
2024-01-18 05:23:20 +00:00
var count = _dc.Conversations.CountDocuments(filterDef);
2024-01-13 02:13:38 +00:00
2024-01-16 01:51:56 +00:00
foreach (var conv in conversationDocs)
2024-01-13 02:13:38 +00:00
{
var convId = conv.Id.ToString();
2024-01-16 01:51:56 +00:00
conversations.Add(new Conversation
2024-01-13 02:13:38 +00:00
{
Id = convId,
AgentId = conv.AgentId.ToString(),
UserId = conv.UserId.ToString(),
2024-02-12 15:14:33 +00:00
TaskId = conv.TaskId,
2024-01-13 02:13:38 +00:00
Title = conv.Title,
Channel = conv.Channel,
Status = conv.Status,
CreatedTime = conv.CreatedTime,
UpdatedTime = conv.UpdatedTime
});
}
2024-01-18 05:23:20 +00:00
return new PagedItems<Conversation>
{
Items = conversations,
Count = (int)count
};
2024-01-13 02:13:38 +00:00
}
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,
Channel = c.Channel,
Status = c.Status,
CreatedTime = c.CreatedTime,
UpdatedTime = c.UpdatedTime
}).ToList();
}
2024-01-29 01:33:40 +00:00
2024-03-07 17:25:23 +00:00
public List<string> GetIdleConversations(int batchSize, int messageLimit, int bufferHours)
{
2024-03-07 17:25:23 +00:00
var page = 1;
2024-03-07 19:38:38 +00:00
var pageLimit = 10;
2024-03-07 17:25:23 +00:00
var batchLimit = 50;
2024-03-07 17:30:51 +00:00
var utcNow = DateTime.UtcNow;
2024-03-07 17:25:23 +00:00
var conversationIds = new List<string>();
if (batchSize <= 0 || batchSize > batchLimit)
{
batchSize = batchLimit;
}
2024-03-07 19:38:38 +00:00
while (true && page < pageLimit)
2024-03-07 17:25:23 +00:00
{
var skip = (page - 1) * batchSize;
var candidates = _dc.Conversations.AsQueryable()
2024-03-07 17:30:51 +00:00
.Where(x => x.CreatedTime <= utcNow.AddHours(-bufferHours))
2024-03-07 17:25:23 +00:00
.Skip(skip)
.Take(batchSize)
.Select(x => x.Id)
.ToList();
if (candidates.IsNullOrEmpty())
{
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();
if (conversationIds.Count >= batchSize)
{
break;
}
page++;
}
return conversationIds.Take(batchSize).ToList();
}
2024-02-28 23:37:36 +00:00
public bool TruncateConversation(string conversationId, string messageId, bool cleanLog = false)
2024-01-29 01:33:40 +00:00
{
if (string.IsNullOrEmpty(conversationId) || string.IsNullOrEmpty(messageId)) return false;
var dialogFilter = Builders<ConversationDialogDocument>.Filter.Eq(x => x.ConversationId, conversationId);
var foundDialog = _dc.ConversationDialogs.Find(dialogFilter).FirstOrDefault();
if (foundDialog == null || foundDialog.Dialogs.IsNullOrEmpty()) return false;
var foundIdx = foundDialog.Dialogs.FindIndex(x => x.MetaData?.MessageId == messageId);
if (foundIdx < 0) return false;
// Handle truncated dialogs
var truncatedDialogs = foundDialog.Dialogs.Where((x, idx) => idx < foundIdx).ToList();
// Handle truncated states
var refTime = foundDialog.Dialogs.ElementAt(foundIdx).MetaData.CreateTime;
var stateFilter = Builders<ConversationStateDocument>.Filter.Eq(x => x.ConversationId, conversationId);
var foundStates = _dc.ConversationStates.Find(stateFilter).FirstOrDefault();
if (foundStates == null || foundStates.States.IsNullOrEmpty()) return false;
var truncatedStates = new List<StateMongoElement>();
foreach (var state in foundStates.States)
{
var values = state.Values.Where(x => x.UpdateTime < refTime).ToList();
if (values.Count == 0) continue;
state.Values = values;
truncatedStates.Add(state);
}
// Save
foundDialog.Dialogs = truncatedDialogs;
foundStates.States = truncatedStates;
_dc.ConversationDialogs.ReplaceOne(dialogFilter, foundDialog);
_dc.ConversationStates.ReplaceOne(stateFilter, foundStates);
2024-02-28 23:37:36 +00:00
// Remove logs
if (cleanLog)
{
var contentLogBuilder = Builders<ConversationContentLogDocument>.Filter;
var stateLogBuilder = Builders<ConversationStateLogDocument>.Filter;
var contentLogFilters = new List<FilterDefinition<ConversationContentLogDocument>>()
{
contentLogBuilder.Eq(x => x.ConversationId, conversationId),
contentLogBuilder.Gte(x => x.CreateTime, refTime)
};
var stateLogFilters = new List<FilterDefinition<ConversationStateLogDocument>>()
{
stateLogBuilder.Eq(x => x.ConversationId, conversationId),
stateLogBuilder.Gte(x => x.CreateTime, refTime)
};
_dc.ContentLogs.DeleteMany(contentLogBuilder.And(contentLogFilters));
_dc.StateLogs.DeleteMany(stateLogBuilder.And(stateLogFilters));
}
2024-01-29 01:33:40 +00:00
return true;
}
2024-01-13 02:13:38 +00:00
}