Merge branch 'master' into lida_dev
# Conflicts: # src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.DoAction.cs 合并,优化Locator option逻辑
This commit is contained in:
commit
933a01a3e2
28
BotSharp.sln
28
BotSharp.sln
|
|
@ -105,6 +105,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "FileStorages", "FileStorage
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.TencentCos", "src\Plugins\BotSharp.Plugin.TencentCos\BotSharp.Plugin.TencentCos.csproj", "{BF029B0A-768B-43A1-8D91-E70B95505716}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Interpreters", "Interpreters", "{C4C59872-3C8A-450D-83D5-2BE402D610D5}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.PythonInterpreter", "src\Plugins\BotSharp.Plugin.PythonInterpreter\BotSharp.Plugin.PythonInterpreter.csproj", "{05E6E405-5021-406E-8A5E-0A7CEC881F6D}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Graph", "Graph", "{97A0B191-64D7-4F8A-BFE8-1BFCC5E247E1}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.Graph", "src\Plugins\BotSharp.Plugin.Graph\BotSharp.Plugin.Graph.csproj", "{EBFE97DA-D0BA-48BA-8B5D-083B60348D1D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -425,6 +433,22 @@ Global
|
|||
{BF029B0A-768B-43A1-8D91-E70B95505716}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BF029B0A-768B-43A1-8D91-E70B95505716}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{BF029B0A-768B-43A1-8D91-E70B95505716}.Release|x64.Build.0 = Release|Any CPU
|
||||
{05E6E405-5021-406E-8A5E-0A7CEC881F6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{05E6E405-5021-406E-8A5E-0A7CEC881F6D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{05E6E405-5021-406E-8A5E-0A7CEC881F6D}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{05E6E405-5021-406E-8A5E-0A7CEC881F6D}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{05E6E405-5021-406E-8A5E-0A7CEC881F6D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{05E6E405-5021-406E-8A5E-0A7CEC881F6D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{05E6E405-5021-406E-8A5E-0A7CEC881F6D}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{05E6E405-5021-406E-8A5E-0A7CEC881F6D}.Release|x64.Build.0 = Release|Any CPU
|
||||
{EBFE97DA-D0BA-48BA-8B5D-083B60348D1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{EBFE97DA-D0BA-48BA-8B5D-083B60348D1D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EBFE97DA-D0BA-48BA-8B5D-083B60348D1D}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{EBFE97DA-D0BA-48BA-8B5D-083B60348D1D}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{EBFE97DA-D0BA-48BA-8B5D-083B60348D1D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EBFE97DA-D0BA-48BA-8B5D-083B60348D1D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{EBFE97DA-D0BA-48BA-8B5D-083B60348D1D}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{EBFE97DA-D0BA-48BA-8B5D-083B60348D1D}.Release|x64.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -475,6 +499,10 @@ Global
|
|||
{54E83C6F-54EE-4ADC-8D72-93C009CC4FB4} = {51AFE054-AE99-497D-A593-69BAEFB5106F}
|
||||
{38B37C0D-1930-4D47-BCBF-E358EC1096B1} = {2635EC9B-2E5F-4313-AC21-0B847F31F36C}
|
||||
{BF029B0A-768B-43A1-8D91-E70B95505716} = {38B37C0D-1930-4D47-BCBF-E358EC1096B1}
|
||||
{C4C59872-3C8A-450D-83D5-2BE402D610D5} = {2635EC9B-2E5F-4313-AC21-0B847F31F36C}
|
||||
{05E6E405-5021-406E-8A5E-0A7CEC881F6D} = {C4C59872-3C8A-450D-83D5-2BE402D610D5}
|
||||
{97A0B191-64D7-4F8A-BFE8-1BFCC5E247E1} = {2635EC9B-2E5F-4313-AC21-0B847F31F36C}
|
||||
{EBFE97DA-D0BA-48BA-8B5D-083B60348D1D} = {97A0B191-64D7-4F8A-BFE8-1BFCC5E247E1}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,13 @@ public interface IAgentService
|
|||
/// <returns></returns>
|
||||
Task<Agent> LoadAgent(string id);
|
||||
|
||||
/// <summary>
|
||||
/// Inherit from an agent
|
||||
/// </summary>
|
||||
/// <param name="agent"></param>
|
||||
/// <returns></returns>
|
||||
Task InheritAgent(Agent agent);
|
||||
|
||||
string RenderedInstruction(Agent agent);
|
||||
|
||||
string RenderedTemplate(Agent agent, string templateName);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Plugins.Models;
|
||||
using BotSharp.Abstraction.Routing.Models;
|
||||
using BotSharp.Abstraction.Tasks.Models;
|
||||
|
||||
namespace BotSharp.Abstraction.Agents.Models;
|
||||
|
|
@ -21,8 +20,7 @@ public class Agent
|
|||
/// Default LLM settings
|
||||
/// </summary>
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public AgentLlmConfig LlmConfig { get; set; }
|
||||
= new AgentLlmConfig();
|
||||
public AgentLlmConfig LlmConfig { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Instruction
|
||||
|
|
@ -30,33 +28,35 @@ public class Agent
|
|||
[JsonIgnore]
|
||||
public string? Instruction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Channel instructions
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public List<ChannelInstruction> ChannelInstructions { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Templates
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public List<AgentTemplate> Templates { get; set; }
|
||||
= new List<AgentTemplate>();
|
||||
public List<AgentTemplate> Templates { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Agent tasks
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public List<AgentTask> Tasks { get; set; }
|
||||
= new List<AgentTask>();
|
||||
public List<AgentTask> Tasks { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Samples
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public List<string> Samples { get; set; }
|
||||
= new List<string>();
|
||||
public List<string> Samples { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Functions
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public List<FunctionDef> Functions { get; set; }
|
||||
= new List<FunctionDef>();
|
||||
public List<FunctionDef> Functions { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Responses
|
||||
|
|
@ -93,23 +93,20 @@ public class Agent
|
|||
/// <summary>
|
||||
/// Agent utilities
|
||||
/// </summary>
|
||||
public List<string> Utilities { get; set; }
|
||||
= new List<string>();
|
||||
public List<string> Utilities { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Inherit from agent
|
||||
/// </summary>
|
||||
public string? InheritAgentId { get; set; }
|
||||
|
||||
public List<RoutingRule> RoutingRules { get; set; }
|
||||
= new List<RoutingRule>();
|
||||
public List<RoutingRule> RoutingRules { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// For rendering deferral
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public Dictionary<string, object> TemplateDict { get; set; }
|
||||
= new Dictionary<string, object>();
|
||||
public Dictionary<string, object> TemplateDict { get; set; } = new();
|
||||
|
||||
public override string ToString()
|
||||
=> $"{Name} {Id}";
|
||||
|
|
@ -124,6 +121,7 @@ public class Agent
|
|||
Description = agent.Description,
|
||||
Type = agent.Type,
|
||||
Instruction = agent.Instruction,
|
||||
ChannelInstructions = agent.ChannelInstructions,
|
||||
Functions = agent.Functions,
|
||||
Responses = agent.Responses,
|
||||
Samples = agent.Samples,
|
||||
|
|
@ -145,6 +143,12 @@ public class Agent
|
|||
return this;
|
||||
}
|
||||
|
||||
public Agent SetChannelInstructions(List<ChannelInstruction> instructions)
|
||||
{
|
||||
ChannelInstructions = instructions ?? new List<ChannelInstruction>();
|
||||
return this;
|
||||
}
|
||||
|
||||
public Agent SetTemplates(List<AgentTemplate> templates)
|
||||
{
|
||||
Templates = templates ?? new List<AgentTemplate>();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
namespace BotSharp.Abstraction.Agents.Models;
|
||||
|
||||
public class ChannelInstruction
|
||||
{
|
||||
public string Channel { get; set; }
|
||||
public string Instruction { get; set; }
|
||||
}
|
||||
|
|
@ -25,11 +25,14 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||
<PackageReference Include="System.Memory.Data" Version="8.0.0" />
|
||||
<PackageReference Include="System.Text.Json" Version="8.0.4" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
|
|
|
|||
|
|
@ -4,6 +4,6 @@ namespace BotSharp.Abstraction.Browsing;
|
|||
|
||||
public interface IWebPageResponseHook
|
||||
{
|
||||
void OnDataFetched(MessageInfo message, string url, string postData, string responsData);
|
||||
T? GetResponse<T>(MessageInfo message, string url, string? queryParameter = null);
|
||||
void OnDataFetched(MessageInfo message, WebPageResponseData response);
|
||||
T? GetResponse<T>(MessageInfo message, WebPageResponseFilter filter);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,10 @@ public class ElementActionArgs
|
|||
|
||||
public string? PressKey { get; set; }
|
||||
|
||||
public bool? IsFirst { get; set; }
|
||||
/// <summary>
|
||||
/// Locator option
|
||||
/// </summary>
|
||||
public bool FirstIfMultipleFound { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Wait time in seconds
|
||||
|
|
|
|||
|
|
@ -15,6 +15,25 @@ public class PageActionArgs
|
|||
/// This value has to be set to true if you want to get the page XHR/ Fetch responses
|
||||
/// </summary>
|
||||
public bool OpenNewTab { get; set; } = false;
|
||||
|
||||
public bool EnableResponseCallback { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Exclude urls for XHR/ Fetch responses
|
||||
/// </summary>
|
||||
public string[]? ExcludeResponseUrls { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Only include urls for XHR/ Fetch responses
|
||||
/// </summary>
|
||||
public string[]? IncludeResponseUrls { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If set to true, the response will be stored in memory
|
||||
/// </summary>
|
||||
public bool ResponseInMemory { get; set; } = false;
|
||||
public List<WebPageResponseData>? ResponseContainer { get; set; }
|
||||
|
||||
public bool UseExistingPage { get; set; } = false;
|
||||
|
||||
public bool WaitForNetworkIdle { get; set; } = true;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
namespace BotSharp.Abstraction.Browsing.Models;
|
||||
|
||||
public class WebPageResponseData
|
||||
{
|
||||
public string Url { get; set; } = null!;
|
||||
public string PostData { get; set; } = null!;
|
||||
public string ResponseData { get; set; } = null!;
|
||||
public bool ResponseInMemory { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
namespace BotSharp.Abstraction.Browsing.Models;
|
||||
|
||||
public class WebPageResponseFilter
|
||||
{
|
||||
public string Url { get; set; } = null!;
|
||||
public string[]? QueryParameters { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
namespace BotSharp.Abstraction.Files.Constants;
|
||||
|
||||
public class FileConstants
|
||||
{
|
||||
public static readonly IEnumerable<string> AudioExtensions = new List<string>
|
||||
{
|
||||
".mp3", ".wav", ".flac", ".aac", ".ogg", ".wma"
|
||||
};
|
||||
}
|
||||
|
|
@ -2,6 +2,8 @@ namespace BotSharp.Abstraction.Files.Converters;
|
|||
|
||||
public interface IPdf2ImageConverter
|
||||
{
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Convert pdf pages to images, and return a list of image file paths
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
using BotSharp.Abstraction.Repositories.Enums;
|
||||
|
||||
namespace BotSharp.Abstraction.Files;
|
||||
|
||||
public class FileCoreSettings
|
||||
{
|
||||
public string Storage { get; set; } = FileStorageEnum.LocalFileStorage;
|
||||
public string Pdf2TextConverter { get; set; }
|
||||
public string Pdf2ImageConverter { get; set; }
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
using BotSharp.Abstraction.Repositories.Enums;
|
||||
|
||||
namespace BotSharp.Abstraction.Files;
|
||||
|
||||
public class FileStorageSettings
|
||||
{
|
||||
public string Default { get; set; } = FileStorageEnum.LocalFileStorage;
|
||||
}
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
using System.IO;
|
||||
|
||||
namespace BotSharp.Abstraction.Files;
|
||||
|
||||
public interface IBotSharpFileService
|
||||
{
|
||||
#region Conversation
|
||||
/// <summary>
|
||||
/// Get the files that have been uploaded in the chat.
|
||||
/// If includeScreenShot is true, it will take the screenshots of non-image files, such as pdf, and return the screenshots instead of the original file.
|
||||
/// </summary>
|
||||
/// <param name="conversationId"></param>
|
||||
/// <param name="source"></param>
|
||||
/// <param name="conversations"></param>
|
||||
/// <param name="contentTypes"></param>
|
||||
/// <param name="includeScreenShot"></param>
|
||||
/// <param name="offset"></param>
|
||||
/// <returns></returns>
|
||||
Task<IEnumerable<MessageFileModel>> GetChatFiles(string conversationId, string source,
|
||||
IEnumerable<RoleDialogModel> conversations, IEnumerable<string> contentTypes,
|
||||
bool includeScreenShot = false, int? offset = null);
|
||||
|
||||
/// <summary>
|
||||
/// Get the files that have been uploaded in the chat. No screenshot images are included.
|
||||
/// </summary>
|
||||
/// <param name="conversationId"></param>
|
||||
/// <param name="messageIds"></param>
|
||||
/// <param name="source"></param>
|
||||
/// <param name="imageOnly"></param>
|
||||
/// <returns></returns>
|
||||
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);
|
||||
|
||||
/// <summary>
|
||||
/// Delete files under messages
|
||||
/// </summary>
|
||||
/// <param name="conversationId">Conversation Id</param>
|
||||
/// <param name="messageIds">Files in these messages will be deleted</param>
|
||||
/// <param name="targetMessageId">The starting message to delete</param>
|
||||
/// <param name="newMessageId">If not null, delete messages while input a new message; otherwise, delete messages only</param>
|
||||
/// <returns></returns>
|
||||
bool DeleteMessageFiles(string conversationId, IEnumerable<string> messageIds, string targetMessageId, string? newMessageId = null);
|
||||
bool DeleteConversationFiles(IEnumerable<string> conversationIds);
|
||||
#endregion
|
||||
|
||||
#region Image
|
||||
Task<RoleDialogModel> GenerateImage(string? provider, string? model, string text);
|
||||
Task<RoleDialogModel> VaryImage(string? provider, string? model, BotSharpFile image);
|
||||
Task<RoleDialogModel> EditImage(string? provider, string? model, string text, BotSharpFile image);
|
||||
Task<RoleDialogModel> EditImage(string? provider, string? model, string text, BotSharpFile image, BotSharpFile mask);
|
||||
#endregion
|
||||
|
||||
#region Pdf
|
||||
/// <summary>
|
||||
/// Take screenshots of pdf pages and get response from llm
|
||||
/// </summary>
|
||||
/// <param name="prompt"></param>
|
||||
/// <param name="files">Pdf files</param>
|
||||
/// <returns></returns>
|
||||
Task<string> ReadPdf(string? provider, string? model, string? modelId, string prompt, List<BotSharpFile> files);
|
||||
#endregion
|
||||
|
||||
#region User
|
||||
string GetUserAvatar();
|
||||
bool SaveUserAvatar(BotSharpFile file);
|
||||
#endregion
|
||||
|
||||
#region Common
|
||||
/// <summary>
|
||||
/// Get file bytes and content type from data, e.g., "data:image/png;base64,aaaaaaaaa"
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
(string, byte[]) GetFileInfoFromData(string data);
|
||||
string GetDirectory(string conversationId);
|
||||
string GetFileContentType(string filePath);
|
||||
byte[] GetFileBytes(string fileStorageUrl);
|
||||
bool SavefileToPath(string filePath, Stream stream);
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
namespace BotSharp.Abstraction.Files;
|
||||
|
||||
public interface IFileInstructService
|
||||
{
|
||||
#region Image
|
||||
Task<RoleDialogModel> ReadImages(string? provider, string? model, string text, IEnumerable<BotSharpFile> images);
|
||||
Task<RoleDialogModel> GenerateImage(string? provider, string? model, string text);
|
||||
Task<RoleDialogModel> VaryImage(string? provider, string? model, BotSharpFile image);
|
||||
Task<RoleDialogModel> EditImage(string? provider, string? model, string text, BotSharpFile image);
|
||||
Task<RoleDialogModel> EditImage(string? provider, string? model, string text, BotSharpFile image, BotSharpFile mask);
|
||||
#endregion
|
||||
|
||||
#region Pdf
|
||||
/// <summary>
|
||||
/// Take screenshots of pdf pages and get response from llm
|
||||
/// </summary>
|
||||
/// <param name="prompt"></param>
|
||||
/// <param name="files">Pdf files</param>
|
||||
/// <returns></returns>
|
||||
Task<string> ReadPdf(string? provider, string? model, string? modelId, string prompt, List<BotSharpFile> files);
|
||||
#endregion
|
||||
|
||||
#region Select file
|
||||
Task<IEnumerable<MessageFileModel>> SelectMessageFiles(string conversationId, SelectFileOptions options);
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
using System.IO;
|
||||
|
||||
namespace BotSharp.Abstraction.Files;
|
||||
|
||||
public interface IFileStorageService
|
||||
{
|
||||
#region Common
|
||||
string GetDirectory(string conversationId);
|
||||
byte[] GetFileBytes(string fileStorageUrl);
|
||||
bool SaveFileStreamToPath(string filePath, Stream stream);
|
||||
bool SaveFileBytesToPath(string filePath, byte[] bytes);
|
||||
string GetParentDir(string dir, int level = 1);
|
||||
bool ExistDirectory(string? dir);
|
||||
void CreateDirectory(string dir);
|
||||
void DeleteDirectory(string dir);
|
||||
string BuildDirectory(params string[] segments);
|
||||
#endregion
|
||||
|
||||
|
||||
#region Conversation
|
||||
/// <summary>
|
||||
/// Get the message file screenshots for specific content types, e.g., pdf
|
||||
/// </summary>
|
||||
/// <param name="conversationId"></param>
|
||||
/// <param name="messageIds"></param>
|
||||
/// <returns></returns>
|
||||
Task<IEnumerable<MessageFileModel>> GetMessageFileScreenshots(string conversationId, IEnumerable<string> messageIds);
|
||||
|
||||
/// <summary>
|
||||
/// Get the files that have been uploaded in the chat. No screenshot images are included.
|
||||
/// </summary>
|
||||
/// <param name="conversationId"></param>
|
||||
/// <param name="messageIds"></param>
|
||||
/// <param name="source"></param>
|
||||
/// <param name="imageOnly"></param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<MessageFileModel> GetMessageFiles(string conversationId, IEnumerable<string> messageIds, string source, IEnumerable<string>? contentTypes = null);
|
||||
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);
|
||||
|
||||
/// <summary>
|
||||
/// Delete files under messages
|
||||
/// </summary>
|
||||
/// <param name="conversationId">Conversation Id</param>
|
||||
/// <param name="messageIds">Files in these messages will be deleted</param>
|
||||
/// <param name="targetMessageId">The starting message to delete</param>
|
||||
/// <param name="newMessageId">If not null, delete messages while input a new message; otherwise, delete messages only</param>
|
||||
/// <returns></returns>
|
||||
bool DeleteMessageFiles(string conversationId, IEnumerable<string> messageIds, string targetMessageId, string? newMessageId = null);
|
||||
bool DeleteConversationFiles(IEnumerable<string> conversationIds);
|
||||
#endregion
|
||||
|
||||
|
||||
#region User
|
||||
string GetUserAvatar();
|
||||
bool SaveUserAvatar(BotSharpFile file);
|
||||
#endregion
|
||||
#region Speech
|
||||
Task SaveSpeechFileAsync(string conversationId, string fileName, BinaryData data);
|
||||
Task<BinaryData> RetrieveSpeechFileAsync(string conversationId, string fileName);
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -1,23 +1,7 @@
|
|||
|
||||
namespace BotSharp.Abstraction.Files.Models;
|
||||
|
||||
public class BotSharpFile
|
||||
public class BotSharpFile : FileBase
|
||||
{
|
||||
[JsonPropertyName("file_name")]
|
||||
public string FileName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// File data, e.g., "data:image/png;base64,aaaaaaaa"
|
||||
/// </summary>
|
||||
[JsonPropertyName("file_data")]
|
||||
public string FileData { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("file_url")]
|
||||
public string FileUrl { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("content_type")]
|
||||
public string ContentType { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("file_storage_url")]
|
||||
public string FileStorageUrl { get; set; } = string.Empty;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
namespace BotSharp.Abstraction.Files.Models;
|
||||
|
||||
public class FileBase
|
||||
{
|
||||
/// <summary>
|
||||
/// External file url
|
||||
/// </summary>
|
||||
[JsonPropertyName("file_url")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? FileUrl { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Internal file storage url
|
||||
/// </summary>
|
||||
[JsonPropertyName("file_storage_url")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? FileStorageUrl { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// File name without extension
|
||||
/// </summary>
|
||||
[JsonPropertyName("file_name")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? FileName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// File data, e.g., "data:image/png;base64,aaaaaaaa"
|
||||
/// </summary>
|
||||
[JsonPropertyName("file_data")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? FileData { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// File content type
|
||||
/// </summary>
|
||||
[JsonPropertyName("content_type")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? ContentType { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// File extension without dot
|
||||
/// </summary>
|
||||
[JsonPropertyName("file_extension")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? FileExtension { get; set; } = string.Empty;
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
namespace BotSharp.Abstraction.Files.Models;
|
||||
|
||||
public class FileSelectContext
|
||||
{
|
||||
[JsonPropertyName("selected_ids")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public IEnumerable<int>? Selecteds { get; set; }
|
||||
}
|
||||
|
|
@ -1,34 +1,10 @@
|
|||
namespace BotSharp.Abstraction.Files.Models;
|
||||
|
||||
public class MessageFileModel
|
||||
public class MessageFileModel : FileBase
|
||||
{
|
||||
[JsonPropertyName("message_id")]
|
||||
public string MessageId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// External file url
|
||||
/// </summary>
|
||||
[JsonPropertyName("file_url")]
|
||||
public string FileUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Internal file storage url
|
||||
/// </summary>
|
||||
[JsonPropertyName("file_storage_url")]
|
||||
public string FileStorageUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// File name without extension
|
||||
/// </summary>
|
||||
[JsonPropertyName("file_name")]
|
||||
public string FileName { get; set; }
|
||||
|
||||
[JsonPropertyName("file_type")]
|
||||
public string FileType { get; set; }
|
||||
|
||||
[JsonPropertyName("content_type")]
|
||||
public string ContentType { get; set; }
|
||||
|
||||
[JsonPropertyName("file_source")]
|
||||
public string FileSource { get; set; } = FileSourceType.User;
|
||||
|
||||
|
|
@ -39,6 +15,6 @@ public class MessageFileModel
|
|||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"File name: {FileName}, File type: {FileType}, Content type: {ContentType}";
|
||||
return $"File name: {FileName}, File extension: {FileExtension}, Content type: {ContentType}, Source: {FileSource}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
namespace BotSharp.Abstraction.Files.Models;
|
||||
|
||||
public class SelectFileOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Llm provider
|
||||
/// </summary>
|
||||
public string? Provider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Llm model id
|
||||
/// </summary>
|
||||
public string? ModelId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Agent id
|
||||
/// </summary>
|
||||
public string? AgentId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Template (prompt) name
|
||||
/// </summary>
|
||||
public string? Template { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Description that user provides to select files
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether include bot generated files
|
||||
/// </summary>
|
||||
public bool IncludeBotFile { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Conversation breakpoint
|
||||
/// </summary>
|
||||
public bool FromBreakpoint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Message offset from last
|
||||
/// </summary>
|
||||
public int? Offset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// File content types. If null, all types of files will be retrived
|
||||
/// </summary>
|
||||
public IEnumerable<string>? ContentTypes { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
using Microsoft.AspNetCore.StaticFiles;
|
||||
|
||||
namespace BotSharp.Abstraction.Files.Utilities;
|
||||
|
||||
public static class FileUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// Get file bytes and content type from data, e.g., "data:image/png;base64,aaaaaaaaa"
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public static (string, byte[]) GetFileInfoFromData(string data)
|
||||
{
|
||||
if (string.IsNullOrEmpty(data))
|
||||
{
|
||||
return (string.Empty, new byte[0]);
|
||||
}
|
||||
|
||||
var typeStartIdx = data.IndexOf(':');
|
||||
var typeEndIdx = data.IndexOf(';');
|
||||
var contentType = data.Substring(typeStartIdx + 1, typeEndIdx - typeStartIdx - 1);
|
||||
|
||||
var base64startIdx = data.IndexOf(',');
|
||||
var base64Str = data.Substring(base64startIdx + 1);
|
||||
|
||||
return (contentType, Convert.FromBase64String(base64Str));
|
||||
}
|
||||
|
||||
public static string GetFileContentType(string filePath)
|
||||
{
|
||||
string contentType;
|
||||
var provider = new FileExtensionContentTypeProvider();
|
||||
if (!provider.TryGetContentType(filePath, out contentType))
|
||||
{
|
||||
contentType = string.Empty;
|
||||
}
|
||||
|
||||
return contentType;
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,12 @@ public class FunctionDef
|
|||
[JsonPropertyName("description")]
|
||||
public string Description { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("channels")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public List<string>? Channels { get; set; }
|
||||
|
||||
[JsonPropertyName("visibility_expression")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? VisibilityExpression { get; set; }
|
||||
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
|
|
|
|||
10
src/Infrastructure/BotSharp.Abstraction/Graph/IGraphDb.cs
Normal file
10
src/Infrastructure/BotSharp.Abstraction/Graph/IGraphDb.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
using BotSharp.Abstraction.Graph.Models;
|
||||
|
||||
namespace BotSharp.Abstraction.Graph;
|
||||
|
||||
public interface IGraphDb
|
||||
{
|
||||
public string Name { get; }
|
||||
|
||||
Task<GraphSearchData> Search(string query, GraphSearchOptions options);
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
namespace BotSharp.Abstraction.Graph.Models;
|
||||
|
||||
public class GraphSearchData
|
||||
{
|
||||
public string Result { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
namespace BotSharp.Abstraction.Graph.Models;
|
||||
|
||||
public class GraphSearchOptions
|
||||
{
|
||||
public string Method { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
namespace BotSharp.Abstraction.Graph.Models;
|
||||
|
||||
public class GraphSearchResult
|
||||
{
|
||||
public string Result { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
namespace BotSharp.Abstraction.Interpreters.Models;
|
||||
|
||||
public class InterpretationRequest
|
||||
{
|
||||
[JsonPropertyName("script")]
|
||||
public string Script { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("language")]
|
||||
public string Language { get; set; } = null!;
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
namespace BotSharp.Abstraction.Knowledges.Enums;
|
||||
|
||||
public static class KnowledgeCollectionName
|
||||
{
|
||||
public static string BotSharp = nameof(BotSharp);
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
namespace BotSharp.Abstraction.Knowledges.Enums;
|
||||
|
||||
public static class KnowledgePayloadName
|
||||
{
|
||||
public static string Text = "text";
|
||||
public static string Question = "question";
|
||||
public static string Answer = "answer";
|
||||
public static string Request = "request";
|
||||
public static string Response = "response";
|
||||
}
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
using BotSharp.Abstraction.Knowledges.Models;
|
||||
|
||||
namespace BotSharp.Abstraction.Knowledges;
|
||||
|
||||
public interface IKnowledgeHook
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
using BotSharp.Abstraction.Knowledges.Models;
|
||||
using BotSharp.Abstraction.Graph.Models;
|
||||
using BotSharp.Abstraction.VectorStorage.Models;
|
||||
|
||||
namespace BotSharp.Abstraction.Knowledges;
|
||||
|
||||
public interface IKnowledgeService
|
||||
{
|
||||
Task<List<KnowledgeChunk>> CollectChunkedKnowledge();
|
||||
Task EmbedKnowledge(List<KnowledgeChunk> chunks);
|
||||
|
||||
Task Feed(KnowledgeFeedModel knowledge);
|
||||
Task EmbedKnowledge(KnowledgeCreationModel knowledge);
|
||||
Task<string> GetKnowledges(KnowledgeRetrievalModel retrievalModel);
|
||||
Task<List<RetrievedResult>> GetAnswer(KnowledgeRetrievalModel retrievalModel);
|
||||
Task<IEnumerable<string>> GetVectorCollections();
|
||||
Task<IEnumerable<VectorSearchResult>> SearchVectorKnowledge(string query, string collectionName, VectorSearchOptions options);
|
||||
Task FeedVectorKnowledge(string collectionName, KnowledgeCreationModel model);
|
||||
Task<StringIdPagedItems<VectorSearchResult>> GetVectorCollectionData(string collectionName, VectorFilter filter);
|
||||
Task<bool> DeleteVectorCollectionData(string collectionName, string id);
|
||||
Task<GraphSearchResult> SearchGraphKnowledge(string query, GraphSearchOptions options);
|
||||
Task<KnowledgeSearchResult> SearchKnowledge(string query, string collectionName, VectorSearchOptions vectorOptions, GraphSearchOptions graphOptions);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,8 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace BotSharp.Abstraction.Knowledges
|
||||
{
|
||||
public interface IPdf2TextConverter
|
||||
{
|
||||
public string Name { get; }
|
||||
Task<string> ConvertPdfToText(string filePath, int? startPageNum, int? endPageNum);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
namespace BotSharp.Abstraction.Knowledges.Models;
|
||||
|
||||
public class KnowledgeFeedModel
|
||||
{
|
||||
public string AgentId { get; set; } = string.Empty;
|
||||
public string Content { get; set; } = string.Empty;
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
namespace BotSharp.Abstraction.Knowledges.Models;
|
||||
|
||||
public class KnowledgeRetrievalModel
|
||||
{
|
||||
public string AgentId { get; set; } = string.Empty;
|
||||
public string Question { get; set; } = string.Empty;
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
using BotSharp.Abstraction.Graph.Models;
|
||||
using BotSharp.Abstraction.VectorStorage.Models;
|
||||
|
||||
namespace BotSharp.Abstraction.Knowledges.Models;
|
||||
|
||||
public class KnowledgeSearchResult
|
||||
{
|
||||
public IEnumerable<VectorSearchResult> VectorResult { get; set; }
|
||||
public GraphSearchResult GraphResult { get; set; }
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BotSharp.Abstraction.Knowledges.Models;
|
||||
|
||||
public class RetrievedResult
|
||||
{
|
||||
public int Paragraph { get; set; }
|
||||
|
||||
[JsonPropertyName("cite_source")]
|
||||
public string CiteSource { get; set; } = "related text";
|
||||
|
||||
[JsonPropertyName("reasoning")]
|
||||
public string Reasoning { get; set; } = "";
|
||||
}
|
||||
|
|
@ -1,9 +1,17 @@
|
|||
using BotSharp.Abstraction.Knowledges.Enums;
|
||||
|
||||
namespace BotSharp.Abstraction.Knowledges.Settings;
|
||||
|
||||
public class KnowledgeBaseSettings
|
||||
{
|
||||
public string DefaultCollection { get; set; } = KnowledgeCollectionName.BotSharp;
|
||||
public string VectorDb { get; set; }
|
||||
public string TextEmbedding { get; set; }
|
||||
public string TextCompletion { get; set; }
|
||||
public string Pdf2TextConverter { get; set; }
|
||||
public string GraphDb { get; set; }
|
||||
public KnowledgeModelSetting TextEmbedding { get; set; }
|
||||
}
|
||||
|
||||
public class KnowledgeModelSetting
|
||||
{
|
||||
public string Provider { get; set; }
|
||||
public string Model { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
namespace BotSharp.Abstraction.MLTasks
|
||||
{
|
||||
public interface ITextToSpeech
|
||||
{
|
||||
/// <summary>
|
||||
/// The LLM provider like Microsoft Azure, OpenAI, ClaudAI
|
||||
/// </summary>
|
||||
string Provider { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Set model name, one provider can consume different model or version(s)
|
||||
/// </summary>
|
||||
/// <param name="model">deployment name</param>
|
||||
void SetModelName(string model);
|
||||
|
||||
Task<BinaryData> GenerateSpeechFromTextAsync(string text, ITextToSpeechOptions? options = null);
|
||||
}
|
||||
|
||||
public interface ITextToSpeechOptions
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -17,4 +17,5 @@ global using BotSharp.Abstraction.Templating;
|
|||
global using BotSharp.Abstraction.Translation.Attributes;
|
||||
global using BotSharp.Abstraction.Messaging.Enums;
|
||||
global using BotSharp.Abstraction.Files.Models;
|
||||
global using BotSharp.Abstraction.Files.Enums;
|
||||
global using BotSharp.Abstraction.Files.Enums;
|
||||
global using BotSharp.Abstraction.Knowledges.Models;
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
namespace BotSharp.Abstraction.Utilities;
|
||||
|
||||
public class StringIdPagination : Pagination
|
||||
{
|
||||
[JsonPropertyName("start_id")]
|
||||
public string? StartId { get; set; }
|
||||
}
|
||||
|
||||
public class StringIdPagedItems<T> : PagedItems<T>
|
||||
{
|
||||
public new ulong Count { get; set; }
|
||||
|
||||
[JsonPropertyName("next_id")]
|
||||
public string? NextId { get; set; }
|
||||
}
|
||||
|
|
@ -1,9 +1,15 @@
|
|||
using BotSharp.Abstraction.VectorStorage.Models;
|
||||
|
||||
namespace BotSharp.Abstraction.VectorStorage;
|
||||
|
||||
public interface IVectorDb
|
||||
{
|
||||
Task<List<string>> GetCollections();
|
||||
string Name { get; }
|
||||
|
||||
Task<IEnumerable<string>> GetCollections();
|
||||
Task<StringIdPagedItems<VectorCollectionData>> GetCollectionData(string collectionName, VectorFilter filter);
|
||||
Task CreateCollection(string collectionName, int dim);
|
||||
Task<bool> Upsert(string collectionName, string id, float[] vector, string text, Dictionary<string, string>? payload = null);
|
||||
Task<List<string>> Search(string collectionName, float[] vector, string returnFieldName, int limit = 5, float confidence = 0.5f);
|
||||
Task<IEnumerable<VectorCollectionData>> Search(string collectionName, float[] vector, IEnumerable<string>? fields, int limit = 5, float confidence = 0.5f, bool withVector = false);
|
||||
Task<bool> DeleteCollectionData(string collectionName, string id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
namespace BotSharp.Abstraction.VectorStorage.Models;
|
||||
|
||||
public class VectorCollectionData
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public Dictionary<string, string> Data { get; set; } = new();
|
||||
public double? Score { get; set; }
|
||||
public float[]? Vector { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
namespace BotSharp.Abstraction.VectorStorage.Models;
|
||||
|
||||
public class VectorFilter : StringIdPagination
|
||||
{
|
||||
[JsonPropertyName("with_vector")]
|
||||
public bool WithVector { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
using BotSharp.Abstraction.Knowledges.Enums;
|
||||
|
||||
namespace BotSharp.Abstraction.VectorStorage.Models;
|
||||
|
||||
public class VectorSearchOptions
|
||||
{
|
||||
public IEnumerable<string>? Fields { get; set; } = new List<string> { KnowledgePayloadName.Text, KnowledgePayloadName.Answer };
|
||||
public int? Limit { get; set; } = 5;
|
||||
public float? Confidence { get; set; } = 0.5f;
|
||||
public bool WithVector { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
namespace BotSharp.Abstraction.VectorStorage.Models;
|
||||
|
||||
public class VectorSearchResult : VectorCollectionData
|
||||
{
|
||||
public VectorSearchResult()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public static VectorSearchResult CopyFrom(VectorCollectionData data)
|
||||
{
|
||||
return new VectorSearchResult
|
||||
{
|
||||
Id = data.Id,
|
||||
Data = data.Data,
|
||||
Score = data.Score,
|
||||
Vector = data.Vector
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -41,44 +41,52 @@ public partial class AgentService
|
|||
});
|
||||
|
||||
Utilities.ClearCache();
|
||||
|
||||
return await Task.FromResult(agentRecord);
|
||||
}
|
||||
|
||||
private Agent FetchAgentFileByName(string agentName, string filePath)
|
||||
private (string, List<ChannelInstruction>) GetInstructionsFromFile(string fileDir)
|
||||
{
|
||||
foreach (var dir in Directory.GetDirectories(filePath))
|
||||
var defaultInstruction = string.Empty;
|
||||
var channelInstructions = new List<ChannelInstruction>();
|
||||
|
||||
var instructionDir = Path.Combine(fileDir, "instructions");
|
||||
if (!Directory.Exists(instructionDir))
|
||||
{
|
||||
var agentJson = File.ReadAllText(Path.Combine(dir, "agent.json"));
|
||||
var agent = JsonSerializer.Deserialize<Agent>(agentJson, _options);
|
||||
if (agent != null && agent.Name.IsEqualTo(agentName))
|
||||
{
|
||||
var functions = FetchFunctionsFromFile(dir);
|
||||
var instruction = FetchInstructionFromFile(dir);
|
||||
var responses = FetchResponsesFromFile(dir);
|
||||
var templates = FetchTemplatesFromFile(dir);
|
||||
var samples = FetchSamplesFromFile(dir);
|
||||
return agent.SetInstruction(instruction)
|
||||
.SetTemplates(templates)
|
||||
.SetFunctions(functions)
|
||||
.SetResponses(responses)
|
||||
.SetSamples(samples);
|
||||
}
|
||||
return (defaultInstruction, channelInstructions);
|
||||
}
|
||||
|
||||
return null;
|
||||
foreach (var file in Directory.GetFiles(instructionDir))
|
||||
{
|
||||
var extension = Path.GetExtension(file).Substring(1);
|
||||
if (!extension.IsEqualTo(_agentSettings.TemplateFormat))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var segments = Path.GetFileName(file).Split(".", StringSplitOptions.RemoveEmptyEntries);
|
||||
if (segments.IsNullOrEmpty() || !segments[0].IsEqualTo("instruction"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (segments.Length == 2)
|
||||
{
|
||||
defaultInstruction = File.ReadAllText(file);
|
||||
}
|
||||
else if (segments.Length == 3)
|
||||
{
|
||||
var item = new ChannelInstruction
|
||||
{
|
||||
Channel = segments[1],
|
||||
Instruction = File.ReadAllText(file)
|
||||
};
|
||||
channelInstructions.Add(item);
|
||||
}
|
||||
}
|
||||
return (defaultInstruction, channelInstructions);
|
||||
}
|
||||
|
||||
private string FetchInstructionFromFile(string fileDir)
|
||||
{
|
||||
var file = Path.Combine(fileDir, $"instruction.{_agentSettings.TemplateFormat}");
|
||||
if (!File.Exists(file)) return null;
|
||||
|
||||
var instruction = File.ReadAllText(file);
|
||||
return instruction;
|
||||
}
|
||||
|
||||
private List<AgentTemplate> FetchTemplatesFromFile(string fileDir)
|
||||
private List<AgentTemplate> GetTemplatesFromFile(string fileDir)
|
||||
{
|
||||
var templates = new List<AgentTemplate>();
|
||||
var templateDir = Path.Combine(fileDir, "templates");
|
||||
|
|
@ -86,10 +94,10 @@ public partial class AgentService
|
|||
|
||||
foreach (var file in Directory.GetFiles(templateDir))
|
||||
{
|
||||
var name = Path.GetFileNameWithoutExtension(file);
|
||||
var extension = Path.GetExtension(file).Substring(1);
|
||||
if (extension.IsEqualTo(_agentSettings.TemplateFormat))
|
||||
{
|
||||
var name = Path.GetFileNameWithoutExtension(file);
|
||||
var content = File.ReadAllText(file);
|
||||
templates.Add(new AgentTemplate(name, content));
|
||||
}
|
||||
|
|
@ -98,7 +106,7 @@ public partial class AgentService
|
|||
return templates;
|
||||
}
|
||||
|
||||
private List<FunctionDef> FetchFunctionsFromFile(string fileDir)
|
||||
private List<FunctionDef> GetFunctionsFromFile(string fileDir)
|
||||
{
|
||||
var functions = new List<FunctionDef>();
|
||||
var functionDir = Path.Combine(fileDir, "functions");
|
||||
|
|
@ -125,7 +133,7 @@ public partial class AgentService
|
|||
return functions;
|
||||
}
|
||||
|
||||
private List<AgentResponse> FetchResponsesFromFile(string fileDir)
|
||||
private List<AgentResponse> GetResponsesFromFile(string fileDir)
|
||||
{
|
||||
var responses = new List<AgentResponse>();
|
||||
var responseDir = Path.Combine(fileDir, "responses");
|
||||
|
|
@ -143,7 +151,7 @@ public partial class AgentService
|
|||
return responses;
|
||||
}
|
||||
|
||||
private List<string> FetchSamplesFromFile(string fileDir)
|
||||
private List<string> GetSamplesFromFile(string fileDir)
|
||||
{
|
||||
var file = Path.Combine(fileDir, "samples.txt");
|
||||
if (!File.Exists(file)) return new List<string>();
|
||||
|
|
@ -152,7 +160,7 @@ public partial class AgentService
|
|||
return samples?.ToList() ?? new List<string>();
|
||||
}
|
||||
|
||||
private List<AgentTask> FetchTasksFromFile(string fileDir)
|
||||
private List<AgentTask> GetTasksFromFile(string fileDir)
|
||||
{
|
||||
var tasks = new List<AgentTask>();
|
||||
var taskDir = Path.Combine(fileDir, "tasks");
|
||||
|
|
|
|||
|
|
@ -53,7 +53,29 @@ public partial class AgentService
|
|||
}
|
||||
|
||||
profile.Plugin = GetPlugin(profile.Id);
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
public async Task InheritAgent(Agent agent)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(agent?.InheritAgentId)) return;
|
||||
|
||||
var inheritedAgent = await GetAgent(agent.InheritAgentId);
|
||||
agent.Templates.AddRange(inheritedAgent.Templates
|
||||
// exclude private template
|
||||
.Where(x => !x.Name.StartsWith("."))
|
||||
// exclude duplicate name
|
||||
.Where(x => !agent.Templates.Exists(t => t.Name == x.Name)));
|
||||
|
||||
agent.Functions.AddRange(inheritedAgent.Functions
|
||||
// exclude private template
|
||||
.Where(x => !x.Name.StartsWith("."))
|
||||
// exclude duplicate name
|
||||
.Where(x => !agent.Functions.Exists(t => t.Name == x.Name)));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(agent.Instruction))
|
||||
{
|
||||
agent.Instruction = inheritedAgent.Instruction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
using BotSharp.Abstraction.Routing.Models;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace BotSharp.Core.Agents.Services;
|
||||
|
||||
public partial class AgentService
|
||||
{
|
||||
public static ConcurrentDictionary<string, Dictionary<string,string>> AgentParameterTypes = new();
|
||||
|
||||
[MemoryCache(10 * 60, perInstanceCache: true)]
|
||||
public async Task<Agent> LoadAgent(string id)
|
||||
{
|
||||
|
|
@ -29,30 +34,12 @@ public partial class AgentService
|
|||
return null;
|
||||
}
|
||||
|
||||
if (agent.InheritAgentId != null)
|
||||
{
|
||||
var inheritedAgent = await GetAgent(agent.InheritAgentId);
|
||||
agent.Templates.AddRange(inheritedAgent.Templates
|
||||
// exclude private template
|
||||
.Where(x => !x.Name.StartsWith("."))
|
||||
// exclude duplicate name
|
||||
.Where(x => !agent.Templates.Exists(t => t.Name == x.Name)));
|
||||
|
||||
agent.Functions.AddRange(inheritedAgent.Functions
|
||||
// exclude private template
|
||||
.Where(x => !x.Name.StartsWith("."))
|
||||
// exclude duplicate name
|
||||
.Where(x => !agent.Functions.Exists(t => t.Name == x.Name)));
|
||||
|
||||
if (agent.Instruction == null)
|
||||
{
|
||||
agent.Instruction = inheritedAgent.Instruction;
|
||||
}
|
||||
}
|
||||
|
||||
agent.TemplateDict = new Dictionary<string, object>();
|
||||
await InheritAgent(agent);
|
||||
OverrideInstructionByChannel(agent);
|
||||
AddOrUpdateParameters(agent);
|
||||
|
||||
// Populate state into dictionary
|
||||
agent.TemplateDict = new Dictionary<string, object>();
|
||||
PopulateState(agent.TemplateDict);
|
||||
|
||||
// After agent is loaded
|
||||
|
|
@ -88,6 +75,23 @@ public partial class AgentService
|
|||
return agent;
|
||||
}
|
||||
|
||||
private void OverrideInstructionByChannel(Agent agent)
|
||||
{
|
||||
var instructions = agent.ChannelInstructions;
|
||||
if (instructions.IsNullOrEmpty()) return;
|
||||
|
||||
var state = _services.GetRequiredService<IConversationStateService>();
|
||||
var channel = state.GetState("channel");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(channel))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var found = instructions.FirstOrDefault(x => x.Channel.IsEqualTo(channel));
|
||||
agent.Instruction = !string.IsNullOrWhiteSpace(found?.Instruction) ? found.Instruction : agent.Instruction;
|
||||
}
|
||||
|
||||
private void PopulateState(Dictionary<string, object> dict)
|
||||
{
|
||||
var conv = _services.GetRequiredService<IConversationService>();
|
||||
|
|
@ -96,4 +100,53 @@ public partial class AgentService
|
|||
dict[t.Key] = t.Value;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddOrUpdateParameters(Agent agent)
|
||||
{
|
||||
var agentId = agent.Id ?? agent.Name;
|
||||
if (AgentParameterTypes.ContainsKey(agentId)) return;
|
||||
|
||||
AddOrUpdateRoutesParameters(agentId, agent.RoutingRules);
|
||||
AddOrUpdateFunctionsParameters(agentId, agent.Functions);
|
||||
}
|
||||
|
||||
private void AddOrUpdateRoutesParameters(string agentId, List<RoutingRule> routingRules)
|
||||
{
|
||||
if(!AgentParameterTypes.TryGetValue(agentId, out var parameterTypes))
|
||||
{
|
||||
parameterTypes = new();
|
||||
}
|
||||
|
||||
foreach (var rule in routingRules.Where(x => x.Required))
|
||||
{
|
||||
if (string.IsNullOrEmpty(rule.FieldType)) continue;
|
||||
parameterTypes.TryAdd(rule.Field, rule.FieldType);
|
||||
}
|
||||
|
||||
AgentParameterTypes.TryAdd(agentId, parameterTypes);
|
||||
}
|
||||
|
||||
private void AddOrUpdateFunctionsParameters(string agentId, List<FunctionDef> functions)
|
||||
{
|
||||
if (!AgentParameterTypes.TryGetValue(agentId, out var parameterTypes))
|
||||
{
|
||||
parameterTypes = new();
|
||||
}
|
||||
|
||||
var parameters = functions.Select(p => p.Parameters);
|
||||
foreach (var param in parameters)
|
||||
{
|
||||
foreach (JsonProperty prop in param.Properties.RootElement.EnumerateObject())
|
||||
{
|
||||
var name = prop.Name;
|
||||
var node = prop.Value;
|
||||
if (node.TryGetProperty("type", out var type))
|
||||
{
|
||||
parameterTypes.TryAdd(name, type.GetString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AgentParameterTypes.TryAdd(agentId, parameterTypes);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,19 +42,20 @@ public partial class AgentService
|
|||
continue;
|
||||
}
|
||||
|
||||
var functions = FetchFunctionsFromFile(dir);
|
||||
var instruction = FetchInstructionFromFile(dir);
|
||||
var responses = FetchResponsesFromFile(dir);
|
||||
var templates = FetchTemplatesFromFile(dir);
|
||||
var samples = FetchSamplesFromFile(dir);
|
||||
agent.SetInstruction(instruction)
|
||||
var (defaultInstruction, channelInstructions) = GetInstructionsFromFile(dir);
|
||||
var functions = GetFunctionsFromFile(dir);
|
||||
var responses = GetResponsesFromFile(dir);
|
||||
var templates = GetTemplatesFromFile(dir);
|
||||
var samples = GetSamplesFromFile(dir);
|
||||
agent.SetInstruction(defaultInstruction)
|
||||
.SetChannelInstructions(channelInstructions)
|
||||
.SetTemplates(templates)
|
||||
.SetFunctions(functions)
|
||||
.SetResponses(responses)
|
||||
.SetSamples(samples);
|
||||
|
||||
var userAgent = BuildUserAgent(agent.Id, user.Id);
|
||||
var tasks = FetchTasksFromFile(dir);
|
||||
var tasks = GetTasksFromFile(dir);
|
||||
|
||||
var isAgentDeleted = _db.DeleteAgent(agent.Id);
|
||||
if (isAgentDeleted)
|
||||
|
|
|
|||
|
|
@ -20,17 +20,32 @@ public partial class AgentService
|
|||
|
||||
public bool RenderFunction(Agent agent, FunctionDef def)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(def.VisibilityExpression))
|
||||
var isRender = true;
|
||||
|
||||
var channels = def.Channels;
|
||||
if (channels != null)
|
||||
{
|
||||
var state = _services.GetRequiredService<IConversationStateService>();
|
||||
var channel = state.GetState("channel");
|
||||
if (!string.IsNullOrWhiteSpace(channel))
|
||||
{
|
||||
isRender = isRender && channels.Contains(channel);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isRender) return false;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(def.VisibilityExpression))
|
||||
{
|
||||
var render = _services.GetRequiredService<ITemplateRender>();
|
||||
var result = render.Render(def.VisibilityExpression, new Dictionary<string, object>
|
||||
{
|
||||
{ "states", agent.TemplateDict }
|
||||
});
|
||||
return result == "visible";
|
||||
isRender = isRender && result == "visible";
|
||||
}
|
||||
|
||||
return true;
|
||||
return isRender;
|
||||
}
|
||||
|
||||
public FunctionParametersDef? RenderFunctionProperty(Agent agent, FunctionDef def)
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ public partial class AgentService
|
|||
record.Profiles = agent.Profiles ?? new List<string>();
|
||||
record.RoutingRules = agent.RoutingRules ?? new List<RoutingRule>();
|
||||
record.Instruction = agent.Instruction ?? string.Empty;
|
||||
record.ChannelInstructions = agent.ChannelInstructions ?? new List<ChannelInstruction>();
|
||||
record.Functions = agent.Functions ?? new List<FunctionDef>();
|
||||
record.Templates = agent.Templates ?? new List<AgentTemplate>();
|
||||
record.Responses = agent.Responses ?? new List<AgentResponse>();
|
||||
|
|
@ -41,7 +42,6 @@ public partial class AgentService
|
|||
_db.UpdateAgent(record, updateField);
|
||||
|
||||
Utilities.ClearCache();
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
|
@ -71,7 +71,7 @@ public partial class AgentService
|
|||
agentSettings.DataDir);
|
||||
|
||||
var clonedAgent = Agent.Clone(agent);
|
||||
var foundAgent = FetchAgentFileById(agent.Id, filePath);
|
||||
var foundAgent = GetAgentFileById(agent.Id, filePath);
|
||||
if (foundAgent == null)
|
||||
{
|
||||
updateResult = $"Cannot find agent {agent.Name} in file directory: {filePath}";
|
||||
|
|
@ -90,6 +90,7 @@ public partial class AgentService
|
|||
.SetProfiles(foundAgent.Profiles)
|
||||
.SetRoutingRules(foundAgent.RoutingRules)
|
||||
.SetInstruction(foundAgent.Instruction)
|
||||
.SetChannelInstructions(foundAgent.ChannelInstructions)
|
||||
.SetTemplates(foundAgent.Templates)
|
||||
.SetFunctions(foundAgent.Functions)
|
||||
.SetResponses(foundAgent.Responses)
|
||||
|
|
@ -165,7 +166,7 @@ public partial class AgentService
|
|||
return patchResult;
|
||||
}
|
||||
|
||||
private Agent? FetchAgentFileById(string agentId, string filePath)
|
||||
private Agent? GetAgentFileById(string agentId, string filePath)
|
||||
{
|
||||
if (!Directory.Exists(filePath)) return null;
|
||||
|
||||
|
|
@ -175,12 +176,13 @@ public partial class AgentService
|
|||
var agent = JsonSerializer.Deserialize<Agent>(agentJson, _options);
|
||||
if (agent != null && agent.Id == agentId)
|
||||
{
|
||||
var functions = FetchFunctionsFromFile(dir);
|
||||
var instruction = FetchInstructionFromFile(dir);
|
||||
var responses = FetchResponsesFromFile(dir);
|
||||
var templates = FetchTemplatesFromFile(dir);
|
||||
var samples = FetchSamplesFromFile(dir);
|
||||
return agent.SetInstruction(instruction)
|
||||
var (defaultInstruction, channelInstructions) = GetInstructionsFromFile(dir);
|
||||
var functions = GetFunctionsFromFile(dir);
|
||||
var responses = GetResponsesFromFile(dir);
|
||||
var templates = GetTemplatesFromFile(dir);
|
||||
var samples = GetSamplesFromFile(dir);
|
||||
return agent.SetInstruction(defaultInstruction)
|
||||
.SetChannelInstructions(channelInstructions)
|
||||
.SetTemplates(templates)
|
||||
.SetFunctions(functions)
|
||||
.SetResponses(responses)
|
||||
|
|
|
|||
|
|
@ -45,19 +45,28 @@
|
|||
<NoWarn>1701;1702</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Planning\**" />
|
||||
<Compile Remove="Translation\Models\**" />
|
||||
<EmbeddedResource Remove="Planning\**" />
|
||||
<EmbeddedResource Remove="Translation\Models\**" />
|
||||
<None Remove="Planning\**" />
|
||||
<None Remove="Translation\Models\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\agent.json" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\instruction.liquid" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\instructions\instruction.liquid" />
|
||||
<None Remove="data\agents\01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b\agent.json" />
|
||||
<None Remove="data\agents\01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b\functions.json" />
|
||||
<None Remove="data\agents\01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b\functions\human_intervention_needed.json" />
|
||||
<None Remove="data\agents\01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b\instruction.liquid" />
|
||||
<None Remove="data\agents\01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b\instructions\instruction.liquid" />
|
||||
<None Remove="data\agents\01fcc3e5-0af7-49e6-ad7a-a760bd12dc4d\agent.json" />
|
||||
<None Remove="data\agents\01fcc3e5-0af7-49e6-ad7a-a760bd12dc4d\instruction.liquid" />
|
||||
<None Remove="data\agents\01fcc3e5-0af7-49e6-ad7a-a760bd12dc4d\instructions\instruction.liquid" />
|
||||
<None Remove="data\agents\01e2fc5c-2c89-4ec7-8470-7688608b496c\agent.json" />
|
||||
<None Remove="data\agents\01e2fc5c-2c89-4ec7-8470-7688608b496c\instruction.liquid" />
|
||||
<None Remove="data\agents\01e2fc5c-2c89-4ec7-8470-7688608b496c\instructions\instruction.liquid" />
|
||||
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\agent.json" />
|
||||
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\instruction.liquid" />
|
||||
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\instructions\instruction.liquid" />
|
||||
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\.welcome.liquid" />
|
||||
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\conversation.summary.liquid" />
|
||||
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.hf.liquid" />
|
||||
|
|
@ -69,8 +78,9 @@
|
|||
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.two_stage.2nd.task.liquid" />
|
||||
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\response_with_function.liquid" />
|
||||
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\translation_prompt.liquid" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\select_file_prompt.liquid" />
|
||||
<None Remove="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\agent.json" />
|
||||
<None Remove="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\instruction.liquid" />
|
||||
<None Remove="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\instructions\instruction.liquid" />
|
||||
<None Remove="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\templates\instruction.executor.liquid" />
|
||||
<None Remove="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\templates\instruction.reviewer.liquid" />
|
||||
<None Remove="data\plugins\config.json" />
|
||||
|
|
@ -80,7 +90,7 @@
|
|||
<Content Include="data\agents\01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b\agent.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b\instruction.liquid">
|
||||
<Content Include="data\agents\01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b\instructions\instruction.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b\functions\human_intervention_needed.json">
|
||||
|
|
@ -89,19 +99,19 @@
|
|||
<Content Include="data\agents\01fcc3e5-0af7-49e6-ad7a-a760bd12dc4d\agent.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\01fcc3e5-0af7-49e6-ad7a-a760bd12dc4d\instruction.liquid">
|
||||
<Content Include="data\agents\01fcc3e5-0af7-49e6-ad7a-a760bd12dc4d\instructions\instruction.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\01e2fc5c-2c89-4ec7-8470-7688608b496c\agent.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\01e2fc5c-2c89-4ec7-8470-7688608b496c\instruction.liquid">
|
||||
<Content Include="data\agents\01e2fc5c-2c89-4ec7-8470-7688608b496c\instructions\instruction.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\agent.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\instruction.liquid">
|
||||
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\instructions\instruction.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.two_stage.1st.plan.liquid">
|
||||
|
|
@ -137,7 +147,7 @@
|
|||
<Content Include="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\agent.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\instruction.liquid">
|
||||
<Content Include="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\instructions\instruction.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\templates\instruction.executor.liquid">
|
||||
|
|
@ -152,7 +162,10 @@
|
|||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\agent.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\instruction.liquid">
|
||||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\instructions\instruction.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\select_file_prompt.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\plugins\config.json">
|
||||
|
|
@ -172,7 +185,6 @@
|
|||
<PackageReference Include="DistributedLock.Redis" Version="1.0.3" />
|
||||
<PackageReference Include="EntityFrameworkCore.BootKit" Version="8.5.1" />
|
||||
<PackageReference Include="Fluid.Core" Version="2.11.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
<PackageReference Include="Nanoid" Version="3.1.0" />
|
||||
</ItemGroup>
|
||||
|
|
@ -181,9 +193,4 @@
|
|||
<ProjectReference Include="..\BotSharp.Abstraction\BotSharp.Abstraction.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Planning\" />
|
||||
<Folder Include="Translation\Models\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -150,6 +150,7 @@ public partial class ConversationService
|
|||
await HookEmitter.Emit<IConversationHook>(_services, async hook =>
|
||||
await hook.OnConversationEnding(response)
|
||||
);
|
||||
response.FunctionName = "conversation_end";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,10 +5,9 @@ public partial class ConversationService : IConversationService
|
|||
public async Task<bool> TruncateConversation(string conversationId, string messageId, string? newMessageId = null)
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var fileService = _services.GetRequiredService<IBotSharpFileService>();
|
||||
var fileStorage = _services.GetRequiredService<IFileStorageService>();
|
||||
var deleteMessageIds = db.TruncateConversation(conversationId, messageId, cleanLog: true);
|
||||
|
||||
fileService.DeleteMessageFiles(conversationId, deleteMessageIds, messageId, newMessageId);
|
||||
fileStorage.DeleteMessageFiles(conversationId, deleteMessageIds, messageId, newMessageId);
|
||||
|
||||
var hooks = _services.GetServices<IConversationHook>().ToList();
|
||||
foreach (var hook in hooks)
|
||||
|
|
|
|||
|
|
@ -37,9 +37,9 @@ public partial class ConversationService : IConversationService
|
|||
public async Task<bool> DeleteConversations(IEnumerable<string> ids)
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var fileService = _services.GetRequiredService<IBotSharpFileService>();
|
||||
var fileStorage = _services.GetRequiredService<IFileStorageService>();
|
||||
var isDeleted = db.DeleteConversations(ids);
|
||||
fileService.DeleteConversationFiles(ids);
|
||||
fileStorage.DeleteConversationFiles(ids);
|
||||
return await Task.FromResult(isDeleted);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -360,9 +360,28 @@ public class ConversationStateService : IConversationStateService, IDisposable
|
|||
stateValue = stateValue?.ToLower();
|
||||
}
|
||||
|
||||
SetState(property.Name, stateValue, source: StateSource.Application);
|
||||
if (CheckArgType(property.Name, stateValue))
|
||||
{
|
||||
SetState(property.Name, stateValue, source: StateSource.Application);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckArgType(string name, string value)
|
||||
{
|
||||
var agentTypes = AgentService.AgentParameterTypes.SelectMany(p => p.Value).ToList();
|
||||
var filed = agentTypes.FirstOrDefault(t => t.Key == name);
|
||||
if (filed.Key != null)
|
||||
{
|
||||
return filed.Value switch
|
||||
{
|
||||
"boolean" => bool.TryParse(value, out _),
|
||||
"number" => long.TryParse(value, out _),
|
||||
_ => true,
|
||||
};
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
28
src/Infrastructure/BotSharp.Core/Files/FileCorePlugin.cs
Normal file
28
src/Infrastructure/BotSharp.Core/Files/FileCorePlugin.cs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
using BotSharp.Abstraction.Repositories.Enums;
|
||||
using BotSharp.Core.Files.Services;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace BotSharp.Core.Files;
|
||||
|
||||
public class FileCorePlugin : IBotSharpPlugin
|
||||
{
|
||||
public string Id => "6a8473c0-04eb-4346-be32-24755ce5973d";
|
||||
|
||||
public string Name => "File Core";
|
||||
|
||||
public string Description => "Provides file storage and analysis.";
|
||||
|
||||
|
||||
public void RegisterDI(IServiceCollection services, IConfiguration config)
|
||||
{
|
||||
var fileCoreSettings = new FileCoreSettings();
|
||||
config.Bind("FileCore", fileCoreSettings);
|
||||
services.AddSingleton(fileCoreSettings);
|
||||
|
||||
if (fileCoreSettings.Storage == FileStorageEnum.LocalFileStorage)
|
||||
{
|
||||
services.AddScoped<IFileStorageService, LocalFileStorageService>();
|
||||
}
|
||||
services.AddScoped<IFileInstructService, FileInstructService>();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
using BotSharp.Abstraction.Repositories.Enums;
|
||||
using BotSharp.Core.Files.Services;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace BotSharp.Core.Files;
|
||||
|
||||
public class FilePlugin : IBotSharpPlugin
|
||||
{
|
||||
public string Id => "6a8473c0-04eb-4346-be32-24755ce5973d";
|
||||
|
||||
public string Name => "File";
|
||||
|
||||
public string Description => "Provides file analysis.";
|
||||
|
||||
|
||||
public void RegisterDI(IServiceCollection services, IConfiguration config)
|
||||
{
|
||||
var myFileStorageSettings = new FileStorageSettings();
|
||||
config.Bind("FileStorage", myFileStorageSettings);
|
||||
|
||||
if (myFileStorageSettings.Default == FileStorageEnum.LocalFileStorage)
|
||||
{
|
||||
services.AddScoped<IBotSharpFileService, BotSharpFileService>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
using Microsoft.AspNetCore.StaticFiles;
|
||||
using System.IO;
|
||||
|
||||
namespace BotSharp.Core.Files.Services;
|
||||
|
||||
public partial class BotSharpFileService
|
||||
{
|
||||
public string GetDirectory(string conversationId)
|
||||
{
|
||||
var dir = Path.Combine(_dbSettings.FileRepository, CONVERSATION_FOLDER, conversationId, "attachments");
|
||||
if (!Directory.Exists(dir))
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
public (string, byte[]) GetFileInfoFromData(string data)
|
||||
{
|
||||
if (string.IsNullOrEmpty(data))
|
||||
{
|
||||
return (string.Empty, new byte[0]);
|
||||
}
|
||||
|
||||
var typeStartIdx = data.IndexOf(':');
|
||||
var typeEndIdx = data.IndexOf(';');
|
||||
var contentType = data.Substring(typeStartIdx + 1, typeEndIdx - typeStartIdx - 1);
|
||||
|
||||
var base64startIdx = data.IndexOf(',');
|
||||
var base64Str = data.Substring(base64startIdx + 1);
|
||||
|
||||
return (contentType, Convert.FromBase64String(base64Str));
|
||||
}
|
||||
|
||||
public string GetFileContentType(string filePath)
|
||||
{
|
||||
string contentType;
|
||||
var provider = new FileExtensionContentTypeProvider();
|
||||
if (!provider.TryGetContentType(filePath, out contentType))
|
||||
{
|
||||
contentType = string.Empty;
|
||||
}
|
||||
|
||||
return contentType;
|
||||
}
|
||||
|
||||
public byte[] GetFileBytes(string fileStorageUrl)
|
||||
{
|
||||
using var stream = File.OpenRead(fileStorageUrl);
|
||||
var bytes = new byte[stream.Length];
|
||||
stream.Read(bytes, 0, (int)stream.Length);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public bool SavefileToPath(string filePath, Stream stream)
|
||||
{
|
||||
using (var fileStream = new FileStream(filePath, FileMode.Create))
|
||||
{
|
||||
stream.CopyTo(fileStream);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,143 +0,0 @@
|
|||
using System.IO;
|
||||
|
||||
namespace BotSharp.Core.Files.Services;
|
||||
|
||||
public partial class BotSharpFileService
|
||||
{
|
||||
public async Task<string> ReadPdf(string? provider, string? model, string? modelId, string prompt, List<BotSharpFile> files)
|
||||
{
|
||||
var content = string.Empty;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(prompt) || files.IsNullOrEmpty())
|
||||
{
|
||||
return content;
|
||||
}
|
||||
|
||||
var guid = Guid.NewGuid().ToString();
|
||||
var sessionDir = GetSessionDirectory(guid);
|
||||
if (!ExistDirectory(sessionDir))
|
||||
{
|
||||
Directory.CreateDirectory(sessionDir);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var pdfFiles = await DownloadFiles(sessionDir, files);
|
||||
var images = await ConvertPdfToImages(pdfFiles);
|
||||
if (images.IsNullOrEmpty()) return content;
|
||||
|
||||
var completion = CompletionProvider.GetChatCompletion(_services, provider: provider ?? "openai",
|
||||
model: model, modelId: modelId ?? "gpt-4", multiModal: true);
|
||||
var message = await completion.GetChatCompletions(new Agent()
|
||||
{
|
||||
Id = Guid.Empty.ToString(),
|
||||
}, new List<RoleDialogModel>
|
||||
{
|
||||
new RoleDialogModel(AgentRole.User, prompt)
|
||||
{
|
||||
Files = images.Select(x => new BotSharpFile { FileStorageUrl = x }).ToList()
|
||||
}
|
||||
});
|
||||
|
||||
content = message.Content;
|
||||
return content;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"Error when analyzing pdf in file service: {ex.Message}\r\n{ex.InnerException}");
|
||||
return content;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(sessionDir, true);
|
||||
}
|
||||
}
|
||||
|
||||
#region Private methods
|
||||
private string GetSessionDirectory(string id)
|
||||
{
|
||||
var dir = Path.Combine(_baseDir, SESSION_FOLDER, id);
|
||||
return dir;
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<string>> DownloadFiles(string dir, List<BotSharpFile> files, string extension = "pdf")
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(dir) || files.IsNullOrEmpty())
|
||||
{
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
var locs = new List<string>();
|
||||
foreach (var file in files)
|
||||
{
|
||||
try
|
||||
{
|
||||
var bytes = new byte[0];
|
||||
if (!string.IsNullOrEmpty(file.FileUrl))
|
||||
{
|
||||
var http = _services.GetRequiredService<IHttpClientFactory>();
|
||||
using var client = http.CreateClient();
|
||||
bytes = await client.GetByteArrayAsync(file.FileUrl);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(file.FileData))
|
||||
{
|
||||
(_, bytes) = GetFileInfoFromData(file.FileData);
|
||||
}
|
||||
|
||||
if (!bytes.IsNullOrEmpty())
|
||||
{
|
||||
var guid = Guid.NewGuid().ToString();
|
||||
var fileDir = Path.Combine(dir, guid);
|
||||
if (!ExistDirectory(fileDir))
|
||||
{
|
||||
Directory.CreateDirectory(fileDir);
|
||||
}
|
||||
|
||||
var pdfDir = Path.Combine(fileDir, $"{guid}.{extension}");
|
||||
using (var fs = new FileStream(pdfDir, FileMode.Create))
|
||||
{
|
||||
fs.Write(bytes, 0, bytes.Length);
|
||||
fs.Close();
|
||||
locs.Add(pdfDir);
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning($"Error when saving pdf file: {ex.Message}\r\n{ex.InnerException}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return locs;
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<string>> ConvertPdfToImages(IEnumerable<string> files)
|
||||
{
|
||||
var images = new List<string>();
|
||||
var converter = GetPdf2ImageConverter();
|
||||
if (converter == null || files.IsNullOrEmpty())
|
||||
{
|
||||
return images;
|
||||
}
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
try
|
||||
{
|
||||
var segs = file.Split(Path.DirectorySeparatorChar);
|
||||
var dir = string.Join(Path.DirectorySeparatorChar, segs.SkipLast(1));
|
||||
var folder = Path.Combine(dir, "screenshots");
|
||||
var urls = await converter.ConvertPdfToImages(file, folder);
|
||||
images.AddRange(urls);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning($"Error when converting pdf file to images ({file}): {ex.Message}\r\n{ex.InnerException}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return images;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -2,8 +2,24 @@ using System.IO;
|
|||
|
||||
namespace BotSharp.Core.Files.Services;
|
||||
|
||||
public partial class BotSharpFileService
|
||||
public partial class FileInstructService
|
||||
{
|
||||
public async Task<RoleDialogModel> ReadImages(string? provider, string? model, string text, IEnumerable<BotSharpFile> images)
|
||||
{
|
||||
var completion = CompletionProvider.GetChatCompletion(_services, provider: provider ?? "openai", model: model ?? "gpt-4o", multiModal: true);
|
||||
var message = await completion.GetChatCompletions(new Agent()
|
||||
{
|
||||
Id = Guid.Empty.ToString(),
|
||||
}, new List<RoleDialogModel>
|
||||
{
|
||||
new RoleDialogModel(AgentRole.User, text)
|
||||
{
|
||||
Files = images?.ToList() ?? new List<BotSharpFile>()
|
||||
}
|
||||
});
|
||||
return message;
|
||||
}
|
||||
|
||||
public async Task<RoleDialogModel> GenerateImage(string? provider, string? model, string text)
|
||||
{
|
||||
var completion = CompletionProvider.GetImageCompletion(_services, provider: provider ?? "openai", model: model ?? "dall-e-3");
|
||||
|
|
@ -31,7 +47,7 @@ public partial class BotSharpFileService
|
|||
{
|
||||
Id = Guid.Empty.ToString()
|
||||
}, new RoleDialogModel(AgentRole.User, string.Empty), stream, image.FileName ?? string.Empty);
|
||||
|
||||
|
||||
stream.Close();
|
||||
return message;
|
||||
}
|
||||
|
|
@ -53,7 +69,7 @@ public partial class BotSharpFileService
|
|||
{
|
||||
Id = Guid.Empty.ToString()
|
||||
}, new RoleDialogModel(AgentRole.User, text), stream, image.FileName ?? string.Empty);
|
||||
|
||||
|
||||
stream.Close();
|
||||
return message;
|
||||
}
|
||||
|
|
@ -82,7 +98,7 @@ public partial class BotSharpFileService
|
|||
{
|
||||
Id = Guid.Empty.ToString()
|
||||
}, new RoleDialogModel(AgentRole.User, text), imageStream, image.FileName ?? string.Empty, maskStream, mask.FileName ?? string.Empty);
|
||||
|
||||
|
||||
imageStream.Close();
|
||||
maskStream.Close();
|
||||
return message;
|
||||
|
|
@ -100,7 +116,7 @@ public partial class BotSharpFileService
|
|||
}
|
||||
else if (!string.IsNullOrEmpty(file.FileData))
|
||||
{
|
||||
(_, bytes) = GetFileInfoFromData(file.FileData);
|
||||
(_, bytes) = FileUtility.GetFileInfoFromData(file.FileData);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
namespace BotSharp.Plugin.TencentCos.Services;
|
||||
using BotSharp.Abstraction.Files.Converters;
|
||||
|
||||
public partial class TencentCosService
|
||||
namespace BotSharp.Core.Files.Services;
|
||||
|
||||
public partial class FileInstructService
|
||||
{
|
||||
public async Task<string> ReadPdf(string? provider, string? model, string? modelId, string prompt, List<BotSharpFile> files)
|
||||
{
|
||||
|
|
@ -12,7 +14,9 @@ public partial class TencentCosService
|
|||
}
|
||||
|
||||
var guid = Guid.NewGuid().ToString();
|
||||
var sessionDir = GetSessionDirectory(guid);
|
||||
|
||||
var sessionDir = _fileStorage.BuildDirectory(SESSION_FOLDER, guid);
|
||||
DeleteIfExistDirectory(sessionDir, true);
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -32,9 +36,7 @@ public partial class TencentCosService
|
|||
Files = images.Select(x => new BotSharpFile { FileStorageUrl = x }).ToList()
|
||||
}
|
||||
});
|
||||
|
||||
content = message.Content;
|
||||
return content;
|
||||
return message.Content;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -43,17 +45,11 @@ public partial class TencentCosService
|
|||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(sessionDir, true);
|
||||
_fileStorage.DeleteDirectory(sessionDir);
|
||||
}
|
||||
}
|
||||
|
||||
#region Private methods
|
||||
private string GetSessionDirectory(string id)
|
||||
{
|
||||
var dir = $"{SESSION_FOLDER}/{id}";
|
||||
return dir;
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<string>> DownloadFiles(string dir, List<BotSharpFile> files, string extension = "pdf")
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(dir) || files.IsNullOrEmpty())
|
||||
|
|
@ -75,18 +71,17 @@ public partial class TencentCosService
|
|||
}
|
||||
else if (!string.IsNullOrEmpty(file.FileData))
|
||||
{
|
||||
(_, bytes) = GetFileInfoFromData(file.FileData);
|
||||
(_, bytes) = FileUtility.GetFileInfoFromData(file.FileData);
|
||||
}
|
||||
|
||||
if (!bytes.IsNullOrEmpty())
|
||||
{
|
||||
var guid = Guid.NewGuid().ToString();
|
||||
var fileDir = $"{dir}/{guid}";
|
||||
var fileDir = _fileStorage.BuildDirectory(dir, guid);
|
||||
DeleteIfExistDirectory(fileDir, true);
|
||||
|
||||
var pdfDir = $"{fileDir}/{guid}.{extension}";
|
||||
|
||||
|
||||
_cosClient.BucketClient.UploadBytes(pdfDir, bytes);
|
||||
var pdfDir = _fileStorage.BuildDirectory(fileDir, $"{guid}.{extension}");
|
||||
_fileStorage.SaveFileBytesToPath(pdfDir, bytes);
|
||||
locs.Add(pdfDir);
|
||||
}
|
||||
}
|
||||
|
|
@ -102,7 +97,8 @@ public partial class TencentCosService
|
|||
private async Task<IEnumerable<string>> ConvertPdfToImages(IEnumerable<string> files)
|
||||
{
|
||||
var images = new List<string>();
|
||||
var converter = GetPdf2ImageConverter();
|
||||
var settings = _services.GetRequiredService<FileCoreSettings>();
|
||||
var converter = _services.GetServices<IPdf2ImageConverter>().FirstOrDefault(x => x.Name == settings.Pdf2ImageConverter);
|
||||
if (converter == null || files.IsNullOrEmpty())
|
||||
{
|
||||
return images;
|
||||
|
|
@ -112,9 +108,8 @@ public partial class TencentCosService
|
|||
{
|
||||
try
|
||||
{
|
||||
var segs = file.Split(Path.DirectorySeparatorChar);
|
||||
var dir = string.Join(Path.DirectorySeparatorChar, segs.SkipLast(1));
|
||||
var folder = Path.Combine(dir, "screenshots");
|
||||
var dir = _fileStorage.GetParentDir(file);
|
||||
var folder = _fileStorage.BuildDirectory(dir, "screenshots");
|
||||
var urls = await converter.ConvertPdfToImages(file, folder);
|
||||
images.AddRange(urls);
|
||||
}
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
using BotSharp.Abstraction.MLTasks;
|
||||
using BotSharp.Abstraction.Templating;
|
||||
|
||||
namespace BotSharp.Core.Files.Services;
|
||||
|
||||
public partial class FileInstructService
|
||||
{
|
||||
public async Task<IEnumerable<MessageFileModel>> SelectMessageFiles(string conversationId, SelectFileOptions options)
|
||||
{
|
||||
if (string.IsNullOrEmpty(conversationId))
|
||||
{
|
||||
return Enumerable.Empty<MessageFileModel>();
|
||||
}
|
||||
|
||||
var convService = _services.GetRequiredService<IConversationService>();
|
||||
var dialogs = convService.GetDialogHistory(fromBreakpoint: options.FromBreakpoint);
|
||||
var messageIds = GetMessageIds(dialogs, options.Offset);
|
||||
|
||||
var files = _fileStorage.GetMessageFiles(conversationId, messageIds, FileSourceType.User, options.ContentTypes);
|
||||
if (options.IncludeBotFile)
|
||||
{
|
||||
var botFiles = _fileStorage.GetMessageFiles(conversationId, messageIds, FileSourceType.Bot, options.ContentTypes);
|
||||
files = MergeMessageFiles(messageIds, files, botFiles);
|
||||
}
|
||||
|
||||
if (files.IsNullOrEmpty())
|
||||
{
|
||||
return Enumerable.Empty<MessageFileModel>();
|
||||
}
|
||||
|
||||
return await SelectFiles(files, dialogs, options);
|
||||
}
|
||||
|
||||
private IEnumerable<MessageFileModel> MergeMessageFiles(IEnumerable<string> messageIds, IEnumerable<MessageFileModel> userFiles, IEnumerable<MessageFileModel> botFiles)
|
||||
{
|
||||
var files = new List<MessageFileModel>();
|
||||
|
||||
if (messageIds.IsNullOrEmpty()) return files;
|
||||
|
||||
foreach (var messageId in messageIds)
|
||||
{
|
||||
var users = userFiles.Where(x => x.MessageId == messageId).ToList();
|
||||
var bots = botFiles.Where(x => x.MessageId == messageId).ToList();
|
||||
|
||||
if (!users.IsNullOrEmpty()) files.AddRange(users);
|
||||
if (!bots.IsNullOrEmpty()) files.AddRange(bots);
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<MessageFileModel>> SelectFiles(IEnumerable<MessageFileModel> files, IEnumerable<RoleDialogModel> dialogs, SelectFileOptions options)
|
||||
{
|
||||
if (files.IsNullOrEmpty()) return new List<MessageFileModel>();
|
||||
|
||||
var llmProviderService = _services.GetRequiredService<ILlmProviderService>();
|
||||
var render = _services.GetRequiredService<ITemplateRender>();
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
|
||||
try
|
||||
{
|
||||
var promptFiles = files.Select((x, idx) =>
|
||||
{
|
||||
return $"id: {idx + 1}, file_name: {x.FileName}.{x.FileExtension}, content_type: {x.ContentType}, author: {x.FileSource}";
|
||||
}).ToList();
|
||||
|
||||
var agentId = !string.IsNullOrWhiteSpace(options.AgentId) ? options.AgentId : BuiltInAgentId.UtilityAssistant;
|
||||
var template = !string.IsNullOrWhiteSpace(options.Template) ? options.Template : "select_file_prompt";
|
||||
|
||||
var foundAgent = db.GetAgent(agentId);
|
||||
var prompt = db.GetAgentTemplate(BuiltInAgentId.UtilityAssistant, template);
|
||||
prompt = render.Render(prompt, new Dictionary<string, object>
|
||||
{
|
||||
{ "file_list", promptFiles }
|
||||
});
|
||||
|
||||
var agent = new Agent
|
||||
{
|
||||
Id = foundAgent?.Id ?? BuiltInAgentId.UtilityAssistant,
|
||||
Name = foundAgent?.Name ?? "Utility Assistant",
|
||||
Instruction = prompt
|
||||
};
|
||||
|
||||
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";
|
||||
var modelId = options?.ModelId ?? "gpt-4";
|
||||
var provider = llmProviderService.GetProviders().FirstOrDefault(x => x == providerName);
|
||||
var model = llmProviderService.GetProviderModel(provider: provider, id: modelId);
|
||||
var completion = CompletionProvider.GetChatCompletion(_services, provider: provider, model: model.Name);
|
||||
|
||||
var response = await completion.GetChatCompletions(agent, new List<RoleDialogModel> { message });
|
||||
var content = response?.Content ?? string.Empty;
|
||||
var selecteds = JsonSerializer.Deserialize<FileSelectContext>(content, new JsonSerializerOptions
|
||||
{
|
||||
AllowTrailingCommas = true
|
||||
});
|
||||
var fids = selecteds?.Selecteds ?? new List<int>();
|
||||
return files.Where((x, idx) => fids.Contains(idx + 1)).ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning($"Error when selecting files. {ex.Message}\r\n{ex.InnerException}");
|
||||
return new List<MessageFileModel>();
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetMessageIds(IEnumerable<RoleDialogModel> conversations, int? offset = null)
|
||||
{
|
||||
if (conversations.IsNullOrEmpty()) return Enumerable.Empty<string>();
|
||||
|
||||
if (offset.HasValue && offset < 1)
|
||||
{
|
||||
offset = 1;
|
||||
}
|
||||
|
||||
var messageIds = new List<string>();
|
||||
if (offset.HasValue)
|
||||
{
|
||||
messageIds = conversations.Select(x => x.MessageId).Distinct().TakeLast(offset.Value).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
messageIds = conversations.Select(x => x.MessageId).Distinct().ToList();
|
||||
}
|
||||
|
||||
return messageIds;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
namespace BotSharp.Core.Files.Services;
|
||||
|
||||
public partial class FileInstructService : IFileInstructService
|
||||
{
|
||||
private readonly IFileStorageService _fileStorage;
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly ILogger<FileInstructService> _logger;
|
||||
|
||||
private const string SESSION_FOLDER = "sessions";
|
||||
|
||||
public FileInstructService(
|
||||
IFileStorageService fileStorate,
|
||||
ILogger<FileInstructService> logger,
|
||||
IServiceProvider services)
|
||||
{
|
||||
_fileStorage = fileStorate;
|
||||
_logger = logger;
|
||||
_services = services;
|
||||
}
|
||||
|
||||
private void DeleteIfExistDirectory(string? dir, bool createNew = false)
|
||||
{
|
||||
if (_fileStorage.ExistDirectory(dir))
|
||||
{
|
||||
_fileStorage.DeleteDirectory(dir);
|
||||
}
|
||||
else if (createNew)
|
||||
{
|
||||
_fileStorage.CreateDirectory(dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
using System.IO;
|
||||
|
||||
namespace BotSharp.Core.Files.Services
|
||||
{
|
||||
public partial class LocalFileStorageService
|
||||
{
|
||||
public async Task SaveSpeechFileAsync(string conversationId, string fileName, BinaryData data)
|
||||
{
|
||||
var dir = Path.Combine(_baseDir, CONVERSATION_FOLDER, conversationId, TEXT_TO_SPEECH_FOLDER);
|
||||
if (!Directory.Exists(dir))
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
var filePath = Path.Combine(dir, fileName);
|
||||
if (File.Exists(filePath)) return;
|
||||
using var file = File.Create(filePath);
|
||||
using var input = data.ToStream();
|
||||
await input.CopyToAsync(file);
|
||||
}
|
||||
|
||||
public async Task<BinaryData> RetrieveSpeechFileAsync(string conversationId, string fileName)
|
||||
{
|
||||
var path = Path.Combine(_baseDir, CONVERSATION_FOLDER, conversationId, TEXT_TO_SPEECH_FOLDER, fileName);
|
||||
using var file = new FileStream(path, FileMode.Open, FileAccess.Read);
|
||||
return await BinaryData.FromStreamAsync(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
using System.IO;
|
||||
|
||||
namespace BotSharp.Core.Files.Services;
|
||||
|
||||
public partial class LocalFileStorageService
|
||||
{
|
||||
public string GetDirectory(string conversationId)
|
||||
{
|
||||
var dir = Path.Combine(_baseDir, CONVERSATION_FOLDER, conversationId, "attachments");
|
||||
if (!Directory.Exists(dir))
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
public byte[] GetFileBytes(string fileStorageUrl)
|
||||
{
|
||||
using var stream = File.OpenRead(fileStorageUrl);
|
||||
var bytes = new byte[stream.Length];
|
||||
stream.Read(bytes, 0, (int)stream.Length);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public bool SaveFileStreamToPath(string filePath, Stream stream)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filePath)) return false;
|
||||
|
||||
using (var fileStream = new FileStream(filePath, FileMode.Create))
|
||||
{
|
||||
stream.CopyTo(fileStream);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SaveFileBytesToPath(string filePath, byte[] bytes)
|
||||
{
|
||||
using (var fs = new FileStream(filePath, FileMode.Create))
|
||||
{
|
||||
fs.Write(bytes, 0, bytes.Length);
|
||||
fs.Flush();
|
||||
fs.Close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public string GetParentDir(string dir, int level = 1)
|
||||
{
|
||||
var segs = dir.Split(Path.DirectorySeparatorChar);
|
||||
return string.Join(Path.DirectorySeparatorChar, segs.SkipLast(level));
|
||||
}
|
||||
|
||||
public string BuildDirectory(params string[] segments)
|
||||
{
|
||||
var relativePath = Path.Combine(segments);
|
||||
return Path.Combine(_baseDir, relativePath);
|
||||
}
|
||||
|
||||
public void CreateDirectory(string dir)
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
|
||||
public bool ExistDirectory(string? dir)
|
||||
{
|
||||
return !string.IsNullOrEmpty(dir) && Directory.Exists(dir);
|
||||
}
|
||||
|
||||
public void DeleteDirectory(string dir)
|
||||
{
|
||||
Directory.Delete(dir, true);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,24 +4,22 @@ using System.IO;
|
|||
|
||||
namespace BotSharp.Core.Files.Services;
|
||||
|
||||
public partial class BotSharpFileService
|
||||
public partial class LocalFileStorageService
|
||||
{
|
||||
public async Task<IEnumerable<MessageFileModel>> GetChatFiles(string conversationId, string source,
|
||||
IEnumerable<RoleDialogModel> conversations, IEnumerable<string> contentTypes,
|
||||
bool includeScreenShot = false, int? offset = null)
|
||||
public async Task<IEnumerable<MessageFileModel>> GetMessageFileScreenshots(string conversationId, IEnumerable<string> messageIds)
|
||||
{
|
||||
var files = new List<MessageFileModel>();
|
||||
if (string.IsNullOrEmpty(conversationId) || conversations.IsNullOrEmpty())
|
||||
if (string.IsNullOrEmpty(conversationId) || messageIds.IsNullOrEmpty())
|
||||
{
|
||||
return files;
|
||||
}
|
||||
|
||||
var messageIds = GetMessageIds(conversations, offset);
|
||||
var source = FileSourceType.User;
|
||||
var pathPrefix = Path.Combine(_baseDir, CONVERSATION_FOLDER, conversationId, FILE_FOLDER);
|
||||
|
||||
foreach (var messageId in messageIds)
|
||||
{
|
||||
var dir = Path.Combine(pathPrefix, messageId, source);
|
||||
var dir = Path.Combine(pathPrefix, messageId, FileSourceType.User);
|
||||
if (!ExistDirectory(dir)) continue;
|
||||
|
||||
foreach (var subDir in Directory.GetDirectories(dir))
|
||||
|
|
@ -29,21 +27,18 @@ public partial class BotSharpFileService
|
|||
var file = Directory.GetFiles(subDir).FirstOrDefault();
|
||||
if (file == null) continue;
|
||||
|
||||
var contentType = GetFileContentType(file);
|
||||
if (contentTypes?.Contains(contentType) != true) continue;
|
||||
var screenshots = await GetScreenshots(file, subDir, messageId, source);
|
||||
if (screenshots.IsNullOrEmpty()) continue;
|
||||
|
||||
var foundFiles = await GetMessageFiles(file, subDir, contentType, messageId, source, includeScreenShot);
|
||||
if (foundFiles.IsNullOrEmpty()) continue;
|
||||
|
||||
files.AddRange(foundFiles);
|
||||
files.AddRange(screenshots);
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
|
||||
public IEnumerable<MessageFileModel> GetMessageFiles(string conversationId, IEnumerable<string> messageIds,
|
||||
string source, bool imageOnly = false)
|
||||
string source, IEnumerable<string>? contentTypes = null)
|
||||
{
|
||||
var files = new List<MessageFileModel>();
|
||||
if (string.IsNullOrWhiteSpace(conversationId) || messageIds.IsNullOrEmpty()) return files;
|
||||
|
|
@ -62,21 +57,21 @@ public partial class BotSharpFileService
|
|||
|
||||
foreach (var file in Directory.GetFiles(subDir))
|
||||
{
|
||||
var contentType = GetFileContentType(file);
|
||||
if (imageOnly && !_imageTypes.Contains(contentType))
|
||||
var contentType = FileUtility.GetFileContentType(file);
|
||||
if (!contentTypes.IsNullOrEmpty() && !contentTypes.Contains(contentType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var fileName = Path.GetFileNameWithoutExtension(file);
|
||||
var fileType = Path.GetExtension(file).Substring(1);
|
||||
var fileExtension = Path.GetExtension(file).Substring(1);
|
||||
var model = new MessageFileModel()
|
||||
{
|
||||
MessageId = messageId,
|
||||
FileUrl = $"/conversation/{conversationId}/message/{messageId}/{source}/file/{index}/{fileName}",
|
||||
FileStorageUrl = file,
|
||||
FileName = fileName,
|
||||
FileType = fileType,
|
||||
FileExtension = fileExtension,
|
||||
ContentType = contentType,
|
||||
FileSource = source
|
||||
};
|
||||
|
|
@ -84,7 +79,6 @@ public partial class BotSharpFileService
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
|
|
@ -141,7 +135,7 @@ public partial class BotSharpFileService
|
|||
|
||||
try
|
||||
{
|
||||
var (_, bytes) = GetFileInfoFromData(file.FileData);
|
||||
var (_, bytes) = FileUtility.GetFileInfoFromData(file.FileData);
|
||||
var subDir = Path.Combine(dir, source, $"{i + 1}");
|
||||
if (!ExistDirectory(subDir))
|
||||
{
|
||||
|
|
@ -180,7 +174,7 @@ public partial class BotSharpFileService
|
|||
{
|
||||
if (ExistDirectory(newDir))
|
||||
{
|
||||
Directory.Delete(newDir, true);
|
||||
DeleteDirectory(newDir);
|
||||
}
|
||||
|
||||
Directory.Move(prevDir, newDir);
|
||||
|
|
@ -189,7 +183,7 @@ public partial class BotSharpFileService
|
|||
var botDir = Path.Combine(newDir, BOT_FILE_FOLDER);
|
||||
if (ExistDirectory(botDir))
|
||||
{
|
||||
Directory.Delete(botDir, true);
|
||||
DeleteDirectory(botDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -199,8 +193,8 @@ public partial class BotSharpFileService
|
|||
var dir = GetConversationFileDirectory(conversationId, messageId);
|
||||
if (!ExistDirectory(dir)) continue;
|
||||
|
||||
DeleteDirectory(dir);
|
||||
Thread.Sleep(100);
|
||||
Directory.Delete(dir, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
@ -215,7 +209,7 @@ public partial class BotSharpFileService
|
|||
var convDir = GetConversationDirectory(conversationId);
|
||||
if (!ExistDirectory(convDir)) continue;
|
||||
|
||||
Directory.Delete(convDir, true);
|
||||
DeleteDirectory(convDir);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
@ -244,111 +238,28 @@ public partial class BotSharpFileService
|
|||
return dir;
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetMessageIds(IEnumerable<RoleDialogModel> conversations, int? offset = null)
|
||||
private IEnumerable<string> GetMessageIds(IEnumerable<RoleDialogModel> dialogs, int? offset = null)
|
||||
{
|
||||
if (conversations.IsNullOrEmpty()) return Enumerable.Empty<string>();
|
||||
if (dialogs.IsNullOrEmpty()) return Enumerable.Empty<string>();
|
||||
|
||||
if (offset <= 0)
|
||||
if (offset.HasValue && offset < 1)
|
||||
{
|
||||
offset = MIN_OFFSET;
|
||||
}
|
||||
else if (offset > MAX_OFFSET)
|
||||
{
|
||||
offset = MAX_OFFSET;
|
||||
offset = 1;
|
||||
}
|
||||
|
||||
var messageIds = new List<string>();
|
||||
if (offset.HasValue)
|
||||
{
|
||||
messageIds = conversations.Select(x => x.MessageId).Distinct().TakeLast(offset.Value).ToList();
|
||||
messageIds = dialogs.Select(x => x.MessageId).Distinct().TakeLast(offset.Value).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
messageIds = conversations.Select(x => x.MessageId).Distinct().ToList();
|
||||
messageIds = dialogs.Select(x => x.MessageId).Distinct().ToList();
|
||||
}
|
||||
|
||||
return messageIds;
|
||||
}
|
||||
|
||||
|
||||
private async Task<IEnumerable<MessageFileModel>> GetMessageFiles(string file, string fileDir, string contentType,
|
||||
string messageId, string source, bool includeScreenShot)
|
||||
{
|
||||
var files = new List<MessageFileModel>();
|
||||
|
||||
try
|
||||
{
|
||||
if (!_imageTypes.Contains(contentType) && includeScreenShot)
|
||||
{
|
||||
var screenShotDir = Path.Combine(fileDir, SCREENSHOT_FILE_FOLDER);
|
||||
if (ExistDirectory(screenShotDir) && !Directory.GetFiles(screenShotDir).IsNullOrEmpty())
|
||||
{
|
||||
foreach (var screenShot in Directory.GetFiles(screenShotDir))
|
||||
{
|
||||
contentType = GetFileContentType(screenShot);
|
||||
if (!_imageTypes.Contains(contentType)) continue;
|
||||
|
||||
var fileName = Path.GetFileNameWithoutExtension(screenShot);
|
||||
var fileType = Path.GetExtension(file).Substring(1);
|
||||
var model = new MessageFileModel()
|
||||
{
|
||||
MessageId = messageId,
|
||||
FileName = fileName,
|
||||
FileType = fileType,
|
||||
FileStorageUrl = screenShot,
|
||||
ContentType = contentType,
|
||||
FileSource = source
|
||||
};
|
||||
files.Add(model);
|
||||
}
|
||||
}
|
||||
else if (contentType == MediaTypeNames.Application.Pdf)
|
||||
{
|
||||
var images = await ConvertPdfToImages(file, screenShotDir);
|
||||
foreach (var image in images)
|
||||
{
|
||||
contentType = GetFileContentType(image);
|
||||
var fileName = Path.GetFileNameWithoutExtension(image);
|
||||
var fileType = Path.GetExtension(image).Substring(1);
|
||||
var model = new MessageFileModel()
|
||||
{
|
||||
MessageId = messageId,
|
||||
FileName = fileName,
|
||||
FileType = fileType,
|
||||
FileStorageUrl = image,
|
||||
ContentType = contentType,
|
||||
FileSource = source
|
||||
};
|
||||
files.Add(model);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var fileName = Path.GetFileNameWithoutExtension(file);
|
||||
var fileType = Path.GetExtension(file).Substring(1);
|
||||
var model = new MessageFileModel()
|
||||
{
|
||||
MessageId = messageId,
|
||||
FileName = fileName,
|
||||
FileType = fileType,
|
||||
FileStorageUrl = file,
|
||||
ContentType = contentType,
|
||||
FileSource = source
|
||||
};
|
||||
files.Add(model);
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning($"Error when getting message files {file} (messageId: {messageId}), Error: {ex.Message}\r\n{ex.InnerException}");
|
||||
return files;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Task<IEnumerable<string>> ConvertPdfToImages(string pdfLoc, string imageLoc)
|
||||
{
|
||||
var converters = _services.GetServices<IPdf2ImageConverter>();
|
||||
|
|
@ -364,8 +275,66 @@ public partial class BotSharpFileService
|
|||
|
||||
private IPdf2ImageConverter? GetPdf2ImageConverter()
|
||||
{
|
||||
var converters = _services.GetServices<IPdf2ImageConverter>();
|
||||
return converters.FirstOrDefault();
|
||||
var settings = _services.GetRequiredService<FileCoreSettings>();
|
||||
var converter = _services.GetServices<IPdf2ImageConverter>().FirstOrDefault(x => x.Name == settings.Pdf2ImageConverter);
|
||||
return converter;
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<MessageFileModel>> GetScreenshots(string file, string parentDir, string messageId, string source)
|
||||
{
|
||||
var files = new List<MessageFileModel>();
|
||||
|
||||
try
|
||||
{
|
||||
var contentType = FileUtility.GetFileContentType(file);
|
||||
var screenshotDir = Path.Combine(parentDir, SCREENSHOT_FILE_FOLDER);
|
||||
|
||||
if (ExistDirectory(screenshotDir) && !Directory.GetFiles(screenshotDir).IsNullOrEmpty())
|
||||
{
|
||||
foreach (var screenshot in Directory.GetFiles(screenshotDir))
|
||||
{
|
||||
var fileName = Path.GetFileNameWithoutExtension(screenshot);
|
||||
var fileExtension = Path.GetExtension(screenshot).Substring(1);
|
||||
var screenshotContentType = FileUtility.GetFileContentType(screenshot);
|
||||
var model = new MessageFileModel()
|
||||
{
|
||||
MessageId = messageId,
|
||||
FileName = fileName,
|
||||
FileExtension = fileExtension,
|
||||
FileStorageUrl = screenshot,
|
||||
ContentType = screenshotContentType,
|
||||
FileSource = source
|
||||
};
|
||||
files.Add(model);
|
||||
}
|
||||
}
|
||||
else if (contentType == MediaTypeNames.Application.Pdf)
|
||||
{
|
||||
var images = await ConvertPdfToImages(file, screenshotDir);
|
||||
foreach (var image in images)
|
||||
{
|
||||
var fileName = Path.GetFileNameWithoutExtension(image);
|
||||
var fileExtension = Path.GetExtension(image).Substring(1);
|
||||
var screenshotContentType = FileUtility.GetFileContentType(image);
|
||||
var model = new MessageFileModel()
|
||||
{
|
||||
MessageId = messageId,
|
||||
FileName = fileName,
|
||||
FileExtension = fileExtension,
|
||||
FileStorageUrl = image,
|
||||
ContentType = screenshotContentType,
|
||||
FileSource = source
|
||||
};
|
||||
files.Add(model);
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning($"Error when getting message file screenshots {file} (messageId: {messageId}), Error: {ex.Message}\r\n{ex.InnerException}");
|
||||
return files;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ using System.IO;
|
|||
|
||||
namespace BotSharp.Core.Files.Services;
|
||||
|
||||
public partial class BotSharpFileService
|
||||
public partial class LocalFileStorageService
|
||||
{
|
||||
public string GetUserAvatar()
|
||||
{
|
||||
|
|
@ -30,11 +30,11 @@ public partial class BotSharpFileService
|
|||
|
||||
if (Directory.Exists(dir))
|
||||
{
|
||||
Directory.Delete(dir, true);
|
||||
DeleteDirectory(dir);
|
||||
}
|
||||
|
||||
dir = GetUserAvatarDir(user?.Id, createNewDir: true);
|
||||
var (_, bytes) = GetFileInfoFromData(file.FileData);
|
||||
var (_, bytes) = FileUtility.GetFileInfoFromData(file.FileData);
|
||||
File.WriteAllBytes(Path.Combine(dir, file.FileName), bytes);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1,20 +1,14 @@
|
|||
using Microsoft.AspNetCore.StaticFiles;
|
||||
using System.IO;
|
||||
|
||||
namespace BotSharp.Core.Files.Services;
|
||||
|
||||
public partial class BotSharpFileService : IBotSharpFileService
|
||||
public partial class LocalFileStorageService : IFileStorageService
|
||||
{
|
||||
private readonly BotSharpDatabaseSettings _dbSettings;
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly IUserIdentity _user;
|
||||
private readonly ILogger<BotSharpFileService> _logger;
|
||||
private readonly ILogger<LocalFileStorageService> _logger;
|
||||
private readonly string _baseDir;
|
||||
private readonly IEnumerable<string> _imageTypes = new List<string>
|
||||
{
|
||||
MediaTypeNames.Image.Png,
|
||||
MediaTypeNames.Image.Jpeg
|
||||
};
|
||||
|
||||
private const string CONVERSATION_FOLDER = "conversations";
|
||||
private const string FILE_FOLDER = "files";
|
||||
|
|
@ -24,14 +18,12 @@ public partial class BotSharpFileService : IBotSharpFileService
|
|||
private const string USERS_FOLDER = "users";
|
||||
private const string USER_AVATAR_FOLDER = "avatar";
|
||||
private const string SESSION_FOLDER = "sessions";
|
||||
private const string TEXT_TO_SPEECH_FOLDER = "speeches";
|
||||
|
||||
private const int MIN_OFFSET = 1;
|
||||
private const int MAX_OFFSET = 5;
|
||||
|
||||
public BotSharpFileService(
|
||||
public LocalFileStorageService(
|
||||
BotSharpDatabaseSettings dbSettings,
|
||||
IUserIdentity user,
|
||||
ILogger<BotSharpFileService> logger,
|
||||
ILogger<LocalFileStorageService> logger,
|
||||
IServiceProvider services)
|
||||
{
|
||||
_dbSettings = dbSettings;
|
||||
|
|
@ -40,11 +32,4 @@ public partial class BotSharpFileService : IBotSharpFileService
|
|||
_services = services;
|
||||
_baseDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, dbSettings.FileRepository);
|
||||
}
|
||||
|
||||
#region Private methods
|
||||
private bool ExistDirectory(string? dir)
|
||||
{
|
||||
return !string.IsNullOrEmpty(dir) && Directory.Exists(dir);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -115,6 +115,22 @@ public class CompletionProvider
|
|||
return completer;
|
||||
}
|
||||
|
||||
public static ITextToSpeech GetTextToSpeech(
|
||||
IServiceProvider services,
|
||||
string provider,
|
||||
string model)
|
||||
{
|
||||
var completions = services.GetServices<ITextToSpeech>();
|
||||
var completer = completions.FirstOrDefault(x => x.Provider == provider);
|
||||
if (completer == null)
|
||||
{
|
||||
var logger = services.GetRequiredService<ILogger<CompletionProvider>>();
|
||||
logger.LogError($"Can't resolve text2speech provider by {provider}");
|
||||
}
|
||||
completer.SetModelName(model);
|
||||
return completer;
|
||||
}
|
||||
|
||||
private static (string, string) GetProviderAndModel(IServiceProvider services,
|
||||
string? provider = null,
|
||||
string? model = null,
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ public class SettingService : ISettingService
|
|||
var plugins = pluginService.GetPlugins(_services);
|
||||
var plugin = plugins.First(x => x.Module.Settings.Name == settingName);
|
||||
var instance = plugin.Module.GetNewSettingsInstance();
|
||||
|
||||
_config.Bind(settingName, instance);
|
||||
if (mask)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Routing.Models;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
namespace BotSharp.Core.Repository
|
||||
{
|
||||
|
|
@ -37,7 +37,7 @@ namespace BotSharp.Core.Repository
|
|||
UpdateAgentRoutingRules(agent.Id, agent.RoutingRules);
|
||||
break;
|
||||
case AgentField.Instruction:
|
||||
UpdateAgentInstruction(agent.Id, agent.Instruction);
|
||||
UpdateAgentInstructions(agent.Id, agent.Instruction, agent.ChannelInstructions);
|
||||
break;
|
||||
case AgentField.Function:
|
||||
UpdateAgentFunctions(agent.Id, agent.Functions);
|
||||
|
|
@ -175,17 +175,30 @@ namespace BotSharp.Core.Repository
|
|||
File.WriteAllText(agentFile, json);
|
||||
}
|
||||
|
||||
private void UpdateAgentInstruction(string agentId, string instruction)
|
||||
private void UpdateAgentInstructions(string agentId, string instruction, List<ChannelInstruction> channelInstructions)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(instruction)) return;
|
||||
|
||||
var (agent, agentFile) = GetAgentFromFile(agentId);
|
||||
if (agent == null) return;
|
||||
|
||||
var instructionFile = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir,
|
||||
agentId, $"{AGENT_INSTRUCTION_FILE}.{_agentSettings.TemplateFormat}");
|
||||
var instructionDir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agentId, AGENT_INSTRUCTIONS_FOLDER);
|
||||
DeleteBeforeCreateDirectory(instructionDir);
|
||||
|
||||
File.WriteAllText(instructionFile, instruction);
|
||||
// Save default instructions
|
||||
var instructionFile = Path.Combine(instructionDir, $"{AGENT_INSTRUCTION_FILE}.{_agentSettings.TemplateFormat}");
|
||||
File.WriteAllText(instructionFile, instruction ?? string.Empty);
|
||||
Thread.Sleep(100);
|
||||
|
||||
// Save channel instructions
|
||||
foreach (var ci in channelInstructions)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ci.Channel)) continue;
|
||||
|
||||
var file = Path.Combine(instructionDir, $"{AGENT_INSTRUCTION_FILE}.{ci.Channel}.{_agentSettings.TemplateFormat}");
|
||||
File.WriteAllText(file, ci.Instruction ?? string.Empty);
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateAgentFunctions(string agentId, List<FunctionDef> inputFunctions)
|
||||
|
|
@ -195,14 +208,8 @@ namespace BotSharp.Core.Repository
|
|||
var (agent, agentFile) = GetAgentFromFile(agentId);
|
||||
if (agent == null) return;
|
||||
|
||||
var functionDir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir,
|
||||
agentId, AGENT_FUNCTIONS_FOLDER);
|
||||
|
||||
if (Directory.Exists(functionDir))
|
||||
{
|
||||
Directory.Delete(functionDir, true);
|
||||
}
|
||||
Directory.CreateDirectory(functionDir);
|
||||
var functionDir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agentId, AGENT_FUNCTIONS_FOLDER);
|
||||
DeleteBeforeCreateDirectory(functionDir);
|
||||
|
||||
foreach (var func in inputFunctions)
|
||||
{
|
||||
|
|
@ -211,7 +218,7 @@ namespace BotSharp.Core.Repository
|
|||
var text = JsonSerializer.Serialize(func, _options);
|
||||
var file = Path.Combine(functionDir, $"{func.Name}.json");
|
||||
File.WriteAllText(file, text);
|
||||
Thread.Sleep(200);
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -223,16 +230,7 @@ namespace BotSharp.Core.Repository
|
|||
if (agent == null) return;
|
||||
|
||||
var templateDir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agentId, AGENT_TEMPLATES_FOLDER);
|
||||
|
||||
if (!Directory.Exists(templateDir))
|
||||
{
|
||||
Directory.CreateDirectory(templateDir);
|
||||
}
|
||||
|
||||
foreach (var file in Directory.GetFiles(templateDir))
|
||||
{
|
||||
File.Delete(file);
|
||||
}
|
||||
DeleteBeforeCreateDirectory(templateDir);
|
||||
|
||||
foreach (var template in templates)
|
||||
{
|
||||
|
|
@ -249,15 +247,7 @@ namespace BotSharp.Core.Repository
|
|||
if (agent == null) return;
|
||||
|
||||
var responseDir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agentId, AGENT_RESPONSES_FOLDER);
|
||||
if (!Directory.Exists(responseDir))
|
||||
{
|
||||
Directory.CreateDirectory(responseDir);
|
||||
}
|
||||
|
||||
foreach (var file in Directory.GetFiles(responseDir))
|
||||
{
|
||||
File.Delete(file);
|
||||
}
|
||||
DeleteBeforeCreateDirectory(responseDir);
|
||||
|
||||
for (int i = 0; i < responses.Count; i++)
|
||||
{
|
||||
|
|
@ -308,7 +298,7 @@ namespace BotSharp.Core.Repository
|
|||
var json = JsonSerializer.Serialize(agent, _options);
|
||||
File.WriteAllText(agentFile, json);
|
||||
|
||||
UpdateAgentInstruction(inputAgent.Id, inputAgent.Instruction);
|
||||
UpdateAgentInstructions(inputAgent.Id, inputAgent.Instruction, inputAgent.ChannelInstructions);
|
||||
UpdateAgentResponses(inputAgent.Id, inputAgent.Responses);
|
||||
UpdateAgentTemplates(inputAgent.Id, inputAgent.Templates);
|
||||
UpdateAgentFunctions(inputAgent.Id, inputAgent.Functions);
|
||||
|
|
@ -348,12 +338,13 @@ namespace BotSharp.Core.Repository
|
|||
var record = JsonSerializer.Deserialize<Agent>(json, _options);
|
||||
if (record == null) return null;
|
||||
|
||||
var instruction = FetchInstruction(dir);
|
||||
var (defaultInstruction, channelInstructions) = FetchInstructions(dir);
|
||||
var functions = FetchFunctions(dir);
|
||||
var samples = FetchSamples(dir);
|
||||
var templates = FetchTemplates(dir);
|
||||
var responses = FetchResponses(dir);
|
||||
return record.SetInstruction(instruction)
|
||||
return record.SetInstruction(defaultInstruction)
|
||||
.SetChannelInstructions(channelInstructions)
|
||||
.SetFunctions(functions)
|
||||
.SetTemplates(templates)
|
||||
.SetSamples(samples)
|
||||
|
|
@ -451,13 +442,9 @@ namespace BotSharp.Core.Repository
|
|||
return true;
|
||||
}
|
||||
|
||||
public void BulkInsertAgents(List<Agent> agents)
|
||||
{
|
||||
}
|
||||
public void BulkInsertAgents(List<Agent> agents) { }
|
||||
|
||||
public void BulkInsertUserAgents(List<UserAgent> userAgents)
|
||||
{
|
||||
}
|
||||
public void BulkInsertUserAgents(List<UserAgent> userAgents) { }
|
||||
|
||||
public bool DeleteAgents()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ public partial class FileRepository : IBotSharpRepository
|
|||
private const string AGENT_TASK_PREFIX = "#metadata";
|
||||
private const string AGENT_TASK_SUFFIX = "/metadata";
|
||||
private const string TRANSLATION_MEMORY_FILE = "memory.json";
|
||||
private const string AGENT_INSTRUCTIONS_FOLDER = "instructions";
|
||||
private const string AGENT_FUNCTIONS_FOLDER = "functions";
|
||||
private const string AGENT_TEMPLATES_FOLDER = "templates";
|
||||
private const string AGENT_RESPONSES_FOLDER = "responses";
|
||||
|
|
@ -123,7 +124,9 @@ public partial class FileRepository : IBotSharpRepository
|
|||
var agent = JsonSerializer.Deserialize<Agent>(json, _options);
|
||||
if (agent != null)
|
||||
{
|
||||
agent = agent.SetInstruction(FetchInstruction(d))
|
||||
var (defaultInstruction, channelInstructions) = FetchInstructions(d);
|
||||
agent = agent.SetInstruction(defaultInstruction)
|
||||
.SetChannelInstructions(channelInstructions)
|
||||
.SetFunctions(FetchFunctions(d))
|
||||
.SetTemplates(FetchTemplates(d))
|
||||
.SetResponses(FetchResponses(d))
|
||||
|
|
@ -165,6 +168,17 @@ public partial class FileRepository : IBotSharpRepository
|
|||
|
||||
|
||||
#region Private methods
|
||||
private void DeleteBeforeCreateDirectory(string dir)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(dir)) return;
|
||||
|
||||
if (Directory.Exists(dir))
|
||||
{
|
||||
Directory.Delete(dir, true);
|
||||
}
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
|
||||
private string GetAgentDataDir(string agentId)
|
||||
{
|
||||
var dir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agentId);
|
||||
|
|
@ -186,13 +200,46 @@ public partial class FileRepository : IBotSharpRepository
|
|||
return (agent, agentFile);
|
||||
}
|
||||
|
||||
private string? FetchInstruction(string fileDir)
|
||||
private (string, List<ChannelInstruction>) FetchInstructions(string fileDir)
|
||||
{
|
||||
var file = Path.Combine(fileDir, $"{AGENT_INSTRUCTION_FILE}.{_agentSettings.TemplateFormat}");
|
||||
if (!File.Exists(file)) return null;
|
||||
var defaultInstruction = string.Empty;
|
||||
var channelInstructions = new List<ChannelInstruction>();
|
||||
|
||||
var instruction = File.ReadAllText(file);
|
||||
return instruction;
|
||||
var instructionDir = Path.Combine(fileDir, AGENT_INSTRUCTIONS_FOLDER);
|
||||
if (!Directory.Exists(instructionDir))
|
||||
{
|
||||
return (defaultInstruction, channelInstructions);
|
||||
}
|
||||
|
||||
foreach (var file in Directory.GetFiles(instructionDir))
|
||||
{
|
||||
var extension = Path.GetExtension(file).Substring(1);
|
||||
if (!extension.IsEqualTo(_agentSettings.TemplateFormat))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var segments = Path.GetFileName(file).Split(".", StringSplitOptions.RemoveEmptyEntries);
|
||||
if (segments.IsNullOrEmpty() || !segments[0].IsEqualTo(AGENT_INSTRUCTION_FILE))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (segments.Length == 2)
|
||||
{
|
||||
defaultInstruction = File.ReadAllText(file);
|
||||
}
|
||||
else if (segments.Length == 3)
|
||||
{
|
||||
var item = new ChannelInstruction
|
||||
{
|
||||
Channel = segments[1],
|
||||
Instruction = File.ReadAllText(file)
|
||||
};
|
||||
channelInstructions.Add(item);
|
||||
}
|
||||
}
|
||||
return (defaultInstruction, channelInstructions);
|
||||
}
|
||||
|
||||
private List<FunctionDef> FetchFunctions(string fileDir)
|
||||
|
|
@ -298,13 +345,14 @@ public partial class FileRepository : IBotSharpRepository
|
|||
var agent = JsonSerializer.Deserialize<Agent>(agentJson, _options);
|
||||
if (agent == null) return null;
|
||||
|
||||
var instruction = FetchInstruction(agentDir);
|
||||
var (defaultInstruction, channelInstructions) = FetchInstructions(agentDir);
|
||||
var functions = FetchFunctions(agentDir);
|
||||
var samples = FetchSamples(agentDir);
|
||||
var templates = FetchTemplates(agentDir);
|
||||
var responses = FetchResponses(agentDir);
|
||||
|
||||
return agent.SetInstruction(instruction)
|
||||
return agent.SetInstruction(defaultInstruction)
|
||||
.SetChannelInstructions(channelInstructions)
|
||||
.SetFunctions(functions)
|
||||
.SetTemplates(templates)
|
||||
.SetSamples(samples)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,10 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler
|
|||
new ParameterPropertyDef("user_goal_agent",
|
||||
"agent who can acheive user initial task, must align with user_goal_description.",
|
||||
required: true),
|
||||
new ParameterPropertyDef("conversation_end",
|
||||
"user is ending the conversation.",
|
||||
type: "boolean",
|
||||
required: true),
|
||||
new ParameterPropertyDef("is_new_task",
|
||||
"whether the user is requesting a new task that is different from the previous topic.",
|
||||
type: "boolean")
|
||||
|
|
|
|||
|
|
@ -205,11 +205,12 @@ public class UserService : IUserService
|
|||
var config = _services.GetRequiredService<IConfiguration>();
|
||||
var issuer = config["Jwt:Issuer"];
|
||||
var audience = config["Jwt:Audience"];
|
||||
var expireInMinutes = int.Parse(config["Jwt:ExpireInMinutes"] ?? "120");
|
||||
var key = Encoding.ASCII.GetBytes(config["Jwt:Key"]);
|
||||
var tokenDescriptor = new SecurityTokenDescriptor
|
||||
{
|
||||
Subject = new ClaimsIdentity(claims),
|
||||
Expires = DateTime.UtcNow.AddHours(2),
|
||||
Expires = DateTime.UtcNow.AddMinutes(expireInMinutes),
|
||||
Issuer = issuer,
|
||||
Audience = audience,
|
||||
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key),
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ global using BotSharp.Abstraction.Translation;
|
|||
global using BotSharp.Abstraction.Files;
|
||||
global using BotSharp.Abstraction.Files.Models;
|
||||
global using BotSharp.Abstraction.Files.Enums;
|
||||
global using BotSharp.Abstraction.Files.Utilities;
|
||||
global using BotSharp.Abstraction.Translation.Attributes;
|
||||
global using BotSharp.Abstraction.Messaging.Enums;
|
||||
global using BotSharp.Core.Repository;
|
||||
|
|
@ -37,4 +38,4 @@ global using BotSharp.Core.Agents.Services;
|
|||
global using BotSharp.Core.Conversations.Services;
|
||||
global using BotSharp.Core.Infrastructures;
|
||||
global using BotSharp.Core.Users.Services;
|
||||
global using Aspects.Cache;
|
||||
global using Aspects.Cache;
|
||||
|
|
@ -7,5 +7,11 @@
|
|||
"updatedDateTime": "2024-01-15T14:39:32Z",
|
||||
"iconUrl": "/images/logo.png",
|
||||
"disabled": false,
|
||||
"isPublic": true
|
||||
"isPublic": true,
|
||||
"llmConfig": {
|
||||
"is_inherit": false,
|
||||
"provider": "openai",
|
||||
"model": "gpt-4o-mini",
|
||||
"max_recursion_depth": 3
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ Please take a look at the files in the [FILES] section from the conversation and
|
|||
|
||||
** 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.
|
||||
** You may need to look at the file_name as a reference to find the correct file id or ids.
|
||||
|
||||
Here is the JSON format to use:
|
||||
{
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Users.Enums;
|
||||
|
||||
namespace BotSharp.OpenAPI.Controllers;
|
||||
|
|
@ -28,17 +27,24 @@ public class AgentController : ControllerBase
|
|||
[HttpGet("/agent/{id}")]
|
||||
public async Task<AgentViewModel?> GetAgent([FromRoute] string id)
|
||||
{
|
||||
var agents = await GetAgents(new AgentFilter
|
||||
var pagedAgents = await _agentService.GetAgents(new AgentFilter
|
||||
{
|
||||
AgentIds = new List<string> { id }
|
||||
}, useHook: true);
|
||||
});
|
||||
|
||||
var targetAgent = agents.Items.FirstOrDefault();
|
||||
if (targetAgent == null) return null;
|
||||
var foundAgent = pagedAgents.Items.FirstOrDefault();
|
||||
if (foundAgent == null) return null;
|
||||
|
||||
await _agentService.InheritAgent(foundAgent);
|
||||
var targetAgent = AgentViewModel.FromAgent(foundAgent);
|
||||
var agentSetting = _services.GetRequiredService<AgentSettings>();
|
||||
targetAgent.IsHost = targetAgent.Id == agentSetting.HostAgentId;
|
||||
|
||||
var redirectAgentIds = targetAgent.RoutingRules
|
||||
.Where(x => !string.IsNullOrEmpty(x.RedirectTo))
|
||||
.Select(x => x.RedirectTo).ToList();
|
||||
.Select(x => x.RedirectTo)
|
||||
.ToList();
|
||||
|
||||
var redirectAgents = await _agentService.GetAgents(new AgentFilter
|
||||
{
|
||||
AgentIds = redirectAgentIds
|
||||
|
|
@ -65,39 +71,16 @@ public class AgentController : ControllerBase
|
|||
}
|
||||
|
||||
[HttpGet("/agents")]
|
||||
public async Task<PagedItems<AgentViewModel>> GetAgents([FromQuery] AgentFilter filter, [FromQuery] bool useHook = false)
|
||||
public async Task<PagedItems<AgentViewModel>> GetAgents([FromQuery] AgentFilter filter)
|
||||
{
|
||||
var agentSetting = _services.GetRequiredService<AgentSettings>();
|
||||
var pagedAgents = await _agentService.GetAgents(filter);
|
||||
|
||||
var items = new List<Agent>();
|
||||
var agents = new List<AgentViewModel>();
|
||||
if (useHook)
|
||||
{
|
||||
// prerender agent
|
||||
foreach (var agent in pagedAgents.Items)
|
||||
{
|
||||
var renderedAgent = await _agentService.LoadAgent(agent.Id);
|
||||
items.Add(renderedAgent);
|
||||
}
|
||||
|
||||
// Set IsHost
|
||||
agents = items.Select(x => AgentViewModel.FromAgent(x)).ToList();
|
||||
foreach (var agent in agents)
|
||||
{
|
||||
agent.IsHost = agentSetting.HostAgentId == agent.Id;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
items = pagedAgents.Items.ToList();
|
||||
agents = items.Select(x => AgentViewModel.FromAgent(x)).ToList();
|
||||
}
|
||||
var agents = pagedAgents?.Items?.Select(x => AgentViewModel.FromAgent(x))?.ToList() ?? new List<AgentViewModel>();
|
||||
|
||||
return new PagedItems<AgentViewModel>
|
||||
{
|
||||
Items = agents,
|
||||
Count = pagedAgents.Count
|
||||
Count = pagedAgents?.Count ?? 0
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using BotSharp.Abstraction.Files.Constants;
|
||||
using BotSharp.Abstraction.Files.Enums;
|
||||
using BotSharp.Abstraction.Options;
|
||||
using BotSharp.Abstraction.Routing;
|
||||
|
|
@ -80,10 +81,10 @@ public class ConversationController : ControllerBase
|
|||
|
||||
var userService = _services.GetRequiredService<IUserService>();
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var fileService = _services.GetRequiredService<IBotSharpFileService>();
|
||||
var fileStorage = _services.GetRequiredService<IFileStorageService>();
|
||||
|
||||
var messageIds = history.Select(x => x.MessageId).Distinct().ToList();
|
||||
var fileMessages = fileService.GetMessagesWithFile(conversationId, messageIds);
|
||||
var fileMessages = fileStorage.GetMessagesWithFile(conversationId, messageIds);
|
||||
|
||||
var dialogs = new List<ChatResponseModel>();
|
||||
foreach (var message in history)
|
||||
|
|
@ -348,15 +349,15 @@ public class ConversationController : ControllerBase
|
|||
{
|
||||
if (files != null && files.Length > 0)
|
||||
{
|
||||
var fileService = _services.GetRequiredService<IBotSharpFileService>();
|
||||
var dir = fileService.GetDirectory(conversationId);
|
||||
var fileStorage = _services.GetRequiredService<IFileStorageService>();
|
||||
var dir = fileStorage.GetDirectory(conversationId);
|
||||
foreach (var file in files)
|
||||
{
|
||||
// Save the file, process it, etc.
|
||||
var fileName = ContentDispositionHeaderValue.Parse(file.ContentDisposition).FileName.Trim('"');
|
||||
var filePath = Path.Combine(dir, fileName);
|
||||
|
||||
fileService.SavefileToPath(filePath, file.OpenReadStream());
|
||||
fileStorage.SaveFileStreamToPath(filePath, file.OpenReadStream());
|
||||
}
|
||||
|
||||
return Ok(new { message = "File uploaded successfully." });
|
||||
|
|
@ -371,25 +372,25 @@ public class ConversationController : ControllerBase
|
|||
var convService = _services.GetRequiredService<IConversationService>();
|
||||
convService.SetConversationId(conversationId, input.States);
|
||||
var conv = await convService.GetConversationRecordOrCreateNew(agentId);
|
||||
var fileService = _services.GetRequiredService<IBotSharpFileService>();
|
||||
var fileStorage = _services.GetRequiredService<IFileStorageService>();
|
||||
var messageId = Guid.NewGuid().ToString();
|
||||
var isSaved = fileService.SaveMessageFiles(conv.Id, messageId, FileSourceType.User, input.Files);
|
||||
var isSaved = fileStorage.SaveMessageFiles(conv.Id, messageId, FileSourceType.User, input.Files);
|
||||
return isSaved ? messageId : string.Empty;
|
||||
}
|
||||
|
||||
[HttpGet("/conversation/{conversationId}/files/{messageId}/{source}")]
|
||||
public IEnumerable<MessageFileViewModel> GetConversationMessageFiles([FromRoute] string conversationId, [FromRoute] string messageId, [FromRoute] string source)
|
||||
{
|
||||
var fileService = _services.GetRequiredService<IBotSharpFileService>();
|
||||
var files = fileService.GetMessageFiles(conversationId, new List<string> { messageId }, source, imageOnly: false);
|
||||
var fileStorage = _services.GetRequiredService<IFileStorageService>();
|
||||
var files = fileStorage.GetMessageFiles(conversationId, new List<string> { messageId }, source);
|
||||
return files?.Select(x => MessageFileViewModel.Transform(x))?.ToList() ?? new List<MessageFileViewModel>();
|
||||
}
|
||||
|
||||
[HttpGet("/conversation/{conversationId}/message/{messageId}/{source}/file/{index}/{fileName}")]
|
||||
public IActionResult GetMessageFile([FromRoute] string conversationId, [FromRoute] string messageId, [FromRoute] string source, [FromRoute] string index, [FromRoute] string fileName)
|
||||
{
|
||||
var fileService = _services.GetRequiredService<IBotSharpFileService>();
|
||||
var file = fileService.GetMessageFile(conversationId, messageId, source, index, fileName);
|
||||
var fileStorage = _services.GetRequiredService<IFileStorageService>();
|
||||
var file = fileStorage.GetMessageFile(conversationId, messageId, source, index, fileName);
|
||||
if (string.IsNullOrEmpty(file))
|
||||
{
|
||||
return NotFound();
|
||||
|
|
@ -413,7 +414,9 @@ public class ConversationController : ControllerBase
|
|||
using Stream stream = System.IO.File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
var bytes = new byte[stream.Length];
|
||||
stream.Read(bytes, 0, (int)stream.Length);
|
||||
return File(bytes, "application/octet-stream", Path.GetFileName(file));
|
||||
var fileExtension = Path.GetExtension(file).ToLower();
|
||||
var enableRangeProcessing = FileConstants.AudioExtensions.Contains(fileExtension);
|
||||
return File(bytes, "application/octet-stream", Path.GetFileName(file), enableRangeProcessing: enableRangeProcessing);
|
||||
}
|
||||
|
||||
private async Task OnChunkReceived(HttpResponse response, ChatResponseModel message)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ using BotSharp.Abstraction.Instructs;
|
|||
using BotSharp.Abstraction.Instructs.Models;
|
||||
using BotSharp.Core.Infrastructures;
|
||||
using BotSharp.OpenAPI.ViewModels.Instructs;
|
||||
using NetTopologySuite.IO;
|
||||
|
||||
namespace BotSharp.OpenAPI.Controllers;
|
||||
|
||||
|
|
@ -87,18 +86,8 @@ public class InstructModeController : ControllerBase
|
|||
|
||||
try
|
||||
{
|
||||
var completion = CompletionProvider.GetChatCompletion(_services, provider: input.Provider ?? "openai",
|
||||
model: input.Model ?? "gpt-4o", multiModal: true);
|
||||
var message = await completion.GetChatCompletions(new Agent()
|
||||
{
|
||||
Id = Guid.Empty.ToString(),
|
||||
}, new List<RoleDialogModel>
|
||||
{
|
||||
new RoleDialogModel(AgentRole.User, input.Text)
|
||||
{
|
||||
Files = input.Files
|
||||
}
|
||||
});
|
||||
var fileInstruct = _services.GetRequiredService<IFileInstructService>();
|
||||
var message = await fileInstruct.ReadImages(input.Provider, input.Model, input.Text, input.Files);
|
||||
return message.Content;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -114,14 +103,14 @@ public class InstructModeController : ControllerBase
|
|||
[HttpPost("/instruct/image-generation")]
|
||||
public async Task<ImageGenerationViewModel> ImageGeneration([FromBody] IncomingMessageModel input)
|
||||
{
|
||||
var fileService = _services.GetRequiredService<IBotSharpFileService>();
|
||||
var state = _services.GetRequiredService<IConversationStateService>();
|
||||
input.States.ForEach(x => state.SetState(x.Key, x.Value, activeRounds: x.ActiveRounds, source: StateSource.External));
|
||||
var imageViewModel = new ImageGenerationViewModel();
|
||||
|
||||
try
|
||||
{
|
||||
var message = await fileService.GenerateImage(input.Provider, input.Model, input.Text);
|
||||
var fileInstruct = _services.GetRequiredService<IFileInstructService>();
|
||||
var message = await fileInstruct.GenerateImage(input.Provider, input.Model, input.Text);
|
||||
imageViewModel.Content = message.Content;
|
||||
imageViewModel.Images = message.GeneratedImages.Select(x => ImageViewModel.ToViewModel(x)).ToList();
|
||||
return imageViewModel;
|
||||
|
|
@ -140,7 +129,6 @@ public class InstructModeController : ControllerBase
|
|||
[HttpPost("/instruct/image-variation")]
|
||||
public async Task<ImageGenerationViewModel> ImageVariation([FromBody] IncomingMessageModel input)
|
||||
{
|
||||
var fileService = _services.GetRequiredService<IBotSharpFileService>();
|
||||
var state = _services.GetRequiredService<IConversationStateService>();
|
||||
input.States.ForEach(x => state.SetState(x.Key, x.Value, activeRounds: x.ActiveRounds, source: StateSource.External));
|
||||
var imageViewModel = new ImageGenerationViewModel();
|
||||
|
|
@ -152,7 +140,9 @@ public class InstructModeController : ControllerBase
|
|||
{
|
||||
return new ImageGenerationViewModel { Message = "Error! Cannot find an image!" };
|
||||
}
|
||||
var message = await fileService.VaryImage(input.Provider, input.Model, image);
|
||||
|
||||
var fileInstruct = _services.GetRequiredService<IFileInstructService>();
|
||||
var message = await fileInstruct.VaryImage(input.Provider, input.Model, image);
|
||||
imageViewModel.Content = message.Content;
|
||||
imageViewModel.Images = message.GeneratedImages.Select(x => ImageViewModel.ToViewModel(x)).ToList();
|
||||
return imageViewModel;
|
||||
|
|
@ -169,7 +159,7 @@ public class InstructModeController : ControllerBase
|
|||
[HttpPost("/instruct/image-edit")]
|
||||
public async Task<ImageGenerationViewModel> ImageEdit([FromBody] IncomingMessageModel input)
|
||||
{
|
||||
var fileService = _services.GetRequiredService<IBotSharpFileService>();
|
||||
var fileInstruct = _services.GetRequiredService<IFileInstructService>();
|
||||
var state = _services.GetRequiredService<IConversationStateService>();
|
||||
input.States.ForEach(x => state.SetState(x.Key, x.Value, activeRounds: x.ActiveRounds, source: StateSource.External));
|
||||
var imageViewModel = new ImageGenerationViewModel();
|
||||
|
|
@ -181,7 +171,7 @@ public class InstructModeController : ControllerBase
|
|||
{
|
||||
return new ImageGenerationViewModel { Message = "Error! Cannot find an image!" };
|
||||
}
|
||||
var message = await fileService.EditImage(input.Provider, input.Model, input.Text, image);
|
||||
var message = await fileInstruct.EditImage(input.Provider, input.Model, input.Text, image);
|
||||
imageViewModel.Content = message.Content;
|
||||
imageViewModel.Images = message.GeneratedImages.Select(x => ImageViewModel.ToViewModel(x)).ToList();
|
||||
return imageViewModel;
|
||||
|
|
@ -198,7 +188,7 @@ public class InstructModeController : ControllerBase
|
|||
[HttpPost("/instruct/image-mask-edit")]
|
||||
public async Task<ImageGenerationViewModel> ImageMaskEdit([FromBody] IncomingMessageModel input)
|
||||
{
|
||||
var fileService = _services.GetRequiredService<IBotSharpFileService>();
|
||||
var fileInstruct = _services.GetRequiredService<IFileInstructService>();
|
||||
var state = _services.GetRequiredService<IConversationStateService>();
|
||||
input.States.ForEach(x => state.SetState(x.Key, x.Value, activeRounds: x.ActiveRounds, source: StateSource.External));
|
||||
var imageViewModel = new ImageGenerationViewModel();
|
||||
|
|
@ -211,7 +201,7 @@ public class InstructModeController : ControllerBase
|
|||
{
|
||||
return new ImageGenerationViewModel { Message = "Error! Cannot find an image or mask!" };
|
||||
}
|
||||
var message = await fileService.EditImage(input.Provider, input.Model, input.Text, image, mask);
|
||||
var message = await fileInstruct.EditImage(input.Provider, input.Model, input.Text, image, mask);
|
||||
imageViewModel.Content = message.Content;
|
||||
imageViewModel.Images = message.GeneratedImages.Select(x => ImageViewModel.ToViewModel(x)).ToList();
|
||||
return imageViewModel;
|
||||
|
|
@ -236,8 +226,8 @@ public class InstructModeController : ControllerBase
|
|||
|
||||
try
|
||||
{
|
||||
var fileService = _services.GetRequiredService<IBotSharpFileService>();
|
||||
var content = await fileService.ReadPdf(input.Provider, input.Model, input.ModelId, input.Text, input.Files);
|
||||
var fileInstruct = _services.GetRequiredService<IFileInstructService>();
|
||||
var content = await fileInstruct.ReadPdf(input.Provider, input.Model, input.ModelId, input.Text, input.Files);
|
||||
viewModel.Content = content;
|
||||
return viewModel;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using BotSharp.Abstraction.Graph.Models;
|
||||
using BotSharp.Abstraction.Knowledges.Models;
|
||||
using BotSharp.Abstraction.Knowledges.Settings;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using BotSharp.Abstraction.VectorStorage.Models;
|
||||
using BotSharp.OpenAPI.ViewModels.Knowledges;
|
||||
|
||||
namespace BotSharp.OpenAPI.Controllers;
|
||||
|
||||
|
|
@ -17,77 +18,107 @@ public class KnowledgeBaseController : ControllerBase
|
|||
_services = services;
|
||||
}
|
||||
|
||||
[HttpGet("/knowledge/{agentId}")]
|
||||
public async Task<List<RetrievedResult>> RetrieveKnowledge([FromRoute] string agentId, [FromQuery(Name = "q")] string question)
|
||||
[HttpGet("knowledge/vector/collections")]
|
||||
public async Task<IEnumerable<string>> GetVectorCollections()
|
||||
{
|
||||
return await _knowledgeService.GetAnswer(new KnowledgeRetrievalModel
|
||||
{
|
||||
AgentId = agentId,
|
||||
Question = question
|
||||
});
|
||||
return await _knowledgeService.GetVectorCollections();
|
||||
}
|
||||
|
||||
[HttpPost("/knowledge-base/upload")]
|
||||
public async Task<IActionResult> UploadKnowledge(IFormFile file, [FromQuery] int? startPageNum, [FromQuery] int? endPageNum)
|
||||
[HttpPost("/knowledge/vector/{collection}/search")]
|
||||
public async Task<IEnumerable<VectorKnowledgeViewModel>> SearchVectorKnowledge([FromRoute] string collection, [FromBody] SearchVectorKnowledgeRequest request)
|
||||
{
|
||||
var setttings = _services.GetRequiredService<KnowledgeBaseSettings>();
|
||||
var textConverter = _services.GetServices<IPdf2TextConverter>()
|
||||
.First(x => x.GetType().FullName.EndsWith(setttings.Pdf2TextConverter));
|
||||
var options = new VectorSearchOptions
|
||||
{
|
||||
Fields = request.Fields,
|
||||
Limit = request.Limit ?? 5,
|
||||
Confidence = request.Confidence ?? 0.5f,
|
||||
WithVector = request.WithVector
|
||||
};
|
||||
|
||||
var results = await _knowledgeService.SearchVectorKnowledge(request.Text, collection, options);
|
||||
return results.Select(x => VectorKnowledgeViewModel.From(x)).ToList();
|
||||
}
|
||||
|
||||
[HttpPost("/knowledge/vector/{collection}/data")]
|
||||
public async Task<StringIdPagedItems<VectorKnowledgeViewModel>> GetVectorCollectionData([FromRoute] string collection, [FromBody] VectorFilter filter)
|
||||
{
|
||||
var data = await _knowledgeService.GetVectorCollectionData(collection, filter);
|
||||
var items = data.Items?.Select(x => VectorKnowledgeViewModel.From(x))?
|
||||
.ToList() ?? new List<VectorKnowledgeViewModel>();
|
||||
|
||||
return new StringIdPagedItems<VectorKnowledgeViewModel>
|
||||
{
|
||||
Count = data.Count,
|
||||
NextId = data.NextId,
|
||||
Items = items
|
||||
};
|
||||
}
|
||||
|
||||
[HttpDelete("/knowledge/vector/{collection}/data/{id}")]
|
||||
public async Task<bool> DeleteVectorCollectionData([FromRoute] string collection, [FromRoute] string id)
|
||||
{
|
||||
return await _knowledgeService.DeleteVectorCollectionData(collection, id);
|
||||
}
|
||||
|
||||
[HttpPost("/knowledge/vector/{collection}/upload")]
|
||||
public async Task<IActionResult> UploadVectorKnowledge([FromRoute] string collection, IFormFile file, [FromForm] int? startPageNum, [FromForm] int? endPageNum)
|
||||
{
|
||||
var setttings = _services.GetRequiredService<FileCoreSettings>();
|
||||
var textConverter = _services.GetServices<IPdf2TextConverter>().FirstOrDefault(x => x.Name == setttings.Pdf2TextConverter);
|
||||
|
||||
var filePath = Path.GetTempFileName();
|
||||
using (var stream = System.IO.File.Create(filePath))
|
||||
using (var stream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
await file.CopyToAsync(stream);
|
||||
await stream.FlushAsync();
|
||||
}
|
||||
|
||||
var content = await textConverter.ConvertPdfToText(filePath, startPageNum, endPageNum);
|
||||
|
||||
// Process uploaded files
|
||||
// Don't rely on or trust the FileName property without validation.
|
||||
|
||||
// Add FeedWithMetaData
|
||||
await _knowledgeService.EmbedKnowledge(new KnowledgeCreationModel
|
||||
await _knowledgeService.FeedVectorKnowledge(collection, new KnowledgeCreationModel
|
||||
{
|
||||
Content = content
|
||||
});
|
||||
|
||||
System.IO.File.Delete(filePath);
|
||||
return Ok(new { count = 1, file.Length });
|
||||
}
|
||||
|
||||
[HttpPost("/knowledge/{agentId}")]
|
||||
public async Task<IActionResult> FeedKnowledge([FromRoute] string agentId, List<IFormFile> files, [FromQuery] int? startPageNum, [FromQuery] int? endPageNum, [FromQuery] bool? paddleModel)
|
||||
[HttpPost("/knowledge/graph/search")]
|
||||
public async Task<GraphKnowledgeViewModel> SearchGraphKnowledge([FromBody] SearchGraphKnowledgeRequest request)
|
||||
{
|
||||
var setttings = _services.GetRequiredService<KnowledgeBaseSettings>();
|
||||
var textConverter = _services.GetServices<IPdf2TextConverter>().First(x => x.GetType().FullName.EndsWith(setttings.Pdf2TextConverter));
|
||||
long size = files.Sum(f => f.Length);
|
||||
|
||||
foreach (var formFile in files)
|
||||
var options = new GraphSearchOptions
|
||||
{
|
||||
var filePath = Path.GetTempFileName();
|
||||
Method = request.Method
|
||||
};
|
||||
|
||||
var result = await _knowledgeService.SearchGraphKnowledge(request.Query, options);
|
||||
return new GraphKnowledgeViewModel
|
||||
{
|
||||
Result = result.Result
|
||||
};
|
||||
}
|
||||
|
||||
using (var stream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
await formFile.CopyToAsync(stream);
|
||||
await stream.FlushAsync(); // Ensure all data is written to the file
|
||||
}
|
||||
[HttpPost("/knowledge/search")]
|
||||
public async Task<KnowledgeSearchViewModel> SearchKnowledge([FromBody] SearchKnowledgeRequest request)
|
||||
{
|
||||
var vectorOptions = new VectorSearchOptions
|
||||
{
|
||||
Fields = request.VectorParams.Fields,
|
||||
Limit = request.VectorParams.Limit ?? 5,
|
||||
Confidence = request.VectorParams.Confidence ?? 0.5f,
|
||||
WithVector = request.VectorParams.WithVector
|
||||
};
|
||||
|
||||
var content = await textConverter.ConvertPdfToText(filePath, startPageNum, endPageNum);
|
||||
var graphOptions = new GraphSearchOptions
|
||||
{
|
||||
Method = request.GraphParams.Method
|
||||
};
|
||||
|
||||
// Process uploaded files
|
||||
// Don't rely on or trust the FileName property without validation.
|
||||
|
||||
// Add FeedWithMetaData
|
||||
await _knowledgeService.Feed(new KnowledgeFeedModel
|
||||
{
|
||||
AgentId = agentId,
|
||||
Content = content
|
||||
});
|
||||
|
||||
// Delete the temp file after processing to clean up
|
||||
System.IO.File.Delete(filePath);
|
||||
}
|
||||
|
||||
return Ok(new { count = files.Count, size });
|
||||
var result = await _knowledgeService.SearchKnowledge(request.Text, request.VectorParams.Collection, vectorOptions, graphOptions);
|
||||
return new KnowledgeSearchViewModel
|
||||
{
|
||||
VectorResult = result?.VectorResult?.Select(x => VectorKnowledgeViewModel.From(x)),
|
||||
GraphResult = result?.GraphResult != null ? new GraphKnowledgeViewModel { Result = result.GraphResult.Result } : null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,15 +137,15 @@ public class UserController : ControllerBase
|
|||
[HttpPost("/user/avatar")]
|
||||
public bool UploadUserAvatar([FromBody] BotSharpFile file)
|
||||
{
|
||||
var fileService = _services.GetRequiredService<IBotSharpFileService>();
|
||||
return fileService.SaveUserAvatar(file);
|
||||
var fileStorage = _services.GetRequiredService<IFileStorageService>();
|
||||
return fileStorage.SaveUserAvatar(file);
|
||||
}
|
||||
|
||||
[HttpGet("/user/avatar")]
|
||||
public IActionResult GetUserAvatar()
|
||||
{
|
||||
var fileService = _services.GetRequiredService<IBotSharpFileService>();
|
||||
var file = fileService.GetUserAvatar();
|
||||
var fileStorage = _services.GetRequiredService<IFileStorageService>();
|
||||
var file = fileStorage.GetUserAvatar();
|
||||
if (string.IsNullOrEmpty(file))
|
||||
{
|
||||
return NotFound();
|
||||
|
|
@ -158,8 +158,8 @@ public class UserController : ControllerBase
|
|||
#region Private methods
|
||||
private FileContentResult BuildFileResult(string file)
|
||||
{
|
||||
var fileService = _services.GetRequiredService<IBotSharpFileService>();
|
||||
var bytes = fileService.GetFileBytes(file);
|
||||
var fileStorage = _services.GetRequiredService<IFileStorageService>();
|
||||
var bytes = fileStorage.GetFileBytes(file);
|
||||
return File(bytes, "application/octet-stream", Path.GetFileName(file));
|
||||
}
|
||||
#endregion
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Routing.Models;
|
||||
using BotSharp.Core.Infrastructures;
|
||||
|
||||
namespace BotSharp.OpenAPI.ViewModels.Agents;
|
||||
|
||||
|
|
@ -16,21 +15,26 @@ public class AgentCreationModel
|
|||
/// </summary>
|
||||
public string Instruction { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public List<ChannelInstruction> ChannelInstructions { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// LLM extensible Instructions in addition to the default Instructions
|
||||
/// </summary>
|
||||
public List<AgentTemplate> Templates { get; set; } = new List<AgentTemplate>();
|
||||
public List<AgentTemplate> Templates { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// LLM callable function definition
|
||||
/// </summary>
|
||||
public List<FunctionDef> Functions { get; set; } = new List<FunctionDef>();
|
||||
public List<FunctionDef> Functions { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Response template
|
||||
/// </summary>
|
||||
public List<AgentResponse> Responses { get; set; } = new List<AgentResponse>();
|
||||
public List<string> Samples { get; set; } = new List<string>();
|
||||
public List<AgentResponse> Responses { get; set; } = new();
|
||||
public List<string> Samples { get; set; } = new();
|
||||
|
||||
public bool IsPublic { get; set; }
|
||||
|
||||
|
|
@ -43,9 +47,9 @@ public class AgentCreationModel
|
|||
/// <summary>
|
||||
/// Combine different Agents together to form a Profile.
|
||||
/// </summary>
|
||||
public List<string> Profiles { get; set; } = new List<string>();
|
||||
public List<string> Utilities { get; set; } = new List<string>();
|
||||
public List<RoutingRuleUpdateModel> RoutingRules { get; set; } = new List<RoutingRuleUpdateModel>();
|
||||
public List<string> Profiles { get; set; } = new();
|
||||
public List<string> Utilities { get; set; } = new();
|
||||
public List<RoutingRuleUpdateModel> RoutingRules { get; set; } = new();
|
||||
public AgentLlmConfig? LlmConfig { get; set; }
|
||||
|
||||
public Agent ToAgent()
|
||||
|
|
@ -55,6 +59,7 @@ public class AgentCreationModel
|
|||
Name = Name,
|
||||
Description = Description,
|
||||
Instruction = Instruction,
|
||||
ChannelInstructions = ChannelInstructions,
|
||||
Templates = Templates,
|
||||
Functions = Functions,
|
||||
Responses = Responses,
|
||||
|
|
@ -64,9 +69,7 @@ public class AgentCreationModel
|
|||
Type = Type,
|
||||
Disabled = Disabled,
|
||||
Profiles = Profiles,
|
||||
RoutingRules = RoutingRules?
|
||||
.Select(x => RoutingRuleUpdateModel.ToDomainElement(x))?
|
||||
.ToList() ?? new List<RoutingRule>(),
|
||||
RoutingRules = RoutingRules?.Select(x => RoutingRuleUpdateModel.ToDomainElement(x))?.ToList() ?? new List<RoutingRule>(),
|
||||
LlmConfig = LlmConfig
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,12 @@ public class AgentUpdateModel
|
|||
/// </summary>
|
||||
public string Instruction { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Channel instructions
|
||||
/// </summary>
|
||||
[JsonPropertyName("channel_instructions")]
|
||||
public List<ChannelInstruction>? ChannelInstructions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Templates
|
||||
/// </summary>
|
||||
|
|
@ -39,11 +45,11 @@ public class AgentUpdateModel
|
|||
/// Routes
|
||||
/// </summary>
|
||||
public List<AgentResponse>? Responses { get; set; }
|
||||
|
||||
[JsonPropertyName("is_public")]
|
||||
|
||||
public bool IsPublic { get; set; }
|
||||
[JsonPropertyName("allow_routing")]
|
||||
|
||||
[JsonPropertyName("allow_routing")]
|
||||
public bool AllowRouting { get; set; }
|
||||
|
||||
public bool Disabled { get; set; }
|
||||
|
|
@ -52,8 +58,8 @@ public class AgentUpdateModel
|
|||
/// Profile by channel
|
||||
/// </summary>
|
||||
public List<string>? Profiles { get; set; }
|
||||
[JsonPropertyName("routing_rules")]
|
||||
|
||||
[JsonPropertyName("routing_rules")]
|
||||
public List<RoutingRuleUpdateModel>? RoutingRules { get; set; }
|
||||
|
||||
[JsonPropertyName("llm_config")]
|
||||
|
|
@ -69,10 +75,9 @@ public class AgentUpdateModel
|
|||
Disabled = Disabled,
|
||||
Type = Type,
|
||||
Profiles = Profiles ?? new List<string>(),
|
||||
RoutingRules = RoutingRules?
|
||||
.Select(x => RoutingRuleUpdateModel.ToDomainElement(x))?
|
||||
.ToList() ?? new List<RoutingRule>(),
|
||||
RoutingRules = RoutingRules?.Select(x => RoutingRuleUpdateModel.ToDomainElement(x))?.ToList() ?? new List<RoutingRule>(),
|
||||
Instruction = Instruction ?? string.Empty,
|
||||
ChannelInstructions = ChannelInstructions ?? new List<ChannelInstruction>(),
|
||||
Templates = Templates ?? new List<AgentTemplate>(),
|
||||
Functions = Functions ?? new List<FunctionDef>(),
|
||||
Responses = Responses ?? new List<AgentResponse>(),
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@ public class AgentViewModel
|
|||
public string Description { get; set; }
|
||||
public string Type { get; set; } = AgentType.Task;
|
||||
public string Instruction { get; set; }
|
||||
|
||||
[JsonPropertyName("channel_instructions")]
|
||||
public List<ChannelInstruction> ChannelInstructions { get; set; }
|
||||
public List<AgentTemplate> Templates { get; set; }
|
||||
public List<FunctionDef> Functions { get; set; }
|
||||
public List<AgentResponse> Responses { get; set; }
|
||||
|
|
@ -60,6 +63,7 @@ public class AgentViewModel
|
|||
Description = agent.Description,
|
||||
Type = agent.Type,
|
||||
Instruction = agent.Instruction,
|
||||
ChannelInstructions = agent.ChannelInstructions,
|
||||
Templates = agent.Templates,
|
||||
Functions = agent.Functions,
|
||||
Responses = agent.Responses,
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ public class MessageFileViewModel
|
|||
[JsonPropertyName("file_name")]
|
||||
public string FileName { get; set; }
|
||||
|
||||
[JsonPropertyName("file_type")]
|
||||
public string FileType { get; set; }
|
||||
[JsonPropertyName("file_extension")]
|
||||
public string FileExtension { get; set; }
|
||||
|
||||
[JsonPropertyName("content_type")]
|
||||
public string ContentType { get; set; }
|
||||
|
|
@ -30,7 +30,7 @@ public class MessageFileViewModel
|
|||
{
|
||||
FileUrl = model.FileUrl,
|
||||
FileName = model.FileName,
|
||||
FileType = model.FileType,
|
||||
FileExtension = model.FileExtension,
|
||||
ContentType = model.ContentType,
|
||||
FileSource = model.FileSource
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BotSharp.OpenAPI.ViewModels.Knowledges;
|
||||
|
||||
public class GraphKnowledgeViewModel
|
||||
{
|
||||
[JsonPropertyName("result")]
|
||||
public string Result { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BotSharp.OpenAPI.ViewModels.Knowledges;
|
||||
|
||||
public class KnowledgeSearchViewModel
|
||||
{
|
||||
[JsonPropertyName("vector_result")]
|
||||
public IEnumerable<VectorKnowledgeViewModel>? VectorResult { get; set; }
|
||||
|
||||
[JsonPropertyName("graph_result")]
|
||||
public GraphKnowledgeViewModel? GraphResult { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BotSharp.OpenAPI.ViewModels.Knowledges;
|
||||
|
||||
public class SearchGraphKnowledgeRequest
|
||||
{
|
||||
[JsonPropertyName("query")]
|
||||
public string Query { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("method")]
|
||||
public string Method { get; set; } = string.Empty;
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BotSharp.OpenAPI.ViewModels.Knowledges;
|
||||
|
||||
public class SearchKnowledgeRequest
|
||||
{
|
||||
[JsonPropertyName("text")]
|
||||
public string Text { get; set; } = string.Empty;
|
||||
|
||||
#region Vector
|
||||
[JsonPropertyName("vector_params")]
|
||||
public VectorParam VectorParams { get; set; }
|
||||
#endregion
|
||||
|
||||
#region Graph
|
||||
[JsonPropertyName("graph_params")]
|
||||
public GraphParam GraphParams { get; set; }
|
||||
#endregion
|
||||
}
|
||||
|
||||
public class VectorParam
|
||||
{
|
||||
[JsonPropertyName("collection")]
|
||||
public string Collection { get; set; }
|
||||
|
||||
[JsonPropertyName("fields")]
|
||||
public IEnumerable<string>? Fields { get; set; }
|
||||
|
||||
[JsonPropertyName("limit")]
|
||||
public int? Limit { get; set; } = 5;
|
||||
|
||||
[JsonPropertyName("confidence")]
|
||||
public float? Confidence { get; set; } = 0.5f;
|
||||
|
||||
[JsonPropertyName("with_vector")]
|
||||
public bool WithVector { get; set; }
|
||||
}
|
||||
|
||||
public class GraphParam
|
||||
{
|
||||
[JsonPropertyName("method")]
|
||||
public string Method { get; set; } = string.Empty;
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BotSharp.OpenAPI.ViewModels.Knowledges;
|
||||
|
||||
public class SearchVectorKnowledgeRequest
|
||||
{
|
||||
[JsonPropertyName("text")]
|
||||
public string Text { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("fields")]
|
||||
public IEnumerable<string>? Fields { get; set; }
|
||||
|
||||
[JsonPropertyName("limit")]
|
||||
public int? Limit { get; set; } = 5;
|
||||
|
||||
[JsonPropertyName("confidence")]
|
||||
public float? Confidence { get; set; } = 0.5f;
|
||||
|
||||
[JsonPropertyName("with_vector")]
|
||||
public bool WithVector { get; set; }
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue