From bcff1dff3aca45c4ff7155225e99ad5b24dd1394 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Tue, 16 Sep 2025 15:48:54 -0500 Subject: [PATCH] refine file storage --- .../Conversations/Models/TokenStatsModel.cs | 8 +- .../{FileSourceType.cs => FileSource.cs} | 2 +- .../Files/IFileStorageService.cs | 6 +- .../Files/Models/MessageFileModel.cs | 2 +- .../Files/Models/MessageFileOptions.cs | 14 +++ .../FileInstructService.SelectFile.cs | 40 ++++--- .../LocalFileStorageService.Conversation.cs | 104 ++++++++---------- .../Controllers/ConversationController.cs | 12 +- .../Functions/HandleAudioRequestFn.cs | 8 +- .../Functions/EditImageFn.cs | 2 +- .../Functions/GenerateImageFn.cs | 2 +- .../Functions/ReadImageFn.cs | 20 ++-- .../ImageCompletionProvider.Generation.cs | 19 ++++ .../TencentCosService.Conversation.cs | 102 +++++++++-------- .../Core/NullFileStorageService.cs | 12 +- 15 files changed, 189 insertions(+), 164 deletions(-) rename src/Infrastructure/BotSharp.Abstraction/Files/Enums/{FileSourceType.cs => FileSource.cs} (78%) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Files/Models/MessageFileOptions.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/TokenStatsModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/TokenStatsModel.cs index 4e032b66..cd299f69 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/TokenStatsModel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/TokenStatsModel.cs @@ -5,13 +5,19 @@ public class TokenStatsModel public string Provider { get; set; } public string Model { get; set; } public string Prompt { get; set; } + + #region Input public int TextInputTokens { get; set; } public int CachedTextInputTokens { get; set; } public int AudioInputTokens { get; set; } public int CachedAudioInputTokens { get; set; } + #endregion + + #region Output public int TextOutputTokens { get; set; } public int AudioOutputTokens { get; set; } - public AgentLlmConfig LlmConfig { get; set; } + #endregion + public int TotalInputTokens => TextInputTokens + CachedTextInputTokens + AudioInputTokens + CachedAudioInputTokens; public int TotalOutputTokens => TextOutputTokens + AudioOutputTokens; diff --git a/src/Infrastructure/BotSharp.Abstraction/Files/Enums/FileSourceType.cs b/src/Infrastructure/BotSharp.Abstraction/Files/Enums/FileSource.cs similarity index 78% rename from src/Infrastructure/BotSharp.Abstraction/Files/Enums/FileSourceType.cs rename to src/Infrastructure/BotSharp.Abstraction/Files/Enums/FileSource.cs index 04c92df6..cde8c79f 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Files/Enums/FileSourceType.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Files/Enums/FileSource.cs @@ -1,6 +1,6 @@ namespace BotSharp.Abstraction.Files.Enums; -public static class FileSourceType +public static class FileSource { public const string User = "user"; public const string Bot = "bot"; diff --git a/src/Infrastructure/BotSharp.Abstraction/Files/IFileStorageService.cs b/src/Infrastructure/BotSharp.Abstraction/Files/IFileStorageService.cs index ef40e11c..db6062af 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Files/IFileStorageService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Files/IFileStorageService.cs @@ -31,12 +31,10 @@ public interface IFileStorageService /// /// /// - /// - /// + /// /// - IEnumerable GetMessageFiles(string conversationId, IEnumerable messageIds, string source, IEnumerable? contentTypes = null); + IEnumerable GetMessageFiles(string conversationId, IEnumerable messageIds, MessageFileOptions? options = null); string GetMessageFile(string conversationId, string messageId, string source, string index, string fileName); - IEnumerable GetMessagesWithFile(string conversationId, IEnumerable messageIds); bool SaveMessageFiles(string conversationId, string messageId, string source, List files); /// diff --git a/src/Infrastructure/BotSharp.Abstraction/Files/Models/MessageFileModel.cs b/src/Infrastructure/BotSharp.Abstraction/Files/Models/MessageFileModel.cs index 1f56d8de..32f43191 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Files/Models/MessageFileModel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Files/Models/MessageFileModel.cs @@ -6,7 +6,7 @@ public class MessageFileModel : FileInformation public string MessageId { get; set; } [JsonPropertyName("file_source")] - public string FileSource { get; set; } = FileSourceType.User; + public string FileSource { get; set; } = Enums.FileSource.User; [JsonPropertyName("file_index")] public string FileIndex { get; set; } = string.Empty; diff --git a/src/Infrastructure/BotSharp.Abstraction/Files/Models/MessageFileOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Files/Models/MessageFileOptions.cs new file mode 100644 index 00000000..98c2c4df --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Files/Models/MessageFileOptions.cs @@ -0,0 +1,14 @@ +namespace BotSharp.Abstraction.Files.Models; + +public class MessageFileOptions +{ + /// + /// File sources: user, bot + /// + public IEnumerable? Sources { get; set; } + + /// + /// File content types + /// + public IEnumerable? ContentTypes { get; set; } +} diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.SelectFile.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.SelectFile.cs index 4c94b619..18b8d0b3 100644 --- a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.SelectFile.cs +++ b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.SelectFile.cs @@ -27,13 +27,13 @@ public partial class FileInstructService dialogs = dialogs.TakeLast(options.MessageLimit.Value).ToList(); } - var messageIds = dialogs.Select(x => x.MessageId).Distinct().ToList(); - var files = _fileStorage.GetMessageFiles(conversationId, messageIds, FileSourceType.User, options.ContentTypes); - if (options.IsIncludeBotFiles) + var messageIds = dialogs.Select(x => x.MessageId).Distinct().ToList(); + var files = _fileStorage.GetMessageFiles(conversationId, messageIds, options: new() { - var botFiles = _fileStorage.GetMessageFiles(conversationId, messageIds, FileSourceType.Bot, options.ContentTypes); - files = MergeMessageFiles(messageIds, files, botFiles); - } + Sources = options.IsIncludeBotFiles ?[FileSource.User, FileSource.Bot] : [FileSource.User], + ContentTypes = options.ContentTypes + }); + files = MergeMessageFiles(messageIds, files); if (files.IsNullOrEmpty()) { @@ -43,19 +43,31 @@ public partial class FileInstructService return await SelectFiles(files, dialogs, options); } - private IEnumerable MergeMessageFiles(IEnumerable messageIds, IEnumerable userFiles, IEnumerable botFiles) + private IEnumerable MergeMessageFiles(IEnumerable messageIds, IEnumerable files) { - var files = new List(); + var mergedFiles = new List(); - if (messageIds.IsNullOrEmpty()) return files; + if (messageIds.IsNullOrEmpty()) + { + return mergedFiles; + } + + var userFiles = files.Where(x => x.FileSource.IsEqualTo(FileSource.User)); + var botFiles = files.Where(x => x.FileSource.IsEqualTo(FileSource.Bot)); foreach (var messageId in messageIds) { var users = userFiles.Where(x => x.MessageId == messageId).OrderBy(x => x.FileIndex, new MessageFileIndexComparer()).ToList(); var bots = botFiles.Where(x => x.MessageId == messageId).OrderBy(x => x.FileIndex, new MessageFileIndexComparer()).ToList(); - if (!users.IsNullOrEmpty()) files.AddRange(users); - if (!bots.IsNullOrEmpty()) files.AddRange(bots); + if (!users.IsNullOrEmpty()) + { + mergedFiles.AddRange(users); + } + if (!bots.IsNullOrEmpty()) + { + mergedFiles.AddRange(bots); + } } return files; @@ -92,7 +104,7 @@ public partial class FileInstructService { var text = $"[Role] '{x.Role}': {x.RichContent?.Message?.Text ?? x.Payload ?? x.Content}"; var fileDescs = x.Files?.Select((f, fidx) => $"- message_id: '{x.MessageId}', file_index: '{f.FileIndex}', " + - $"content_type: '{f.ContentType}', author: '{(x.Role == AgentRole.User ? FileSourceType.User : FileSourceType.Bot)}'"); + $"content_type: '{f.ContentType}', author: '{(x.Role == AgentRole.User ? FileSource.User : FileSource.Bot)}'"); var desc = string.Empty; if (!fileDescs.IsNullOrEmpty()) @@ -187,7 +199,7 @@ public partial class FileInstructService var userMsg = group.FirstOrDefault(x => x.Role == AgentRole.User); if (userMsg != null) { - var userFiles = found.Where(x => x.FileSource == FileSourceType.User); + var userFiles = found.Where(x => x.FileSource == FileSource.User); userMsg.Files = userFiles.Select(x => new BotSharpFile { ContentType = x.ContentType, @@ -202,7 +214,7 @@ public partial class FileInstructService var botMsg = group.LastOrDefault(x => x.Role == AgentRole.Assistant); if (botMsg != null) { - var botFiles = found.Where(x => x.FileSource == FileSourceType.Bot); + var botFiles = found.Where(x => x.FileSource == FileSource.Bot); botMsg.Files = botFiles.Select(x => new BotSharpFile { ContentType = x.ContentType, diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Conversation.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Conversation.cs index 1216dc3f..f695a5b9 100644 --- a/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Conversation.cs +++ b/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Conversation.cs @@ -14,7 +14,7 @@ public partial class LocalFileStorageService return files; } - var source = FileSourceType.User; + var source = FileSource.User; var pathPrefix = Path.Combine(_baseDir, CONVERSATION_FOLDER, conversationId, FILE_FOLDER); foreach (var messageId in messageIds) @@ -24,7 +24,7 @@ public partial class LocalFileStorageService continue; } - var dir = Path.Combine(pathPrefix, messageId, FileSourceType.User); + var dir = Path.Combine(pathPrefix, messageId, FileSource.User); if (!ExistDirectory(dir)) { continue; @@ -51,8 +51,7 @@ public partial class LocalFileStorageService } - public IEnumerable GetMessageFiles(string conversationId, IEnumerable messageIds, - string source, IEnumerable? contentTypes = null) + public IEnumerable GetMessageFiles(string conversationId, IEnumerable messageIds, MessageFileOptions? options = null) { var files = new List(); if (string.IsNullOrWhiteSpace(conversationId) || messageIds.IsNullOrEmpty()) @@ -67,39 +66,56 @@ public partial class LocalFileStorageService continue; } - var dir = Path.Combine(_baseDir, CONVERSATION_FOLDER, conversationId, FILE_FOLDER, messageId, source); - if (!ExistDirectory(dir)) + var baseDir = Path.Combine(_baseDir, CONVERSATION_FOLDER, conversationId, FILE_FOLDER, messageId); + if (!ExistDirectory(baseDir)) { continue; } - foreach (var subDir in Directory.GetDirectories(dir)) + var sources = options?.Sources != null + ? options.Sources + : Directory.GetDirectories(baseDir).Select(x => x.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries).Last()); + if (sources.IsNullOrEmpty()) { - var index = subDir.Split(Path.DirectorySeparatorChar).Last(); + continue; + } - foreach (var file in Directory.GetFiles(subDir)) + foreach (var source in sources) + { + var dir = Path.Combine(baseDir, source); + if (!ExistDirectory(dir)) { - var contentType = FileUtility.GetFileContentType(file); - if (!contentTypes.IsNullOrEmpty() && !contentTypes.Contains(contentType)) - { - continue; - } + continue; + } - var fileName = Path.GetFileNameWithoutExtension(file); - var fileExtension = Path.GetExtension(file).Substring(1); - var model = new MessageFileModel() + foreach (var subDir in Directory.GetDirectories(dir)) + { + var fileIndex = subDir.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries).Last(); + + foreach (var file in Directory.GetFiles(subDir)) { - MessageId = messageId, - FileUrl = $"/conversation/{conversationId}/message/{messageId}/{source}/file/{index}/{fileName}", - FileDownloadUrl = $"/conversation/{conversationId}/message/{messageId}/{source}/file/{index}/{fileName}/download", - FileStorageUrl = file, - FileName = fileName, - FileExtension = fileExtension, - ContentType = contentType, - FileSource = source, - FileIndex = index - }; - files.Add(model); + var contentType = FileUtility.GetFileContentType(file); + if (options?.ContentTypes != null && !options.ContentTypes.Contains(contentType)) + { + continue; + } + + var fileName = Path.GetFileNameWithoutExtension(file); + var fileExtension = Path.GetExtension(file).Substring(1); + var model = new MessageFileModel + { + MessageId = messageId, + FileUrl = $"/conversation/{conversationId}/message/{messageId}/{source}/file/{fileIndex}/{fileName}", + FileDownloadUrl = $"/conversation/{conversationId}/message/{messageId}/{source}/file/{fileIndex}/{fileName}/download", + FileStorageUrl = file, + FileName = fileName, + FileExtension = fileExtension, + ContentType = contentType, + FileSource = source, + FileIndex = fileIndex + }; + files.Add(model); + } } } } @@ -126,38 +142,6 @@ public partial class LocalFileStorageService return found; } - public IEnumerable GetMessagesWithFile(string conversationId, IEnumerable messageIds) - { - var foundMsgs = new List(); - if (string.IsNullOrWhiteSpace(conversationId) || messageIds.IsNullOrEmpty()) - { - return foundMsgs; - } - - foreach (var messageId in messageIds) - { - if (string.IsNullOrWhiteSpace(messageId)) - { - continue; - } - - 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 files) { if (string.IsNullOrWhiteSpace(conversationId) diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs index 5da7a784..76425dfe 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs @@ -96,7 +96,7 @@ public class ConversationController : ControllerBase var fileStorage = _services.GetRequiredService(); var messageIds = history.Select(x => x.MessageId).Distinct().ToList(); - var fileMessages = fileStorage.GetMessagesWithFile(conversationId, messageIds); + var files = fileStorage.GetMessageFiles(conversationId, messageIds, options: new() { Sources = [FileSource.User, FileSource.Bot] }); var dialogs = new List(); foreach (var message in history) @@ -115,7 +115,7 @@ public class ConversationController : ControllerBase Data = message.Data, Sender = UserDto.FromUser(user), Payload = message.Payload, - HasMessageFiles = fileMessages.Any(x => x.MessageId.IsEqualTo(message.MessageId) && x.FileSource == FileSourceType.User) + HasMessageFiles = files.Any(x => x.MessageId.IsEqualTo(message.MessageId) && x.FileSource == FileSource.User) }); } else if (message.Role == AgentRole.Assistant) @@ -136,7 +136,7 @@ public class ConversationController : ControllerBase Role = message.Role, }, RichContent = message.SecondaryRichContent ?? message.RichContent, - HasMessageFiles = fileMessages.Any(x => x.MessageId.IsEqualTo(message.MessageId) && x.FileSource == FileSourceType.Bot) + HasMessageFiles = files.Any(x => x.MessageId.IsEqualTo(message.MessageId) && x.FileSource == FileSource.Bot) }); } } @@ -490,7 +490,7 @@ public class ConversationController : ControllerBase var conv = await convService.GetConversationRecordOrCreateNew(agentId); var fileStorage = _services.GetRequiredService(); var messageId = Guid.NewGuid().ToString(); - var isSaved = fileStorage.SaveMessageFiles(conv.Id, messageId, FileSourceType.User, input.Files); + var isSaved = fileStorage.SaveMessageFiles(conv.Id, messageId, FileSource.User, input.Files); return isSaved ? messageId : string.Empty; } @@ -498,8 +498,8 @@ public class ConversationController : ControllerBase public IEnumerable GetConversationMessageFiles([FromRoute] string conversationId, [FromRoute] string messageId, [FromRoute] string source) { var fileStorage = _services.GetRequiredService(); - var files = fileStorage.GetMessageFiles(conversationId, new List { messageId }, source); - return files?.Select(x => MessageFileViewModel.Transform(x))?.ToList() ?? new List(); + var files = fileStorage.GetMessageFiles(conversationId, [messageId], options: new() { Sources = [source] }); + return files?.Select(x => MessageFileViewModel.Transform(x))?.ToList() ?? []; } [HttpGet("/conversation/{conversationId}/message/{messageId}/{source}/file/{index}/{fileName}")] diff --git a/src/Plugins/BotSharp.Plugin.AudioHandler/Functions/HandleAudioRequestFn.cs b/src/Plugins/BotSharp.Plugin.AudioHandler/Functions/HandleAudioRequestFn.cs index a114c2fa..e27b4054 100644 --- a/src/Plugins/BotSharp.Plugin.AudioHandler/Functions/HandleAudioRequestFn.cs +++ b/src/Plugins/BotSharp.Plugin.AudioHandler/Functions/HandleAudioRequestFn.cs @@ -13,7 +13,7 @@ public class HandleAudioRequestFn : IFunctionCallback private readonly ILogger _logger; private readonly BotSharpOptions _options; - private readonly IEnumerable _audioContentType = new List + private readonly IEnumerable _audioContentTypes = new List { AudioType.mp3.ToFileType(), AudioType.wav.ToFileType(), @@ -52,7 +52,11 @@ public class HandleAudioRequestFn : IFunctionCallback } var messageId = dialogs.Select(x => x.MessageId).Distinct().ToList(); - var audioMessageFiles = _fileStorage.GetMessageFiles(convId, messageId, FileSourceType.User, _audioContentType); + var audioMessageFiles = _fileStorage.GetMessageFiles(convId, messageId, options: new() + { + Sources = [FileSource.User], + ContentTypes = _audioContentTypes + }); audioMessageFiles = audioMessageFiles.Where(x => x.ContentType.Contains("audio")).ToList(); diff --git a/src/Plugins/BotSharp.Plugin.FileHandler/Functions/EditImageFn.cs b/src/Plugins/BotSharp.Plugin.FileHandler/Functions/EditImageFn.cs index ec1530e8..4388485b 100644 --- a/src/Plugins/BotSharp.Plugin.FileHandler/Functions/EditImageFn.cs +++ b/src/Plugins/BotSharp.Plugin.FileHandler/Functions/EditImageFn.cs @@ -191,7 +191,7 @@ public class EditImageFn : IFunctionCallback }; var fileStorage = _services.GetRequiredService(); - fileStorage.SaveMessageFiles(_conversationId, _messageId, FileSourceType.Bot, files); + fileStorage.SaveMessageFiles(_conversationId, _messageId, FileSource.Bot, files); return files.Select(x => x.FileName); } diff --git a/src/Plugins/BotSharp.Plugin.FileHandler/Functions/GenerateImageFn.cs b/src/Plugins/BotSharp.Plugin.FileHandler/Functions/GenerateImageFn.cs index 4a98d056..2e13a0c0 100644 --- a/src/Plugins/BotSharp.Plugin.FileHandler/Functions/GenerateImageFn.cs +++ b/src/Plugins/BotSharp.Plugin.FileHandler/Functions/GenerateImageFn.cs @@ -151,7 +151,7 @@ public class GenerateImageFn : IFunctionCallback }).ToList(); var fileStorage = _services.GetRequiredService(); - fileStorage.SaveMessageFiles(_conversationId, _messageId, FileSourceType.Bot, files); + fileStorage.SaveMessageFiles(_conversationId, _messageId, FileSource.Bot, files); return files.Select(x => x.FileName); } } diff --git a/src/Plugins/BotSharp.Plugin.FileHandler/Functions/ReadImageFn.cs b/src/Plugins/BotSharp.Plugin.FileHandler/Functions/ReadImageFn.cs index 98d1f52f..29d6ca14 100644 --- a/src/Plugins/BotSharp.Plugin.FileHandler/Functions/ReadImageFn.cs +++ b/src/Plugins/BotSharp.Plugin.FileHandler/Functions/ReadImageFn.cs @@ -10,6 +10,12 @@ public class ReadImageFn : IFunctionCallback private readonly IServiceProvider _services; private readonly ILogger _logger; + private readonly IEnumerable _imageContentTypes = new List + { + MediaTypeNames.Image.Png, + MediaTypeNames.Image.Jpeg + }; + public ReadImageFn( IServiceProvider services, ILogger logger) @@ -58,17 +64,13 @@ public class ReadImageFn : IFunctionCallback return new List(); } - var contentTypes = new List - { - MediaTypeNames.Image.Png, - MediaTypeNames.Image.Jpeg - }; - var fileStorage = _services.GetRequiredService(); var messageIds = dialogs.Select(x => x.MessageId).Distinct().ToList(); - var userImages = fileStorage.GetMessageFiles(conversationId, messageIds, FileSourceType.User, contentTypes); - var botImages = fileStorage.GetMessageFiles(conversationId, messageIds, FileSourceType.Bot, contentTypes); - var images = userImages.Concat(botImages); + var images = fileStorage.GetMessageFiles(conversationId, messageIds, options: new() + { + Sources = [FileSource.User, FileSource.Bot], + ContentTypes = _imageContentTypes + }); foreach (var dialog in dialogs) { diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Image/ImageCompletionProvider.Generation.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Image/ImageCompletionProvider.Generation.cs index 7ac788cf..acaea6d6 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Image/ImageCompletionProvider.Generation.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Image/ImageCompletionProvider.Generation.cs @@ -1,4 +1,5 @@ #pragma warning disable OPENAI001 +using BotSharp.Abstraction.Hooks; using OpenAI.Images; namespace BotSharp.Plugin.OpenAI.Providers.Image; @@ -7,10 +8,18 @@ public partial class ImageCompletionProvider { public async Task GetImageGeneration(Agent agent, RoleDialogModel message) { + var hooks = _services.GetHooks(agent.Id); + var client = ProviderHelper.GetClient(Provider, _model, _services); var (prompt, imageCount, options) = PrepareGenerationOptions(message); var imageClient = client.GetImageClient(_model); + // Before generation + foreach (var hook in hooks) + { + await hook.BeforeGenerating(agent, [new RoleDialogModel(AgentRole.User, prompt)]); + } + var response = imageClient.GenerateImages(prompt, imageCount, options); var images = response.Value; @@ -23,6 +32,16 @@ public partial class ImageCompletionProvider GeneratedImages = generatedImages }; + // After generation + var usage = response.Value.Usage; + foreach (var hook in hooks) + { + await hook.AfterGenerated(responseMessage, new TokenStatsModel + { + + }); + } + return await Task.FromResult(responseMessage); } diff --git a/src/Plugins/BotSharp.Plugin.TencentCos/Services/TencentCosService.Conversation.cs b/src/Plugins/BotSharp.Plugin.TencentCos/Services/TencentCosService.Conversation.cs index 799a0f54..db88dc52 100644 --- a/src/Plugins/BotSharp.Plugin.TencentCos/Services/TencentCosService.Conversation.cs +++ b/src/Plugins/BotSharp.Plugin.TencentCos/Services/TencentCosService.Conversation.cs @@ -16,7 +16,7 @@ public partial class TencentCosService return files; } - var source = FileSourceType.User; + var source = FileSource.User; var pathPrefix = $"{CONVERSATION_FOLDER}/{conversationId}/{FILE_FOLDER}"; foreach (var messageId in messageIds) { @@ -36,46 +36,66 @@ public partial class TencentCosService return files; } - public IEnumerable GetMessageFiles(string conversationId, IEnumerable messageIds, - string source, IEnumerable? contentTypes = null) + public IEnumerable GetMessageFiles(string conversationId, IEnumerable messageIds, MessageFileOptions? options = null) { var files = new List(); - if (string.IsNullOrWhiteSpace(conversationId) || messageIds.IsNullOrEmpty()) return files; + if (string.IsNullOrWhiteSpace(conversationId) || messageIds.IsNullOrEmpty()) + { + return files; + } foreach (var messageId in messageIds) { - var dir = $"{CONVERSATION_FOLDER}/{conversationId}/{FILE_FOLDER}/{messageId}/{source}"; - if (!ExistDirectory(dir)) + var baseDir = $"{CONVERSATION_FOLDER}/{conversationId}/{FILE_FOLDER}/{messageId}"; + if (!ExistDirectory(baseDir)) { continue; } - foreach (var subDir in _cosClient.BucketClient.GetDirectories(dir)) + var sources = options?.Sources != null + ? options.Sources + : _cosClient.BucketClient.GetDirectories(baseDir).Select(x => x.Split("/", StringSplitOptions.RemoveEmptyEntries).Last()); + if (sources.IsNullOrEmpty()) { - foreach (var file in _cosClient.BucketClient.GetDirFiles(subDir)) - { - var contentType = FileUtility.GetFileContentType(file); - if (!contentTypes.IsNullOrEmpty() && !contentTypes.Contains(contentType)) - { - continue; - } + continue; + } - var fileName = Path.GetFileNameWithoutExtension(file); - var fileExtension = Path.GetExtension(file).Substring(1); + foreach (var source in sources) + { + var dir = Path.Combine(baseDir, source); + if (!ExistDirectory(dir)) + { + continue; + } + + foreach (var subDir in _cosClient.BucketClient.GetDirectories(dir)) + { var fileIndex = subDir.Split("/", StringSplitOptions.RemoveEmptyEntries).LastOrDefault() ?? string.Empty; - var model = new MessageFileModel() + + foreach (var file in _cosClient.BucketClient.GetDirFiles(subDir)) { - MessageId = messageId, - FileUrl = BuilFileUrl(file), - FileDownloadUrl = BuilFileUrl(file), - FileStorageUrl = file, - FileName = fileName, - FileExtension = fileExtension, - ContentType = contentType, - FileSource = source, - FileIndex = fileIndex - }; - files.Add(model); + var contentType = FileUtility.GetFileContentType(file); + if (options?.ContentTypes != null && !options.ContentTypes.Contains(contentType)) + { + continue; + } + + var fileName = Path.GetFileNameWithoutExtension(file); + var fileExtension = Path.GetExtension(file).Substring(1); + var model = new MessageFileModel() + { + MessageId = messageId, + FileUrl = BuilFileUrl(file), + FileDownloadUrl = BuilFileUrl(file), + FileStorageUrl = file, + FileName = fileName, + FileExtension = fileExtension, + ContentType = contentType, + FileSource = source, + FileIndex = fileIndex + }; + files.Add(model); + } } } } @@ -87,37 +107,13 @@ public partial class TencentCosService public string GetMessageFile(string conversationId, string messageId, string source, string index, string fileName) { - var dir = $"{CONVERSATION_FOLDER}/{conversationId}/{FILE_FOLDER}/{source}/{index}/"; + var dir = $"{CONVERSATION_FOLDER}/{conversationId}/{FILE_FOLDER}/{source}/{index}"; var fileList = _cosClient.BucketClient.GetDirFiles(dir); var found = fileList.FirstOrDefault(f => Path.GetFileNameWithoutExtension(f).IsEqualTo(fileName)); return found; } - public IEnumerable GetMessagesWithFile(string conversationId, IEnumerable messageIds) - { - var foundMsgs = new List(); - if (string.IsNullOrWhiteSpace(conversationId) || messageIds.IsNullOrEmpty()) return foundMsgs; - - foreach (var messageId in messageIds) - { - var prefix = $"{CONVERSATION_FOLDER}/{conversationId}/{FILE_FOLDER}/{messageId}"; - var userDir = $"{prefix}/{FileSourceType.User}/"; - if (ExistDirectory(userDir)) - { - foundMsgs.Add(new MessageFileModel { MessageId = messageId, FileSource = FileSourceType.User }); - } - - var botDir = $"{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 files) { if (files.IsNullOrEmpty()) return false; diff --git a/tests/BotSharp.LLM.Tests/Core/NullFileStorageService.cs b/tests/BotSharp.LLM.Tests/Core/NullFileStorageService.cs index bb1d0c44..fcc3aaf7 100644 --- a/tests/BotSharp.LLM.Tests/Core/NullFileStorageService.cs +++ b/tests/BotSharp.LLM.Tests/Core/NullFileStorageService.cs @@ -63,8 +63,7 @@ namespace BotSharp.Plugin.Google.Core }); } - public IEnumerable GetMessageFiles(string conversationId, IEnumerable messageIds, string source, - IEnumerable? contentTypes = null) + public IEnumerable GetMessageFiles(string conversationId, IEnumerable messageIds, MessageFileOptions? options = null) { return new List { @@ -78,15 +77,6 @@ namespace BotSharp.Plugin.Google.Core return $"FakePath/{fileName}"; } - public IEnumerable GetMessagesWithFile(string conversationId, IEnumerable messageIds) - { - return new List - { - new MessageFileModel { FileName = "MessageFile1.jpg" }, - new MessageFileModel { FileName = "MessageFile2.png" } - }; - } - public bool SaveMessageFiles(string conversationId, string messageId, string source, List files) { return true;