Merge pull request #529 from iceljc/features/refine-file-loading

refine message file loading
This commit is contained in:
C. Oceania 2024-07-09 09:52:49 -05:00 committed by GitHub
commit 18a08ce58b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 123 additions and 75 deletions

View file

@ -6,6 +6,7 @@ public interface IBotSharpFileService
Task<IEnumerable<MessageFileModel>> GetChatImages(string conversationId, string source, IEnumerable<string> fileTypes, List<RoleDialogModel> conversations, int? offset = null);
IEnumerable<MessageFileModel> GetMessageFiles(string conversationId, IEnumerable<string> messageIds, string source, bool imageOnly = false);
string GetMessageFile(string conversationId, string messageId, string source, string index, string fileName);
IEnumerable<MessageFileModel> GetMessagesWithFile(string conversationId, IEnumerable<string> messageIds);
bool SaveMessageFiles(string conversationId, string messageId, string source, List<BotSharpFile> files);
string GetUserAvatar();

View file

@ -28,4 +28,9 @@ public class KeyValue
{
public string Key { get; set; }
public string? Value { get; set; }
public override string ToString()
{
return $"Key: {Key}, Value: {Value}";
}
}

View file

@ -1,3 +1,4 @@
using AspectInjector.Broker;
using BotSharp.Abstraction.Files.Converters;
using Microsoft.EntityFrameworkCore;
using System.IO;
@ -126,7 +127,7 @@ public partial class BotSharpFileService
string source, bool imageOnly = false)
{
var files = new List<MessageFileModel>();
if (messageIds.IsNullOrEmpty()) return files;
if (string.IsNullOrWhiteSpace(conversationId) || messageIds.IsNullOrEmpty()) return files;
foreach (var messageId in messageIds)
{
@ -159,7 +160,8 @@ public partial class BotSharpFileService
FileStorageUrl = file,
FileName = fileName,
FileType = fileType,
ContentType = contentType
ContentType = contentType,
FileSource = source
};
files.Add(model);
}
@ -181,6 +183,30 @@ public partial class BotSharpFileService
return found;
}
public IEnumerable<MessageFileModel> GetMessagesWithFile(string conversationId, IEnumerable<string> messageIds)
{
var foundMsgs = new List<MessageFileModel>();
if (string.IsNullOrWhiteSpace(conversationId) || messageIds.IsNullOrEmpty()) return foundMsgs;
foreach (var messageId in messageIds)
{
var prefix = Path.Combine(_baseDir, CONVERSATION_FOLDER, conversationId, FILE_FOLDER, messageId);
var userDir = Path.Combine(prefix, FileSourceType.User);
if (ExistDirectory(userDir))
{
foundMsgs.Add(new MessageFileModel { MessageId = messageId, FileSource = FileSourceType.User });
}
var botDir = Path.Combine(prefix, FileSourceType.Bot);
if (ExistDirectory(botDir))
{
foundMsgs.Add(new MessageFileModel { MessageId = messageId, FileSource = FileSourceType.Bot });
}
}
return foundMsgs;
}
public bool SaveMessageFiles(string conversationId, string messageId, string source, List<BotSharpFile> files)
{
if (files.IsNullOrEmpty()) return false;

View file

@ -31,7 +31,7 @@ public class AgentController : ControllerBase
var agents = await GetAgents(new AgentFilter
{
AgentIds = new List<string> { id }
});
}, useHook: true);
var targetAgent = agents.Items.FirstOrDefault();
if (targetAgent == null) return null;
@ -63,26 +63,35 @@ public class AgentController : ControllerBase
targetAgent.Editable = editable;
return targetAgent;
}
[HttpGet("/agents")]
public async Task<PagedItems<AgentViewModel>> GetAgents([FromQuery] AgentFilter filter)
public async Task<PagedItems<AgentViewModel>> GetAgents([FromQuery] AgentFilter filter, [FromQuery] bool useHook = false)
{
var agentSetting = _services.GetRequiredService<AgentSettings>();
var pagedAgents = await _agentService.GetAgents(filter);
// prerender agent
var items = new List<Agent>();
foreach (var agent in pagedAgents.Items)
var agents = new List<AgentViewModel>();
if (useHook)
{
var renderedAgent = await _agentService.LoadAgent(agent.Id);
items.Add(renderedAgent);
}
// prerender agent
foreach (var agent in pagedAgents.Items)
{
var renderedAgent = await _agentService.LoadAgent(agent.Id);
items.Add(renderedAgent);
}
// Set IsHost
var agents = items.Select(x => AgentViewModel.FromAgent(x)).ToList();
foreach(var agent in agents)
// Set IsHost
agents = items.Select(x => AgentViewModel.FromAgent(x)).ToList();
foreach (var agent in agents)
{
agent.IsHost = agentSetting.HostAgentId == agent.Id;
}
}
else
{
agent.IsHost = agentSetting.HostAgentId == agent.Id;
items = pagedAgents.Items.ToList();
agents = items.Select(x => AgentViewModel.FromAgent(x)).ToList();
}
return new PagedItems<AgentViewModel>

View file

@ -80,6 +80,10 @@ public class ConversationController : ControllerBase
var userService = _services.GetRequiredService<IUserService>();
var agentService = _services.GetRequiredService<IAgentService>();
var fileService = _services.GetRequiredService<IBotSharpFileService>();
var messageIds = history.Select(x => x.MessageId).Distinct().ToList();
var fileMessages = fileService.GetMessagesWithFile(conversationId, messageIds);
var dialogs = new List<ChatResponseModel>();
foreach (var message in history)
@ -96,7 +100,8 @@ public class ConversationController : ControllerBase
Text = !string.IsNullOrEmpty(message.SecondaryContent) ? message.SecondaryContent : message.Content,
Data = message.Data,
Sender = UserViewModel.FromUser(user),
Payload = message.Payload
Payload = message.Payload,
HasMessageFiles = fileMessages.Any(x => x.MessageId.IsEqualTo(message.MessageId) && x.FileSource == FileSourceType.User)
});
}
else if (message.Role == AgentRole.Assistant)
@ -115,11 +120,11 @@ public class ConversationController : ControllerBase
FirstName = agent?.Name ?? "Unkown",
Role = message.Role,
},
RichContent = message.SecondaryRichContent ?? message.RichContent
RichContent = message.SecondaryRichContent ?? message.RichContent,
HasMessageFiles = fileMessages.Any(x => x.MessageId.IsEqualTo(message.MessageId) && x.FileSource == FileSourceType.Bot)
});
}
}
return dialogs;
}

View file

@ -32,6 +32,9 @@ public class ChatResponseModel : InstructResult
[JsonPropertyName("payload")]
public string? Payload { get; set; }
[JsonPropertyName("has_message_files")]
public bool HasMessageFiles { get; set; }
[JsonPropertyName("created_at")]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}

View file

@ -16,6 +16,9 @@ public class MessageFileViewModel
[JsonPropertyName("content_type")]
public string ContentType { get; set; }
[JsonPropertyName("file_source")]
public string FileSource { get; set; }
public MessageFileViewModel()
{
@ -28,7 +31,8 @@ public class MessageFileViewModel
FileUrl = model.FileUrl,
FileName = model.FileName,
FileType = model.FileType,
ContentType = model.ContentType
ContentType = model.ContentType,
FileSource = model.FileSource
};
}
}

View file

@ -1,5 +1,3 @@
using BotSharp.Plugin.MongoStorage.Models;
namespace BotSharp.Plugin.MongoStorage.Collections;
public class AgentDocument : MongoBase

View file

@ -1,5 +1,3 @@
using BotSharp.Plugin.MongoStorage.Models;
namespace BotSharp.Plugin.MongoStorage.Collections;
public class ConversationDialogDocument : MongoBase

View file

@ -1,5 +1,3 @@
using BotSharp.Plugin.MongoStorage.Models;
namespace BotSharp.Plugin.MongoStorage.Collections;
public class ConversationStateDocument : MongoBase

View file

@ -1,5 +1,3 @@
using BotSharp.Plugin.MongoStorage.Models;
namespace BotSharp.Plugin.MongoStorage.Collections;
public class LlmCompletionLogDocument : MongoBase

View file

@ -40,6 +40,19 @@ public class MongoDbContext
return collection;
}
private IMongoCollection<ConversationStateDocument> CreateConversationStateIndex()
{
var collection = Database.GetCollection<ConversationStateDocument>($"{_collectionPrefix}_ConversationStates");
var indexes = collection.Indexes.List().ToList();
var stateIndex = indexes.FirstOrDefault(x => x.GetElement("name").ToString().StartsWith("States.Key"));
if (stateIndex == null)
{
var indexDef = Builders<ConversationStateDocument>.IndexKeys.Ascending("States.Key");
collection.Indexes.CreateOne(new CreateIndexModel<ConversationStateDocument>(indexDef));
}
return collection;
}
private IMongoCollection<AgentTaskDocument> CreateAgentTaskIndex()
{
var collection = Database.GetCollection<AgentTaskDocument>($"{_collectionPrefix}_AgentTasks");
@ -93,7 +106,7 @@ public class MongoDbContext
=> Database.GetCollection<ConversationDialogDocument>($"{_collectionPrefix}_ConversationDialogs");
public IMongoCollection<ConversationStateDocument> ConversationStates
=> Database.GetCollection<ConversationStateDocument>($"{_collectionPrefix}_ConversationStates");
=> CreateConversationStateIndex();
public IMongoCollection<ExecutionLogDocument> ExectionLogs
=> Database.GetCollection<ExecutionLogDocument>($"{_collectionPrefix}_ExecutionLogs");

View file

@ -1,5 +1,9 @@
using Amazon.Util.Internal;
using BotSharp.Abstraction.Conversations.Models;
using BotSharp.Abstraction.Repositories.Filters;
using MongoDB.Bson.Serialization;
using MongoDB.Driver;
using System.Collections.Immutable;
namespace BotSharp.Plugin.MongoStorage.Repository;
@ -225,91 +229,77 @@ public partial class MongoRepository
public PagedItems<Conversation> GetConversations(ConversationFilter filter)
{
var conversations = new List<Conversation>();
var builder = Builders<ConversationDocument>.Filter;
var filters = new List<FilterDefinition<ConversationDocument>>() { builder.Empty };
var convBuilder = Builders<ConversationDocument>.Filter;
var convFilters = new List<FilterDefinition<ConversationDocument>>() { convBuilder.Empty };
// Filter conversations
if (!string.IsNullOrEmpty(filter?.Id))
{
filters.Add(builder.Eq(x => x.Id, filter.Id));
convFilters.Add(convBuilder.Eq(x => x.Id, filter.Id));
}
if (!string.IsNullOrEmpty(filter?.AgentId))
{
filters.Add(builder.Eq(x => x.AgentId, filter.AgentId));
convFilters.Add(convBuilder.Eq(x => x.AgentId, filter.AgentId));
}
if (!string.IsNullOrEmpty(filter?.Status))
{
filters.Add(builder.Eq(x => x.Status, filter.Status));
convFilters.Add(convBuilder.Eq(x => x.Status, filter.Status));
}
if (!string.IsNullOrEmpty(filter?.Channel))
{
filters.Add(builder.Eq(x => x.Channel, filter.Channel));
convFilters.Add(convBuilder.Eq(x => x.Channel, filter.Channel));
}
if (!string.IsNullOrEmpty(filter?.UserId))
{
filters.Add(builder.Eq(x => x.UserId, filter.UserId));
convFilters.Add(convBuilder.Eq(x => x.UserId, filter.UserId));
}
if (!string.IsNullOrEmpty(filter?.TaskId))
{
filters.Add(builder.Eq(x => x.TaskId, filter.TaskId));
convFilters.Add(convBuilder.Eq(x => x.TaskId, filter.TaskId));
}
if (filter?.StartTime != null)
{
filters.Add(builder.Gte(x => x.CreatedTime, filter.StartTime.Value));
convFilters.Add(convBuilder.Gte(x => x.CreatedTime, filter.StartTime.Value));
}
// Check states
if (filter != null && !filter.States.IsNullOrEmpty())
// Filter states
var stateFilters = new List<FilterDefinition<ConversationStateDocument>>();
if (filter != null && string.IsNullOrEmpty(filter.Id) && !filter.States.IsNullOrEmpty())
{
var targetConvIds = new List<string>();
foreach (var pair in filter.States)
{
if (pair == null || string.IsNullOrWhiteSpace(pair.Key)) continue;
var query = _dc.ConversationStates.AsQueryable();
var convIds = query.AsEnumerable().Where(x =>
var elementFilters = new List<FilterDefinition<StateMongoElement>> { Builders<StateMongoElement>.Filter.Eq(x => x.Key, pair.Key) };
if (!string.IsNullOrEmpty(pair.Value))
{
var foundState = x.States.FirstOrDefault(s => s.Key.IsEqualTo(pair.Key));
if (foundState == null) return false;
if (!string.IsNullOrWhiteSpace(pair.Value))
{
return pair.Value.IsEqualTo(foundState.Values.LastOrDefault()?.Data);
}
return true;
}).Select(x => x.ConversationId).ToList();
targetConvIds = targetConvIds.Concat(convIds).Distinct().ToList();
elementFilters.Add(Builders<StateMongoElement>.Filter.Eq("Values.Data", pair.Value));
}
stateFilters.Add(Builders<ConversationStateDocument>.Filter.ElemMatch(x => x.States, Builders<StateMongoElement>.Filter.And(elementFilters)));
}
filters.Add(builder.In(x => x.Id, targetConvIds));
var targetConvIds = _dc.ConversationStates.Find(Builders<ConversationStateDocument>.Filter.And(stateFilters)).ToEnumerable().Select(x => x.ConversationId).Distinct().ToList();
convFilters.Add(convBuilder.In(x => x.Id, targetConvIds));
}
var filterDef = builder.And(filters);
// Sort and paginate
var filterDef = convBuilder.And(convFilters);
var sortDef = Builders<ConversationDocument>.Sort.Descending(x => x.CreatedTime);
var pager = filter?.Pager ?? new Pagination();
var conversationDocs = _dc.Conversations.Find(filterDef).Sort(sortDef).Skip(pager.Offset).Limit(pager.Size).ToList();
var count = _dc.Conversations.CountDocuments(filterDef);
foreach (var conv in conversationDocs)
var conversations = conversationDocs.Select(x => new Conversation
{
var convId = conv.Id.ToString();
conversations.Add(new Conversation
{
Id = convId,
AgentId = conv.AgentId.ToString(),
UserId = conv.UserId.ToString(),
TaskId = conv.TaskId,
Title = conv.Title,
Channel = conv.Channel,
Status = conv.Status,
DialogCount = conv.DialogCount,
CreatedTime = conv.CreatedTime,
UpdatedTime = conv.UpdatedTime
});
}
Id = x.Id.ToString(),
AgentId = x.AgentId.ToString(),
UserId = x.UserId.ToString(),
TaskId = x.TaskId,
Title = x.Title,
Channel = x.Channel,
Status = x.Status,
DialogCount = x.DialogCount,
CreatedTime = x.CreatedTime,
UpdatedTime = x.UpdatedTime
}).ToList();
return new PagedItems<Conversation>
{