refine file select
This commit is contained in:
parent
93e904d5f4
commit
e14a83a381
|
|
@ -10,6 +10,7 @@ public interface IImageConverter
|
||||||
/// <param name="pdfLocation">Pdf file location</param>
|
/// <param name="pdfLocation">Pdf file location</param>
|
||||||
/// <param name="imageFolderLocation">Image folder location</param>
|
/// <param name="imageFolderLocation">Image folder location</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
/// <exception cref="NotImplementedException"></exception>
|
||||||
Task<IEnumerable<string>> ConvertPdfToImages(string pdfLocation, string imageFolderLocation) => throw new NotImplementedException();
|
Task<IEnumerable<string>> ConvertPdfToImages(string pdfLocation, string imageFolderLocation) => throw new NotImplementedException();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
namespace BotSharp.Abstraction.Files.Models;
|
namespace BotSharp.Abstraction.Files.Models;
|
||||||
|
|
||||||
public class BotSharpFile : FileInformation
|
public class BotSharpFile : FileInformation
|
||||||
|
|
@ -9,4 +8,8 @@ public class BotSharpFile : FileInformation
|
||||||
[JsonPropertyName("file_data")]
|
[JsonPropertyName("file_data")]
|
||||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
public string? FileData { get; set; } = string.Empty;
|
public string? FileData { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("file_index")]
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public string? FileIndex { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,21 @@ namespace BotSharp.Abstraction.Files.Models;
|
||||||
|
|
||||||
public class FileSelectContext
|
public class FileSelectContext
|
||||||
{
|
{
|
||||||
[JsonPropertyName("selected_ids")]
|
[JsonPropertyName("selected_files")]
|
||||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
public List<FileSelectItem>? SelectedFiles { get; set; }
|
||||||
public IEnumerable<int>? Selecteds { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class FileSelectItem
|
||||||
|
{
|
||||||
|
[JsonPropertyName("message_id")]
|
||||||
|
public string MessageId { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("file_index")]
|
||||||
|
public string FileIndex { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("file_source")]
|
||||||
|
public string FileSource { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("file_name")]
|
||||||
|
public string? FileName { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,9 @@ public class MessageFileModel : FileInformation
|
||||||
[JsonPropertyName("file_source")]
|
[JsonPropertyName("file_source")]
|
||||||
public string FileSource { get; set; } = FileSourceType.User;
|
public string FileSource { get; set; } = FileSourceType.User;
|
||||||
|
|
||||||
|
[JsonPropertyName("file_index")]
|
||||||
|
public string FileIndex { get; set; } = string.Empty;
|
||||||
|
|
||||||
public MessageFileModel()
|
public MessageFileModel()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,16 @@ public class SelectFileOptions
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? Model { get; set; }
|
public string? Model { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Llm maximum output tokens
|
||||||
|
/// </summary>
|
||||||
|
public int? MaxOutputTokens { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Llm reasoning effort level
|
||||||
|
/// </summary>
|
||||||
|
public string? ReasoningEffortLevel { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Agent id
|
/// Agent id
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -30,7 +40,7 @@ public class SelectFileOptions
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether include bot generated files
|
/// Whether include bot generated files
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IncludeBotFile { get; set; }
|
public bool IsIncludeBotFiles { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Conversation breakpoint
|
/// Conversation breakpoint
|
||||||
|
|
@ -38,12 +48,22 @@ public class SelectFileOptions
|
||||||
public bool FromBreakpoint { get; set; }
|
public bool FromBreakpoint { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Message offset from last
|
/// The maximum number of messages
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int? Offset { get; set; }
|
public int? MessageLimit { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whehter attach files to messages
|
||||||
|
/// </summary>
|
||||||
|
public bool IsAttachFiles { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// File content types. If null, all types of files will be retrived
|
/// File content types. If null, all types of files will be retrived
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<string>? ContentTypes { get; set; }
|
public IEnumerable<string>? ContentTypes { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Data that can be used to fill in the prompt
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, object>? Data { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using BotSharp.Abstraction.MLTasks;
|
using BotSharp.Abstraction.MLTasks;
|
||||||
|
using BotSharp.Abstraction.Models;
|
||||||
using BotSharp.Abstraction.Templating;
|
using BotSharp.Abstraction.Templating;
|
||||||
|
|
||||||
namespace BotSharp.Core.Files.Services;
|
namespace BotSharp.Core.Files.Services;
|
||||||
|
|
@ -12,12 +13,23 @@ public partial class FileInstructService
|
||||||
return Enumerable.Empty<MessageFileModel>();
|
return Enumerable.Empty<MessageFileModel>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var routeContext = _services.GetRequiredService<IRoutingContext>();
|
||||||
var convService = _services.GetRequiredService<IConversationService>();
|
var convService = _services.GetRequiredService<IConversationService>();
|
||||||
var dialogs = convService.GetDialogHistory(fromBreakpoint: options.FromBreakpoint);
|
|
||||||
var messageIds = GetMessageIds(dialogs, options.Offset);
|
|
||||||
|
|
||||||
|
var dialogs = routeContext.GetDialogs();
|
||||||
|
if (dialogs.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
dialogs = convService.GetDialogHistory(fromBreakpoint: options.FromBreakpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.MessageLimit > 0)
|
||||||
|
{
|
||||||
|
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);
|
var files = _fileStorage.GetMessageFiles(conversationId, messageIds, FileSourceType.User, options.ContentTypes);
|
||||||
if (options.IncludeBotFile)
|
if (options.IsIncludeBotFiles)
|
||||||
{
|
{
|
||||||
var botFiles = _fileStorage.GetMessageFiles(conversationId, messageIds, FileSourceType.Bot, options.ContentTypes);
|
var botFiles = _fileStorage.GetMessageFiles(conversationId, messageIds, FileSourceType.Bot, options.ContentTypes);
|
||||||
files = MergeMessageFiles(messageIds, files, botFiles);
|
files = MergeMessageFiles(messageIds, files, botFiles);
|
||||||
|
|
@ -39,8 +51,8 @@ public partial class FileInstructService
|
||||||
|
|
||||||
foreach (var messageId in messageIds)
|
foreach (var messageId in messageIds)
|
||||||
{
|
{
|
||||||
var users = userFiles.Where(x => x.MessageId == messageId).ToList();
|
var users = userFiles.Where(x => x.MessageId == messageId).OrderBy(x => x.FileIndex, new MessageFileIndexComparer()).ToList();
|
||||||
var bots = botFiles.Where(x => x.MessageId == messageId).ToList();
|
var bots = botFiles.Where(x => x.MessageId == messageId).OrderBy(x => x.FileIndex, new MessageFileIndexComparer()).ToList();
|
||||||
|
|
||||||
if (!users.IsNullOrEmpty()) files.AddRange(users);
|
if (!users.IsNullOrEmpty()) files.AddRange(users);
|
||||||
if (!bots.IsNullOrEmpty()) files.AddRange(bots);
|
if (!bots.IsNullOrEmpty()) files.AddRange(bots);
|
||||||
|
|
@ -51,87 +63,175 @@ public partial class FileInstructService
|
||||||
|
|
||||||
private async Task<IEnumerable<MessageFileModel>> SelectFiles(IEnumerable<MessageFileModel> files, IEnumerable<RoleDialogModel> dialogs, SelectFileOptions options)
|
private async Task<IEnumerable<MessageFileModel>> SelectFiles(IEnumerable<MessageFileModel> files, IEnumerable<RoleDialogModel> dialogs, SelectFileOptions options)
|
||||||
{
|
{
|
||||||
if (files.IsNullOrEmpty()) return new List<MessageFileModel>();
|
var res = new List<MessageFileModel>();
|
||||||
|
if (files.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
var agentService = _services.GetRequiredService<IAgentService>();
|
||||||
var llmProviderService = _services.GetRequiredService<ILlmProviderService>();
|
var llmProviderService = _services.GetRequiredService<ILlmProviderService>();
|
||||||
var render = _services.GetRequiredService<ITemplateRender>();
|
var render = _services.GetRequiredService<ITemplateRender>();
|
||||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var promptFiles = files.Select((x, idx) =>
|
// Handle dialogs and files
|
||||||
|
var innerDialogs = (dialogs ?? []).ToList();
|
||||||
|
var text = !string.IsNullOrWhiteSpace(options.Description) ? options.Description : "Please follow the instruction and select file(s).";
|
||||||
|
innerDialogs = innerDialogs.Concat([new RoleDialogModel(AgentRole.User, text)]).ToList();
|
||||||
|
|
||||||
|
if (options.IsAttachFiles)
|
||||||
{
|
{
|
||||||
return $"id: {idx + 1}, file_name: {x.FileName}.{x.FileExtension}, content_type: {x.ContentType}, author: {x.FileSource}";
|
AssembleMessageFiles(innerDialogs, files, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Handle instruction
|
||||||
|
var promptMessages = innerDialogs.Select(x =>
|
||||||
|
{
|
||||||
|
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}', " +
|
||||||
|
$"file_name: '{f.FileFullName}', content_type: '{f.ContentType}', author: '{(x.Role == AgentRole.User ? FileSourceType.User : FileSourceType.Bot)}'");
|
||||||
|
|
||||||
|
var desc = string.Empty;
|
||||||
|
if (!fileDescs.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
desc = $"[Files]: \r\n\t{string.Join("\r\n\t", fileDescs)}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return new NameDesc(text, desc);
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
var agentId = !string.IsNullOrWhiteSpace(options.AgentId) ? options.AgentId : BuiltInAgentId.UtilityAssistant;
|
var agentId = !string.IsNullOrWhiteSpace(options.AgentId) ? options.AgentId : BuiltInAgentId.UtilityAssistant;
|
||||||
var template = !string.IsNullOrWhiteSpace(options.Template) ? options.Template : "util-file-select_file_instruction";
|
var template = !string.IsNullOrWhiteSpace(options.Template) ? options.Template : "util-file-select_file_instruction";
|
||||||
|
var prompt = db.GetAgentTemplate(agentId, template);
|
||||||
|
|
||||||
var foundAgent = db.GetAgent(agentId);
|
var data = new Dictionary<string, object>
|
||||||
var prompt = db.GetAgentTemplate(BuiltInAgentId.UtilityAssistant, template);
|
|
||||||
prompt = render.Render(prompt, new Dictionary<string, object>
|
|
||||||
{
|
{
|
||||||
{ "file_list", promptFiles }
|
{ "message_files", promptMessages }
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (!options.Data.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
foreach (var item in options.Data)
|
||||||
|
{
|
||||||
|
data[item.Key] = item.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prompt = render.Render(prompt, data);
|
||||||
|
|
||||||
|
|
||||||
|
// Build agent
|
||||||
|
var foundAgent = await agentService.GetAgent(agentId);
|
||||||
var agent = new Agent
|
var agent = new Agent
|
||||||
{
|
{
|
||||||
Id = foundAgent?.Id ?? BuiltInAgentId.UtilityAssistant,
|
Id = foundAgent?.Id ?? BuiltInAgentId.UtilityAssistant,
|
||||||
Name = foundAgent?.Name ?? "Utility Assistant",
|
Name = foundAgent?.Name ?? "Utility Assistant",
|
||||||
Instruction = prompt
|
Instruction = prompt,
|
||||||
|
LlmConfig = new AgentLlmConfig
|
||||||
|
{
|
||||||
|
MaxOutputTokens = options.MaxOutputTokens,
|
||||||
|
ReasoningEffortLevel = options.ReasoningEffortLevel
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var message = dialogs.LastOrDefault();
|
|
||||||
var text = !string.IsNullOrWhiteSpace(options.Description) ? options.Description : message?.Content;
|
|
||||||
if (message == null)
|
|
||||||
{
|
|
||||||
message = new RoleDialogModel(AgentRole.User, text);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
message = RoleDialogModel.From(message, AgentRole.User, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
var providerName = options.Provider ?? "openai";
|
// Get ai response
|
||||||
var model = options?.Model ?? "gpt-4.1-mini";
|
var provider = options.Provider ?? "openai";
|
||||||
var provider = llmProviderService.GetProviders().FirstOrDefault(x => x == providerName);
|
var model = options?.Model ?? "gpt-5-mini";
|
||||||
var completion = CompletionProvider.GetChatCompletion(_services, provider: provider, model: model);
|
var completion = CompletionProvider.GetChatCompletion(_services, provider: provider, model: model);
|
||||||
|
|
||||||
var response = await completion.GetChatCompletions(agent, new List<RoleDialogModel> { message });
|
var response = await completion.GetChatCompletions(agent, innerDialogs);
|
||||||
var content = response?.Content ?? string.Empty;
|
var content = response?.Content ?? "{}";
|
||||||
var selecteds = JsonSerializer.Deserialize<FileSelectContext>(content, new JsonSerializerOptions
|
var selecteds = JsonSerializer.Deserialize<FileSelectContext>(content, new JsonSerializerOptions
|
||||||
{
|
{
|
||||||
AllowTrailingCommas = true
|
AllowTrailingCommas = true
|
||||||
});
|
});
|
||||||
var fids = selecteds?.Selecteds ?? new List<int>();
|
var selectedFiles = selecteds?.SelectedFiles ?? new List<FileSelectItem>();
|
||||||
return files.Where((x, idx) => fids.Contains(idx + 1)).ToList();
|
|
||||||
|
if (!selectedFiles.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
res = files.Where(file => selectedFiles.Any(x => x.MessageId.IsEqualTo(file.MessageId)
|
||||||
|
&& x.FileIndex.IsEqualTo(file.FileIndex)
|
||||||
|
&& x.FileSource.IsEqualTo(file.FileSource))).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogWarning(ex, $"Error when selecting files.");
|
_logger.LogWarning(ex, $"Error when selecting files.");
|
||||||
return new List<MessageFileModel>();
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<string> GetMessageIds(IEnumerable<RoleDialogModel> conversations, int? offset = null)
|
private void AssembleMessageFiles(IEnumerable<RoleDialogModel> dialogs, IEnumerable<MessageFileModel> files, SelectFileOptions options)
|
||||||
{
|
{
|
||||||
if (conversations.IsNullOrEmpty()) return Enumerable.Empty<string>();
|
if (dialogs.IsNullOrEmpty() || files.IsNullOrEmpty())
|
||||||
|
|
||||||
if (offset.HasValue && offset < 1)
|
|
||||||
{
|
{
|
||||||
offset = 1;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var messageIds = new List<string>();
|
var groupedDialogs = dialogs.GroupBy(x => x.MessageId);
|
||||||
if (offset.HasValue)
|
foreach (var group in groupedDialogs)
|
||||||
{
|
{
|
||||||
messageIds = conversations.Select(x => x.MessageId).Distinct().TakeLast(offset.Value).ToList();
|
var targetMessageId = group.Key;
|
||||||
}
|
var found = files.Where(x => x.MessageId == targetMessageId);
|
||||||
else
|
|
||||||
{
|
|
||||||
messageIds = conversations.Select(x => x.MessageId).Distinct().ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
return messageIds;
|
if (found.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var userMsg = group.FirstOrDefault(x => x.Role == AgentRole.User);
|
||||||
|
if (userMsg != null)
|
||||||
|
{
|
||||||
|
var userFiles = found.Where(x => x.FileSource == FileSourceType.User);
|
||||||
|
userMsg.Files = userFiles.Select(x => new BotSharpFile
|
||||||
|
{
|
||||||
|
ContentType = x.ContentType,
|
||||||
|
FileUrl = x.FileUrl,
|
||||||
|
FileStorageUrl = x.FileStorageUrl,
|
||||||
|
FileName = x.FileName,
|
||||||
|
FileExtension = x.FileExtension,
|
||||||
|
FileIndex = x.FileIndex
|
||||||
|
}).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
var botMsg = group.LastOrDefault(x => x.Role == AgentRole.Assistant);
|
||||||
|
if (botMsg != null)
|
||||||
|
{
|
||||||
|
var botFiles = found.Where(x => x.FileSource == FileSourceType.Bot);
|
||||||
|
botMsg.Files = botFiles.Select(x => new BotSharpFile
|
||||||
|
{
|
||||||
|
ContentType = x.ContentType,
|
||||||
|
FileUrl = x.FileUrl,
|
||||||
|
FileStorageUrl = x.FileStorageUrl,
|
||||||
|
FileName = x.FileName,
|
||||||
|
FileExtension = x.FileExtension,
|
||||||
|
FileIndex = x.FileIndex
|
||||||
|
}).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private sealed class MessageFileIndexComparer : IComparer<string>
|
||||||
|
{
|
||||||
|
public int Compare(string? x, string? y)
|
||||||
|
{
|
||||||
|
if (x == null) return -1;
|
||||||
|
if (y == null) return 1;
|
||||||
|
|
||||||
|
var isNumx = int.TryParse(x, out var xNum);
|
||||||
|
var isNumy = int.TryParse(y, out var yNum);
|
||||||
|
|
||||||
|
if (isNumx && isNumy)
|
||||||
|
{
|
||||||
|
return xNum.CompareTo(yNum);
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Compare(x, y, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -96,7 +96,8 @@ public partial class LocalFileStorageService
|
||||||
FileName = fileName,
|
FileName = fileName,
|
||||||
FileExtension = fileExtension,
|
FileExtension = fileExtension,
|
||||||
ContentType = contentType,
|
ContentType = contentType,
|
||||||
FileSource = source
|
FileSource = source,
|
||||||
|
FileIndex = index
|
||||||
};
|
};
|
||||||
files.Add(model);
|
files.Add(model);
|
||||||
}
|
}
|
||||||
|
|
@ -195,7 +196,7 @@ public partial class LocalFileStorageService
|
||||||
fs.Write(binary.ToArray(), 0, binary.Length);
|
fs.Write(binary.ToArray(), 0, binary.Length);
|
||||||
fs.Flush(true);
|
fs.Flush(true);
|
||||||
fs.Close();
|
fs.Close();
|
||||||
Thread.Sleep(100);
|
Thread.Sleep(50);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
"llmConfig": {
|
"llmConfig": {
|
||||||
"is_inherit": false,
|
"is_inherit": false,
|
||||||
"provider": "openai",
|
"provider": "openai",
|
||||||
"model": "gpt-4o-mini",
|
"model": "gpt-5-mini",
|
||||||
"max_recursion_depth": 3
|
"max_recursion_depth": 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,44 +1,34 @@
|
||||||
Please take a look at the files in the [FILES] section from the conversation and select the files based on the conversation with user.
|
You are a File Selector that helps user select files from the chat context.
|
||||||
|
- Please take a look at the messages and files in the [MESSAGES AND FILES] and [REQUIREMENTS] section, and select the files that user asks for.
|
||||||
|
- After you figure out what files the user is asking for, please follow the [RESPONSE FORMAT] section and generate your response.
|
||||||
|
|
||||||
** Ensure the output is only in JSON format without any additional text.
|
|
||||||
** If no files are selected, you must output an empty list [].
|
|
||||||
** You may need to look at the file_name as a reference to find the correct file id or ids.
|
|
||||||
|
|
||||||
|
[REQUIREMENTS]
|
||||||
|
- You need to select the files based on chat context with user.
|
||||||
|
- You need to check starting from the latest message and its files and go backwards. Usually the recent files are more relevant.
|
||||||
|
- You can select multiple files if necessary. DO NOT select the duplicate files.
|
||||||
|
- You need to select the files based on the relevance with the user's request. The output selected files must be in the descending order of relevance.
|
||||||
|
- You must ensure the information (e.g., role, message_id, file_index, file_name) of each selected file is from the same message.
|
||||||
|
- If no files are selected, you must output an empty list [] for "selected_files".
|
||||||
|
|
||||||
|
|
||||||
|
[MESSAGES AND FILES]
|
||||||
|
{% for item in message_files -%}
|
||||||
|
================
|
||||||
|
{{ item.name }}
|
||||||
|
{{ item.description }}{{ "\r\n" }}
|
||||||
|
================
|
||||||
|
{%- endfor %}
|
||||||
|
|
||||||
|
|
||||||
|
[RESPONSE FORMAT]
|
||||||
*** Your output must be in the following JSON format:
|
*** Your output must be in the following JSON format:
|
||||||
{
|
{
|
||||||
"selected_ids": a list of id selected from the [FILES] section
|
"selected_files": a list of files selected from the [MESSAGES AND FILES] section, each item in this list should be in JSON format:
|
||||||
}
|
{
|
||||||
|
"message_id": "the message_id of the file",
|
||||||
Suppose there are four files:
|
"file_source": "the file_source of the file: 'user' | 'bot'",
|
||||||
|
"file_index": "the file_index of the selected file",
|
||||||
id: 1, file_name: example_file.jpg, content_type: image/jpeg, author: user
|
"file_name": "the file_name of the selected file"
|
||||||
id: 2, file_name: example_file.pdf, content_type: application/pdf, author: user
|
}
|
||||||
id: 3, file_name: example_file.png, content_type: image/png, author: bot
|
}
|
||||||
id: 4, file_name: example_file.png, content_type: image/png, author: bot
|
|
||||||
|
|
||||||
=====
|
|
||||||
Example 1:
|
|
||||||
USER: I want to send the first file and the third file.
|
|
||||||
OUTPUT: { "selected_ids": [1, 3] }
|
|
||||||
|
|
||||||
Example 2:
|
|
||||||
USER: Send all the images.
|
|
||||||
OUTPUT: { "selected_ids": [1, 2, 4] }
|
|
||||||
|
|
||||||
Example 3:
|
|
||||||
USER: Send all the images I uploaded.
|
|
||||||
OUTPUT: { "selected_ids": [1] }
|
|
||||||
|
|
||||||
Example 4:
|
|
||||||
USER: Send the image and the pdf file.
|
|
||||||
OUTPUT: { "selected_ids": [1, 2] }
|
|
||||||
|
|
||||||
Example 5:
|
|
||||||
USER: Send the images generated by bot
|
|
||||||
OUTPUT: { "selected_ids": [3, 4] }
|
|
||||||
=====
|
|
||||||
|
|
||||||
[FILES]
|
|
||||||
{% for file in file_list -%}
|
|
||||||
{{ file }}{{ "\r\n" }}
|
|
||||||
{%- endfor %}
|
|
||||||
|
|
@ -11,23 +11,17 @@ public class HandleEmailSenderFn : IFunctionCallback
|
||||||
|
|
||||||
private readonly IServiceProvider _services;
|
private readonly IServiceProvider _services;
|
||||||
private readonly ILogger<HandleEmailSenderFn> _logger;
|
private readonly ILogger<HandleEmailSenderFn> _logger;
|
||||||
private readonly IHttpClientFactory _httpClientFactory;
|
|
||||||
private readonly IHttpContextAccessor _context;
|
|
||||||
private readonly BotSharpOptions _options;
|
private readonly BotSharpOptions _options;
|
||||||
private readonly EmailSenderSettings _emailSettings;
|
private readonly EmailSenderSettings _emailSettings;
|
||||||
|
|
||||||
public HandleEmailSenderFn(
|
public HandleEmailSenderFn(
|
||||||
IServiceProvider services,
|
IServiceProvider services,
|
||||||
ILogger<HandleEmailSenderFn> logger,
|
ILogger<HandleEmailSenderFn> logger,
|
||||||
IHttpClientFactory httpClientFactory,
|
|
||||||
IHttpContextAccessor context,
|
|
||||||
BotSharpOptions options,
|
BotSharpOptions options,
|
||||||
EmailSenderSettings emailPluginSettings)
|
EmailSenderSettings emailPluginSettings)
|
||||||
{
|
{
|
||||||
_services = services;
|
_services = services;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_httpClientFactory = httpClientFactory;
|
|
||||||
_context = context;
|
|
||||||
_options = options;
|
_options = options;
|
||||||
_emailSettings = emailPluginSettings;
|
_emailSettings = emailPluginSettings;
|
||||||
}
|
}
|
||||||
|
|
@ -76,7 +70,16 @@ public class HandleEmailSenderFn : IFunctionCallback
|
||||||
var conversationId = convService.ConversationId;
|
var conversationId = convService.ConversationId;
|
||||||
|
|
||||||
var fileInstruct = _services.GetRequiredService<IFileInstructService>();
|
var fileInstruct = _services.GetRequiredService<IFileInstructService>();
|
||||||
var selecteds = await fileInstruct.SelectMessageFiles(conversationId, new SelectFileOptions { IncludeBotFile = true });
|
var selecteds = await fileInstruct.SelectMessageFiles(conversationId, new SelectFileOptions
|
||||||
|
{
|
||||||
|
IsIncludeBotFiles = true,
|
||||||
|
IsAttachFiles = true,
|
||||||
|
MessageLimit = 50,
|
||||||
|
Provider = "openai",
|
||||||
|
Model = "gpt-5-mini",
|
||||||
|
MaxOutputTokens = 8192,
|
||||||
|
ReasoningEffortLevel = "low"
|
||||||
|
});
|
||||||
return selecteds;
|
return selecteds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ public class EditImageFn : IFunctionCallback
|
||||||
private readonly ILogger<EditImageFn> _logger;
|
private readonly ILogger<EditImageFn> _logger;
|
||||||
private readonly FileHandlerSettings _settings;
|
private readonly FileHandlerSettings _settings;
|
||||||
|
|
||||||
|
private string _agentId;
|
||||||
private string _conversationId;
|
private string _conversationId;
|
||||||
private string _messageId;
|
private string _messageId;
|
||||||
|
|
||||||
|
|
@ -39,6 +40,7 @@ public class EditImageFn : IFunctionCallback
|
||||||
private void Init(RoleDialogModel message)
|
private void Init(RoleDialogModel message)
|
||||||
{
|
{
|
||||||
var convService = _services.GetRequiredService<IConversationService>();
|
var convService = _services.GetRequiredService<IConversationService>();
|
||||||
|
_agentId = message.CurrentAgentId;
|
||||||
_conversationId = convService.ConversationId;
|
_conversationId = convService.ConversationId;
|
||||||
_messageId = message.MessageId;
|
_messageId = message.MessageId;
|
||||||
}
|
}
|
||||||
|
|
@ -56,8 +58,14 @@ public class EditImageFn : IFunctionCallback
|
||||||
var selecteds = await fileInstruct.SelectMessageFiles(_conversationId, new SelectFileOptions
|
var selecteds = await fileInstruct.SelectMessageFiles(_conversationId, new SelectFileOptions
|
||||||
{
|
{
|
||||||
Description = description,
|
Description = description,
|
||||||
IncludeBotFile = true,
|
IsIncludeBotFiles = true,
|
||||||
ContentTypes = [MediaTypeNames.Image.Png]
|
IsAttachFiles = true,
|
||||||
|
ContentTypes = [MediaTypeNames.Image.Png, MediaTypeNames.Image.Jpeg],
|
||||||
|
MessageLimit = 50,
|
||||||
|
Provider = "openai",
|
||||||
|
Model = "gpt-5-mini",
|
||||||
|
MaxOutputTokens = 8192,
|
||||||
|
ReasoningEffortLevel = "low"
|
||||||
});
|
});
|
||||||
return selecteds?.FirstOrDefault();
|
return selecteds?.FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
@ -92,7 +100,12 @@ public class EditImageFn : IFunctionCallback
|
||||||
stream.Close();
|
stream.Close();
|
||||||
SaveGeneratedImage(response?.GeneratedImages?.FirstOrDefault());
|
SaveGeneratedImage(response?.GeneratedImages?.FirstOrDefault());
|
||||||
|
|
||||||
return $"Your image is successfylly editted.";
|
if (!string.IsNullOrWhiteSpace(response?.Content))
|
||||||
|
{
|
||||||
|
return response.Content;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await GetImageEditGreetingResponse(description);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -102,6 +115,21 @@ public class EditImageFn : IFunctionCallback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<string> GetImageEditGreetingResponse(string description)
|
||||||
|
{
|
||||||
|
var agent = new Agent
|
||||||
|
{
|
||||||
|
Id = BuiltInAgentId.UtilityAssistant,
|
||||||
|
Name = "Utility Assistant"
|
||||||
|
};
|
||||||
|
|
||||||
|
var text = $"Please generate a user-friendly response from the following description to inform user that you have completed the required image: {description}";
|
||||||
|
|
||||||
|
var completion = CompletionProvider.GetChatCompletion(_services, provider: "openai", model: "gpt-4o-mini");
|
||||||
|
var response = await completion.GetChatCompletions(agent, [new RoleDialogModel(AgentRole.User, text)]);
|
||||||
|
return response?.Content ?? "Your image is successfully edited.";
|
||||||
|
}
|
||||||
|
|
||||||
private (string, string) GetLlmProviderModel()
|
private (string, string) GetLlmProviderModel()
|
||||||
{
|
{
|
||||||
var state = _services.GetRequiredService<IConversationStateService>();
|
var state = _services.GetRequiredService<IConversationStateService>();
|
||||||
|
|
|
||||||
|
|
@ -58,13 +58,17 @@ public class ReadImageFn : IFunctionCallback
|
||||||
return new List<RoleDialogModel>();
|
return new List<RoleDialogModel>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var fileStorage = _services.GetRequiredService<IFileStorageService>();
|
var contentTypes = new List<string>
|
||||||
var messageIds = dialogs.Select(x => x.MessageId).Distinct().ToList();
|
|
||||||
var images = fileStorage.GetMessageFiles(conversationId, messageIds, FileSourceType.User, new List<string>
|
|
||||||
{
|
{
|
||||||
MediaTypeNames.Image.Png,
|
MediaTypeNames.Image.Png,
|
||||||
MediaTypeNames.Image.Jpeg
|
MediaTypeNames.Image.Jpeg
|
||||||
});
|
};
|
||||||
|
|
||||||
|
var fileStorage = _services.GetRequiredService<IFileStorageService>();
|
||||||
|
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);
|
||||||
|
|
||||||
foreach (var dialog in dialogs)
|
foreach (var dialog in dialogs)
|
||||||
{
|
{
|
||||||
|
|
@ -82,13 +86,12 @@ public class ReadImageFn : IFunctionCallback
|
||||||
if (!imageUrls.IsNullOrEmpty())
|
if (!imageUrls.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
var lastDialog = dialogs.LastOrDefault(x => x.Role == AgentRole.User) ?? dialogs.Last();
|
var lastDialog = dialogs.LastOrDefault(x => x.Role == AgentRole.User) ?? dialogs.Last();
|
||||||
var files = lastDialog.Files ?? [];
|
lastDialog.Files ??= [];
|
||||||
|
|
||||||
var addnFiles = imageUrls.Select(x => x?.Trim())
|
var addnFiles = imageUrls.Select(x => x?.Trim())
|
||||||
.Where(x => !string.IsNullOrWhiteSpace(x))
|
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||||||
.Select(x => new BotSharpFile { FileUrl = x }).ToList();
|
.Select(x => new BotSharpFile { FileUrl = x }).ToList();
|
||||||
|
lastDialog.Files.AddRange(addnFiles);
|
||||||
files.AddRange(addnFiles);
|
|
||||||
lastDialog.Files = files;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return dialogs;
|
return dialogs;
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Please call util-file-edit_image if user wants to edit or change an image in the conversation.
|
Please call util-file-edit_image if user wants to edit, change or modify an image in the conversation.
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
** Please call util-file-generate_image if user wants you to provide or generate an image or picture.
|
** Please call util-file-generate_image if user wants you to provide or generate an image or picture.
|
||||||
** If user does not generate image explicitly, please do not call generate_image.
|
** Please do not call util-file-generate_image, if user does not generate image explicitly or wants to change or edit the existing image.
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
using BotSharp.Abstraction.Files;
|
using BotSharp.Abstraction.Files;
|
||||||
|
using BotSharp.Abstraction.Files.Models;
|
||||||
using BotSharp.Abstraction.Files.Utilities;
|
using BotSharp.Abstraction.Files.Utilities;
|
||||||
using BotSharp.Abstraction.Hooks;
|
using BotSharp.Abstraction.Hooks;
|
||||||
using GenerativeAI;
|
using GenerativeAI;
|
||||||
using GenerativeAI.Core;
|
using GenerativeAI.Core;
|
||||||
using GenerativeAI.Types;
|
using GenerativeAI.Types;
|
||||||
|
using Google.Ai.Generativelanguage.V1Beta2;
|
||||||
|
|
||||||
namespace BotSharp.Plugin.GoogleAi.Providers.Chat;
|
namespace BotSharp.Plugin.GoogleAi.Providers.Chat;
|
||||||
|
|
||||||
|
|
@ -272,51 +274,22 @@ public class GeminiChatCompletionProvider : IChatCompletion
|
||||||
|
|
||||||
if (allowMultiModal && !message.Files.IsNullOrEmpty())
|
if (allowMultiModal && !message.Files.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
foreach (var file in message.Files)
|
CollectMessageContentParts(contentParts, message.Files);
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(file.FileData))
|
|
||||||
{
|
|
||||||
var (contentType, binary) = FileUtility.GetFileInfoFromData(file.FileData);
|
|
||||||
contentParts.Add(new Part()
|
|
||||||
{
|
|
||||||
InlineData = new()
|
|
||||||
{
|
|
||||||
MimeType = contentType.IfNullOrEmptyAs(file.ContentType),
|
|
||||||
Data = Convert.ToBase64String(binary.ToArray())
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (!string.IsNullOrEmpty(file.FileStorageUrl))
|
|
||||||
{
|
|
||||||
var contentType = FileUtility.GetFileContentType(file.FileStorageUrl);
|
|
||||||
var binary = fileStorage.GetFileBytes(file.FileStorageUrl);
|
|
||||||
contentParts.Add(new Part()
|
|
||||||
{
|
|
||||||
InlineData = new()
|
|
||||||
{
|
|
||||||
MimeType = contentType.IfNullOrEmptyAs(file.ContentType),
|
|
||||||
Data = Convert.ToBase64String(binary.ToArray())
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (!string.IsNullOrEmpty(file.FileUrl))
|
|
||||||
{
|
|
||||||
contentParts.Add(new Part()
|
|
||||||
{
|
|
||||||
FileData = new()
|
|
||||||
{
|
|
||||||
FileUri = file.FileUrl
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
contents.Add(new Content(contentParts, AgentRole.User));
|
contents.Add(new Content(contentParts, AgentRole.User));
|
||||||
convPrompts.Add($"{AgentRole.User}: {text}");
|
convPrompts.Add($"{AgentRole.User}: {text}");
|
||||||
}
|
}
|
||||||
else if (message.Role == AgentRole.Assistant)
|
else if (message.Role == AgentRole.Assistant)
|
||||||
{
|
{
|
||||||
contents.Add(new Content(message.Content, AgentRole.Model));
|
var text = message.Content;
|
||||||
|
var contentParts = new List<Part> { new() { Text = text } };
|
||||||
|
|
||||||
|
if (allowMultiModal && !message.Files.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
CollectMessageContentParts(contentParts, message.Files);
|
||||||
|
}
|
||||||
|
|
||||||
|
contents.Add(new Content(contentParts, AgentRole.Model));
|
||||||
convPrompts.Add($"{AgentRole.Assistant}: {message.Content}");
|
convPrompts.Add($"{AgentRole.Assistant}: {message.Content}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -342,6 +315,50 @@ public class GeminiChatCompletionProvider : IChatCompletion
|
||||||
return (prompt, request);
|
return (prompt, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void CollectMessageContentParts(List<Part> contentParts, List<BotSharpFile> files)
|
||||||
|
{
|
||||||
|
var fileStorage = _services.GetRequiredService<IFileStorageService>();
|
||||||
|
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(file.FileData))
|
||||||
|
{
|
||||||
|
var (contentType, binary) = FileUtility.GetFileInfoFromData(file.FileData);
|
||||||
|
contentParts.Add(new Part()
|
||||||
|
{
|
||||||
|
InlineData = new()
|
||||||
|
{
|
||||||
|
MimeType = contentType.IfNullOrEmptyAs(file.ContentType),
|
||||||
|
Data = Convert.ToBase64String(binary.ToArray())
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(file.FileStorageUrl))
|
||||||
|
{
|
||||||
|
var contentType = FileUtility.GetFileContentType(file.FileStorageUrl);
|
||||||
|
var binary = fileStorage.GetFileBytes(file.FileStorageUrl);
|
||||||
|
contentParts.Add(new Part()
|
||||||
|
{
|
||||||
|
InlineData = new()
|
||||||
|
{
|
||||||
|
MimeType = contentType.IfNullOrEmptyAs(file.ContentType),
|
||||||
|
Data = Convert.ToBase64String(binary.ToArray())
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(file.FileUrl))
|
||||||
|
{
|
||||||
|
contentParts.Add(new Part()
|
||||||
|
{
|
||||||
|
FileData = new()
|
||||||
|
{
|
||||||
|
FileUri = file.FileUrl
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private string GetPrompt(IEnumerable<string> systemPrompts, IEnumerable<string> funcPrompts, IEnumerable<string> convPrompts)
|
private string GetPrompt(IEnumerable<string> systemPrompts, IEnumerable<string> funcPrompts, IEnumerable<string> convPrompts)
|
||||||
{
|
{
|
||||||
var prompt = string.Empty;
|
var prompt = string.Empty;
|
||||||
|
|
|
||||||
|
|
@ -321,7 +321,6 @@ public class ChatCompletionProvider : IChatCompletion
|
||||||
{
|
{
|
||||||
var agentService = _services.GetRequiredService<IAgentService>();
|
var agentService = _services.GetRequiredService<IAgentService>();
|
||||||
var state = _services.GetRequiredService<IConversationStateService>();
|
var state = _services.GetRequiredService<IConversationStateService>();
|
||||||
var fileStorage = _services.GetRequiredService<IFileStorageService>();
|
|
||||||
var settingsService = _services.GetRequiredService<ILlmProviderService>();
|
var settingsService = _services.GetRequiredService<ILlmProviderService>();
|
||||||
var settings = settingsService.GetSetting(Provider, _model);
|
var settings = settingsService.GetSetting(Provider, _model);
|
||||||
var allowMultiModal = settings != null && settings.MultiModal;
|
var allowMultiModal = settings != null && settings.MultiModal;
|
||||||
|
|
@ -397,34 +396,21 @@ public class ChatCompletionProvider : IChatCompletion
|
||||||
|
|
||||||
if (allowMultiModal && !message.Files.IsNullOrEmpty())
|
if (allowMultiModal && !message.Files.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
foreach (var file in message.Files)
|
CollectMessageContentParts(contentParts, message.Files, imageDetailLevel);
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(file.FileData))
|
|
||||||
{
|
|
||||||
var (contentType, binary) = FileUtility.GetFileInfoFromData(file.FileData);
|
|
||||||
var contentPart = ChatMessageContentPart.CreateImagePart(binary, contentType.IfNullOrEmptyAs(file.ContentType), imageDetailLevel);
|
|
||||||
contentParts.Add(contentPart);
|
|
||||||
}
|
|
||||||
else if (!string.IsNullOrEmpty(file.FileStorageUrl))
|
|
||||||
{
|
|
||||||
var contentType = FileUtility.GetFileContentType(file.FileStorageUrl);
|
|
||||||
var binary = fileStorage.GetFileBytes(file.FileStorageUrl);
|
|
||||||
var contentPart = ChatMessageContentPart.CreateImagePart(binary, contentType.IfNullOrEmptyAs(file.ContentType), imageDetailLevel);
|
|
||||||
contentParts.Add(contentPart);
|
|
||||||
}
|
|
||||||
else if (!string.IsNullOrEmpty(file.FileUrl))
|
|
||||||
{
|
|
||||||
var uri = new Uri(file.FileUrl);
|
|
||||||
var contentPart = ChatMessageContentPart.CreateImagePart(uri, imageDetailLevel);
|
|
||||||
contentParts.Add(contentPart);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
messages.Add(new UserChatMessage(contentParts) { ParticipantName = message.FunctionName });
|
messages.Add(new UserChatMessage(contentParts) { ParticipantName = message.FunctionName });
|
||||||
}
|
}
|
||||||
else if (message.Role == AgentRole.Assistant)
|
else if (message.Role == AgentRole.Assistant)
|
||||||
{
|
{
|
||||||
messages.Add(new AssistantChatMessage(message.Content));
|
var text = message.Content;
|
||||||
|
var textPart = ChatMessageContentPart.CreateTextPart(text);
|
||||||
|
var contentParts = new List<ChatMessageContentPart> { textPart };
|
||||||
|
|
||||||
|
if (allowMultiModal && !message.Files.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
CollectMessageContentParts(contentParts, message.Files, imageDetailLevel);
|
||||||
|
}
|
||||||
|
messages.Add(new AssistantChatMessage(contentParts));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -432,6 +418,33 @@ public class ChatCompletionProvider : IChatCompletion
|
||||||
return (prompt, messages, options);
|
return (prompt, messages, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void CollectMessageContentParts(List<ChatMessageContentPart> contentParts, List<BotSharpFile> files, ChatImageDetailLevel imageDetailLevel)
|
||||||
|
{
|
||||||
|
var fileStorage = _services.GetRequiredService<IFileStorageService>();
|
||||||
|
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(file.FileData))
|
||||||
|
{
|
||||||
|
var (contentType, binary) = FileUtility.GetFileInfoFromData(file.FileData);
|
||||||
|
var contentPart = ChatMessageContentPart.CreateImagePart(binary, contentType.IfNullOrEmptyAs(file.ContentType), imageDetailLevel);
|
||||||
|
contentParts.Add(contentPart);
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(file.FileStorageUrl))
|
||||||
|
{
|
||||||
|
var contentType = FileUtility.GetFileContentType(file.FileStorageUrl);
|
||||||
|
var binary = fileStorage.GetFileBytes(file.FileStorageUrl);
|
||||||
|
var contentPart = ChatMessageContentPart.CreateImagePart(binary, contentType.IfNullOrEmptyAs(file.ContentType), imageDetailLevel);
|
||||||
|
contentParts.Add(contentPart);
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(file.FileUrl))
|
||||||
|
{
|
||||||
|
var uri = new Uri(file.FileUrl);
|
||||||
|
var contentPart = ChatMessageContentPart.CreateImagePart(uri, imageDetailLevel);
|
||||||
|
contentParts.Add(contentPart);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private string GetPrompt(IEnumerable<ChatMessage> messages, ChatCompletionOptions options)
|
private string GetPrompt(IEnumerable<ChatMessage> messages, ChatCompletionOptions options)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue