diff --git a/BotSharp.sln b/BotSharp.sln
index b7545cdc..8dda48e8 100644
--- a/BotSharp.sln
+++ b/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}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs
index 8405d9b5..1fd7a38c 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs
@@ -20,6 +20,13 @@ public interface IAgentService
///
Task LoadAgent(string id);
+ ///
+ /// Inherit from an agent
+ ///
+ ///
+ ///
+ Task InheritAgent(Agent agent);
+
string RenderedInstruction(Agent agent);
string RenderedTemplate(Agent agent, string templateName);
diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs
index 01ec2acb..43f0f5ba 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs
@@ -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
///
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
- public AgentLlmConfig LlmConfig { get; set; }
- = new AgentLlmConfig();
+ public AgentLlmConfig LlmConfig { get; set; } = new();
///
/// Instruction
@@ -30,33 +28,35 @@ public class Agent
[JsonIgnore]
public string? Instruction { get; set; }
+ ///
+ /// Channel instructions
+ ///
+ [JsonIgnore]
+ public List ChannelInstructions { get; set; } = new();
+
///
/// Templates
///
[JsonIgnore]
- public List Templates { get; set; }
- = new List();
+ public List Templates { get; set; } = new();
///
/// Agent tasks
///
[JsonIgnore]
- public List Tasks { get; set; }
- = new List();
+ public List Tasks { get; set; } = new();
///
/// Samples
///
[JsonIgnore]
- public List Samples { get; set; }
- = new List();
+ public List Samples { get; set; } = new();
///
/// Functions
///
[JsonIgnore]
- public List Functions { get; set; }
- = new List();
+ public List Functions { get; set; } = new();
///
/// Responses
@@ -93,23 +93,20 @@ public class Agent
///
/// Agent utilities
///
- public List Utilities { get; set; }
- = new List();
+ public List Utilities { get; set; } = new();
///
/// Inherit from agent
///
public string? InheritAgentId { get; set; }
- public List RoutingRules { get; set; }
- = new List();
+ public List RoutingRules { get; set; } = new();
///
/// For rendering deferral
///
[JsonIgnore]
- public Dictionary TemplateDict { get; set; }
- = new Dictionary();
+ public Dictionary 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 instructions)
+ {
+ ChannelInstructions = instructions ?? new List();
+ return this;
+ }
+
public Agent SetTemplates(List templates)
{
Templates = templates ?? new List();
diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/ChannelInstruction.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/ChannelInstruction.cs
new file mode 100644
index 00000000..d4ff6c1f
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/ChannelInstruction.cs
@@ -0,0 +1,7 @@
+namespace BotSharp.Abstraction.Agents.Models;
+
+public class ChannelInstruction
+{
+ public string Channel { get; set; }
+ public string Instruction { get; set; }
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj b/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj
index 446b8869..2e3aa24c 100644
--- a/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj
+++ b/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj
@@ -25,11 +25,14 @@
+
+
+
diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebPageResponseHook.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebPageResponseHook.cs
index a7e33b22..4e32117b 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebPageResponseHook.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebPageResponseHook.cs
@@ -4,6 +4,6 @@ namespace BotSharp.Abstraction.Browsing;
public interface IWebPageResponseHook
{
- void OnDataFetched(MessageInfo message, string url, string postData, string responsData);
- T? GetResponse(MessageInfo message, string url, string? queryParameter = null);
+ void OnDataFetched(MessageInfo message, WebPageResponseData response);
+ T? GetResponse(MessageInfo message, WebPageResponseFilter filter);
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/PageActionArgs.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/PageActionArgs.cs
index 661b0da4..d260a09d 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/PageActionArgs.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/PageActionArgs.cs
@@ -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
///
public bool OpenNewTab { get; set; } = false;
+
+ public bool EnableResponseCallback { get; set; } = false;
+
+ ///
+ /// Exclude urls for XHR/ Fetch responses
+ ///
+ public string[]? ExcludeResponseUrls { get; set; }
+
+ ///
+ /// Only include urls for XHR/ Fetch responses
+ ///
+ public string[]? IncludeResponseUrls { get; set; }
+
+ ///
+ /// If set to true, the response will be stored in memory
+ ///
+ public bool ResponseInMemory { get; set; } = false;
+ public List? ResponseContainer { get; set; }
+
public bool UseExistingPage { get; set; } = false;
public bool WaitForNetworkIdle { get; set; } = true;
diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/WebPageResponseData.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/WebPageResponseData.cs
new file mode 100644
index 00000000..1d03b0b7
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/WebPageResponseData.cs
@@ -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;
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/WebPageResponseFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/WebPageResponseFilter.cs
new file mode 100644
index 00000000..cd97a01d
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/WebPageResponseFilter.cs
@@ -0,0 +1,7 @@
+namespace BotSharp.Abstraction.Browsing.Models;
+
+public class WebPageResponseFilter
+{
+ public string Url { get; set; } = null!;
+ public string[]? QueryParameters { get; set; }
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Files/Constants/FileConstants.cs b/src/Infrastructure/BotSharp.Abstraction/Files/Constants/FileConstants.cs
new file mode 100644
index 00000000..dab5bc81
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Files/Constants/FileConstants.cs
@@ -0,0 +1,9 @@
+namespace BotSharp.Abstraction.Files.Constants;
+
+public class FileConstants
+{
+ public static readonly IEnumerable AudioExtensions = new List
+ {
+ ".mp3", ".wav", ".flac", ".aac", ".ogg", ".wma"
+ };
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Files/Converters/IPdf2ImageConverter.cs b/src/Infrastructure/BotSharp.Abstraction/Files/Converters/IPdf2ImageConverter.cs
index 54ad3a6d..87df6137 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Files/Converters/IPdf2ImageConverter.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Files/Converters/IPdf2ImageConverter.cs
@@ -2,6 +2,8 @@ namespace BotSharp.Abstraction.Files.Converters;
public interface IPdf2ImageConverter
{
+ public string Name { get; }
+
///
/// Convert pdf pages to images, and return a list of image file paths
///
diff --git a/src/Infrastructure/BotSharp.Abstraction/Files/FileCoreSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Files/FileCoreSettings.cs
new file mode 100644
index 00000000..10ccd1a0
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Files/FileCoreSettings.cs
@@ -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; }
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Files/FileStorageSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Files/FileStorageSettings.cs
deleted file mode 100644
index 23ba12c6..00000000
--- a/src/Infrastructure/BotSharp.Abstraction/Files/FileStorageSettings.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using BotSharp.Abstraction.Repositories.Enums;
-
-namespace BotSharp.Abstraction.Files;
-
-public class FileStorageSettings
-{
- public string Default { get; set; } = FileStorageEnum.LocalFileStorage;
-}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Files/IBotSharpFileService.cs b/src/Infrastructure/BotSharp.Abstraction/Files/IBotSharpFileService.cs
deleted file mode 100644
index dd91a2bb..00000000
--- a/src/Infrastructure/BotSharp.Abstraction/Files/IBotSharpFileService.cs
+++ /dev/null
@@ -1,82 +0,0 @@
-using System.IO;
-
-namespace BotSharp.Abstraction.Files;
-
-public interface IBotSharpFileService
-{
- #region Conversation
- ///
- /// 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.
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- Task> GetChatFiles(string conversationId, string source,
- IEnumerable conversations, IEnumerable contentTypes,
- bool includeScreenShot = false, int? offset = null);
-
- ///
- /// Get the files that have been uploaded in the chat. No screenshot images are included.
- ///
- ///
- ///
- ///
- ///
- ///
- IEnumerable GetMessageFiles(string conversationId, IEnumerable messageIds, string source, bool imageOnly = false);
- string GetMessageFile(string conversationId, string messageId, string source, string index, string fileName);
- IEnumerable GetMessagesWithFile(string conversationId, IEnumerable messageIds);
- bool SaveMessageFiles(string conversationId, string messageId, string source, List files);
-
- ///
- /// Delete files under messages
- ///
- /// Conversation Id
- /// Files in these messages will be deleted
- /// The starting message to delete
- /// If not null, delete messages while input a new message; otherwise, delete messages only
- ///
- bool DeleteMessageFiles(string conversationId, IEnumerable messageIds, string targetMessageId, string? newMessageId = null);
- bool DeleteConversationFiles(IEnumerable conversationIds);
- #endregion
-
- #region Image
- Task GenerateImage(string? provider, string? model, string text);
- Task VaryImage(string? provider, string? model, BotSharpFile image);
- Task EditImage(string? provider, string? model, string text, BotSharpFile image);
- Task EditImage(string? provider, string? model, string text, BotSharpFile image, BotSharpFile mask);
- #endregion
-
- #region Pdf
- ///
- /// Take screenshots of pdf pages and get response from llm
- ///
- ///
- /// Pdf files
- ///
- Task ReadPdf(string? provider, string? model, string? modelId, string prompt, List files);
- #endregion
-
- #region User
- string GetUserAvatar();
- bool SaveUserAvatar(BotSharpFile file);
- #endregion
-
- #region Common
- ///
- /// Get file bytes and content type from data, e.g., "data:image/png;base64,aaaaaaaaa"
- ///
- ///
- ///
- (string, byte[]) GetFileInfoFromData(string data);
- string GetDirectory(string conversationId);
- string GetFileContentType(string filePath);
- byte[] GetFileBytes(string fileStorageUrl);
- bool SavefileToPath(string filePath, Stream stream);
- #endregion
-}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Files/IFileInstructService.cs b/src/Infrastructure/BotSharp.Abstraction/Files/IFileInstructService.cs
new file mode 100644
index 00000000..78a400e1
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Files/IFileInstructService.cs
@@ -0,0 +1,26 @@
+namespace BotSharp.Abstraction.Files;
+
+public interface IFileInstructService
+{
+ #region Image
+ Task ReadImages(string? provider, string? model, string text, IEnumerable images);
+ Task GenerateImage(string? provider, string? model, string text);
+ Task VaryImage(string? provider, string? model, BotSharpFile image);
+ Task EditImage(string? provider, string? model, string text, BotSharpFile image);
+ Task EditImage(string? provider, string? model, string text, BotSharpFile image, BotSharpFile mask);
+ #endregion
+
+ #region Pdf
+ ///
+ /// Take screenshots of pdf pages and get response from llm
+ ///
+ ///
+ /// Pdf files
+ ///
+ Task ReadPdf(string? provider, string? model, string? modelId, string prompt, List files);
+ #endregion
+
+ #region Select file
+ Task> SelectMessageFiles(string conversationId, SelectFileOptions options);
+ #endregion
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Files/IFileStorageService.cs b/src/Infrastructure/BotSharp.Abstraction/Files/IFileStorageService.cs
new file mode 100644
index 00000000..a3e5a32d
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Files/IFileStorageService.cs
@@ -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
+ ///
+ /// Get the message file screenshots for specific content types, e.g., pdf
+ ///
+ ///
+ ///
+ ///
+ Task> GetMessageFileScreenshots(string conversationId, IEnumerable messageIds);
+
+ ///
+ /// Get the files that have been uploaded in the chat. No screenshot images are included.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ IEnumerable GetMessageFiles(string conversationId, IEnumerable messageIds, string source, IEnumerable? contentTypes = null);
+ string GetMessageFile(string conversationId, string messageId, string source, string index, string fileName);
+ IEnumerable GetMessagesWithFile(string conversationId, IEnumerable messageIds);
+ bool SaveMessageFiles(string conversationId, string messageId, string source, List files);
+
+ ///
+ /// Delete files under messages
+ ///
+ /// Conversation Id
+ /// Files in these messages will be deleted
+ /// The starting message to delete
+ /// If not null, delete messages while input a new message; otherwise, delete messages only
+ ///
+ bool DeleteMessageFiles(string conversationId, IEnumerable messageIds, string targetMessageId, string? newMessageId = null);
+ bool DeleteConversationFiles(IEnumerable conversationIds);
+ #endregion
+
+
+ #region User
+ string GetUserAvatar();
+ bool SaveUserAvatar(BotSharpFile file);
+ #endregion
+ #region Speech
+ Task SaveSpeechFileAsync(string conversationId, string fileName, BinaryData data);
+ Task RetrieveSpeechFileAsync(string conversationId, string fileName);
+ #endregion
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Files/Models/BotSharpFile.cs b/src/Infrastructure/BotSharp.Abstraction/Files/Models/BotSharpFile.cs
index 7e556e67..11a11e46 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Files/Models/BotSharpFile.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Files/Models/BotSharpFile.cs
@@ -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;
-
- ///
- /// File data, e.g., "data:image/png;base64,aaaaaaaa"
- ///
- [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;
+
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Files/Models/FileBase.cs b/src/Infrastructure/BotSharp.Abstraction/Files/Models/FileBase.cs
new file mode 100644
index 00000000..3483921a
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Files/Models/FileBase.cs
@@ -0,0 +1,46 @@
+namespace BotSharp.Abstraction.Files.Models;
+
+public class FileBase
+{
+ ///
+ /// External file url
+ ///
+ [JsonPropertyName("file_url")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public string? FileUrl { get; set; } = string.Empty;
+
+ ///
+ /// Internal file storage url
+ ///
+ [JsonPropertyName("file_storage_url")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public string? FileStorageUrl { get; set; } = string.Empty;
+
+ ///
+ /// File name without extension
+ ///
+ [JsonPropertyName("file_name")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public string? FileName { get; set; } = string.Empty;
+
+ ///
+ /// File data, e.g., "data:image/png;base64,aaaaaaaa"
+ ///
+ [JsonPropertyName("file_data")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public string? FileData { get; set; } = string.Empty;
+
+ ///
+ /// File content type
+ ///
+ [JsonPropertyName("content_type")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public string? ContentType { get; set; } = string.Empty;
+
+ ///
+ /// File extension without dot
+ ///
+ [JsonPropertyName("file_extension")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public string? FileExtension { get; set; } = string.Empty;
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Files/Models/FileSelectContext.cs b/src/Infrastructure/BotSharp.Abstraction/Files/Models/FileSelectContext.cs
new file mode 100644
index 00000000..d13b4f1e
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Files/Models/FileSelectContext.cs
@@ -0,0 +1,8 @@
+namespace BotSharp.Abstraction.Files.Models;
+
+public class FileSelectContext
+{
+ [JsonPropertyName("selected_ids")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public IEnumerable? Selecteds { get; set; }
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Files/Models/MessageFileModel.cs b/src/Infrastructure/BotSharp.Abstraction/Files/Models/MessageFileModel.cs
index 7cd93269..2a0128b6 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Files/Models/MessageFileModel.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Files/Models/MessageFileModel.cs
@@ -1,34 +1,10 @@
namespace BotSharp.Abstraction.Files.Models;
-public class MessageFileModel
+public class MessageFileModel : FileBase
{
[JsonPropertyName("message_id")]
public string MessageId { get; set; }
- ///
- /// External file url
- ///
- [JsonPropertyName("file_url")]
- public string FileUrl { get; set; }
-
- ///
- /// Internal file storage url
- ///
- [JsonPropertyName("file_storage_url")]
- public string FileStorageUrl { get; set; }
-
- ///
- /// File name without extension
- ///
- [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}";
}
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Files/Models/SelectFileOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Files/Models/SelectFileOptions.cs
new file mode 100644
index 00000000..d61c1b7c
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Files/Models/SelectFileOptions.cs
@@ -0,0 +1,49 @@
+namespace BotSharp.Abstraction.Files.Models;
+
+public class SelectFileOptions
+{
+ ///
+ /// Llm provider
+ ///
+ public string? Provider { get; set; }
+
+ ///
+ /// Llm model id
+ ///
+ public string? ModelId { get; set; }
+
+ ///
+ /// Agent id
+ ///
+ public string? AgentId { get; set; }
+
+ ///
+ /// Template (prompt) name
+ ///
+ public string? Template { get; set; }
+
+ ///
+ /// Description that user provides to select files
+ ///
+ public string? Description { get; set; }
+
+ ///
+ /// Whether include bot generated files
+ ///
+ public bool IncludeBotFile { get; set; }
+
+ ///
+ /// Conversation breakpoint
+ ///
+ public bool FromBreakpoint { get; set; }
+
+ ///
+ /// Message offset from last
+ ///
+ public int? Offset { get; set; }
+
+ ///
+ /// File content types. If null, all types of files will be retrived
+ ///
+ public IEnumerable? ContentTypes { get; set; }
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Files/Utilities/FileUtility.cs b/src/Infrastructure/BotSharp.Abstraction/Files/Utilities/FileUtility.cs
new file mode 100644
index 00000000..df33906d
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Files/Utilities/FileUtility.cs
@@ -0,0 +1,40 @@
+using Microsoft.AspNetCore.StaticFiles;
+
+namespace BotSharp.Abstraction.Files.Utilities;
+
+public static class FileUtility
+{
+ ///
+ /// Get file bytes and content type from data, e.g., "data:image/png;base64,aaaaaaaaa"
+ ///
+ ///
+ ///
+ 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;
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionDef.cs b/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionDef.cs
index 4366d323..d5dabb3c 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionDef.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionDef.cs
@@ -8,7 +8,12 @@ public class FunctionDef
[JsonPropertyName("description")]
public string Description { get; set; } = null!;
+ [JsonPropertyName("channels")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public List? Channels { get; set; }
+
[JsonPropertyName("visibility_expression")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? VisibilityExpression { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
diff --git a/src/Infrastructure/BotSharp.Abstraction/Graph/IGraphDb.cs b/src/Infrastructure/BotSharp.Abstraction/Graph/IGraphDb.cs
new file mode 100644
index 00000000..64c5388b
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Graph/IGraphDb.cs
@@ -0,0 +1,10 @@
+using BotSharp.Abstraction.Graph.Models;
+
+namespace BotSharp.Abstraction.Graph;
+
+public interface IGraphDb
+{
+ public string Name { get; }
+
+ Task Search(string query, GraphSearchOptions options);
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Graph/Models/GraphSearchData.cs b/src/Infrastructure/BotSharp.Abstraction/Graph/Models/GraphSearchData.cs
new file mode 100644
index 00000000..b6a9b714
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Graph/Models/GraphSearchData.cs
@@ -0,0 +1,6 @@
+namespace BotSharp.Abstraction.Graph.Models;
+
+public class GraphSearchData
+{
+ public string Result { get; set; }
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Graph/Models/GraphSearchOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Graph/Models/GraphSearchOptions.cs
new file mode 100644
index 00000000..294aeea7
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Graph/Models/GraphSearchOptions.cs
@@ -0,0 +1,6 @@
+namespace BotSharp.Abstraction.Graph.Models;
+
+public class GraphSearchOptions
+{
+ public string Method { get; set; }
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Graph/Models/GraphSearchResult.cs b/src/Infrastructure/BotSharp.Abstraction/Graph/Models/GraphSearchResult.cs
new file mode 100644
index 00000000..e9985447
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Graph/Models/GraphSearchResult.cs
@@ -0,0 +1,6 @@
+namespace BotSharp.Abstraction.Graph.Models;
+
+public class GraphSearchResult
+{
+ public string Result { get; set; }
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Interpreters/Models/InterpretationRequest.cs b/src/Infrastructure/BotSharp.Abstraction/Interpreters/Models/InterpretationRequest.cs
new file mode 100644
index 00000000..5a8f7552
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Interpreters/Models/InterpretationRequest.cs
@@ -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!;
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Knowledges/Enums/KnowledgeCollectionName.cs b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Enums/KnowledgeCollectionName.cs
new file mode 100644
index 00000000..7d92e504
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Enums/KnowledgeCollectionName.cs
@@ -0,0 +1,6 @@
+namespace BotSharp.Abstraction.Knowledges.Enums;
+
+public static class KnowledgeCollectionName
+{
+ public static string BotSharp = nameof(BotSharp);
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Knowledges/Enums/KnowledgePayloadName.cs b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Enums/KnowledgePayloadName.cs
new file mode 100644
index 00000000..9d95967f
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Enums/KnowledgePayloadName.cs
@@ -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";
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeHook.cs b/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeHook.cs
index aefcdec6..3a5ab788 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeHook.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeHook.cs
@@ -1,5 +1,3 @@
-using BotSharp.Abstraction.Knowledges.Models;
-
namespace BotSharp.Abstraction.Knowledges;
public interface IKnowledgeHook
diff --git a/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs b/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs
index 5b2b795b..5bf7aced 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs
@@ -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> CollectChunkedKnowledge();
- Task EmbedKnowledge(List chunks);
-
- Task Feed(KnowledgeFeedModel knowledge);
- Task EmbedKnowledge(KnowledgeCreationModel knowledge);
- Task GetKnowledges(KnowledgeRetrievalModel retrievalModel);
- Task> GetAnswer(KnowledgeRetrievalModel retrievalModel);
+ Task> GetVectorCollections();
+ Task> SearchVectorKnowledge(string query, string collectionName, VectorSearchOptions options);
+ Task FeedVectorKnowledge(string collectionName, KnowledgeCreationModel model);
+ Task> GetVectorCollectionData(string collectionName, VectorFilter filter);
+ Task DeleteVectorCollectionData(string collectionName, string id);
+ Task SearchGraphKnowledge(string query, GraphSearchOptions options);
+ Task SearchKnowledge(string query, string collectionName, VectorSearchOptions vectorOptions, GraphSearchOptions graphOptions);
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Knowledges/IPdf2TextConverter.cs b/src/Infrastructure/BotSharp.Abstraction/Knowledges/IPdf2TextConverter.cs
index d2ca2940..b8f2d47b 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Knowledges/IPdf2TextConverter.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Knowledges/IPdf2TextConverter.cs
@@ -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 ConvertPdfToText(string filePath, int? startPageNum, int? endPageNum);
}
}
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/KnowledgeFeedModel.cs b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/KnowledgeFeedModel.cs
deleted file mode 100644
index 7e3a315e..00000000
--- a/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/KnowledgeFeedModel.cs
+++ /dev/null
@@ -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;
-}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/KnowledgeRetrievalModel.cs b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/KnowledgeRetrievalModel.cs
deleted file mode 100644
index 03f66eb5..00000000
--- a/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/KnowledgeRetrievalModel.cs
+++ /dev/null
@@ -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;
-}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/KnowledgeSearchResult.cs b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/KnowledgeSearchResult.cs
new file mode 100644
index 00000000..771b070b
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/KnowledgeSearchResult.cs
@@ -0,0 +1,10 @@
+using BotSharp.Abstraction.Graph.Models;
+using BotSharp.Abstraction.VectorStorage.Models;
+
+namespace BotSharp.Abstraction.Knowledges.Models;
+
+public class KnowledgeSearchResult
+{
+ public IEnumerable VectorResult { get; set; }
+ public GraphSearchResult GraphResult { get; set; }
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/RetrievedResult.cs b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/RetrievedResult.cs
deleted file mode 100644
index 296e1ed6..00000000
--- a/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/RetrievedResult.cs
+++ /dev/null
@@ -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; } = "";
-}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Knowledges/Settings/KnowledgeBaseSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Settings/KnowledgeBaseSettings.cs
index 97f7f55c..65bf7637 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Knowledges/Settings/KnowledgeBaseSettings.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Settings/KnowledgeBaseSettings.cs
@@ -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; }
+}
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Abstraction/MLTasks/ITextToSpeech.cs b/src/Infrastructure/BotSharp.Abstraction/MLTasks/ITextToSpeech.cs
new file mode 100644
index 00000000..344fad0e
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/MLTasks/ITextToSpeech.cs
@@ -0,0 +1,23 @@
+namespace BotSharp.Abstraction.MLTasks
+{
+ public interface ITextToSpeech
+ {
+ ///
+ /// The LLM provider like Microsoft Azure, OpenAI, ClaudAI
+ ///
+ string Provider { get; }
+
+ ///
+ /// Set model name, one provider can consume different model or version(s)
+ ///
+ /// deployment name
+ void SetModelName(string model);
+
+ Task GenerateSpeechFromTextAsync(string text, ITextToSpeechOptions? options = null);
+ }
+
+ public interface ITextToSpeechOptions
+ {
+
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Using.cs b/src/Infrastructure/BotSharp.Abstraction/Using.cs
index 89c9f7db..825f7d8f 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Using.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Using.cs
@@ -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;
\ No newline at end of file
+global using BotSharp.Abstraction.Files.Enums;
+global using BotSharp.Abstraction.Knowledges.Models;
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Abstraction/Utilities/StringIdPagination.cs b/src/Infrastructure/BotSharp.Abstraction/Utilities/StringIdPagination.cs
new file mode 100644
index 00000000..d8e4d355
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Utilities/StringIdPagination.cs
@@ -0,0 +1,15 @@
+namespace BotSharp.Abstraction.Utilities;
+
+public class StringIdPagination : Pagination
+{
+ [JsonPropertyName("start_id")]
+ public string? StartId { get; set; }
+}
+
+public class StringIdPagedItems : PagedItems
+{
+ public new ulong Count { get; set; }
+
+ [JsonPropertyName("next_id")]
+ public string? NextId { get; set; }
+}
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/IVectorDb.cs b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/IVectorDb.cs
index 68aaaa78..8aeb5ee9 100644
--- a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/IVectorDb.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/IVectorDb.cs
@@ -1,9 +1,15 @@
+using BotSharp.Abstraction.VectorStorage.Models;
+
namespace BotSharp.Abstraction.VectorStorage;
public interface IVectorDb
{
- Task> GetCollections();
+ string Name { get; }
+
+ Task> GetCollections();
+ Task> GetCollectionData(string collectionName, VectorFilter filter);
Task CreateCollection(string collectionName, int dim);
Task Upsert(string collectionName, string id, float[] vector, string text, Dictionary? payload = null);
- Task> Search(string collectionName, float[] vector, string returnFieldName, int limit = 5, float confidence = 0.5f);
+ Task> Search(string collectionName, float[] vector, IEnumerable? fields, int limit = 5, float confidence = 0.5f, bool withVector = false);
+ Task DeleteCollectionData(string collectionName, string id);
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorCollectionData.cs b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorCollectionData.cs
new file mode 100644
index 00000000..e623532a
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorCollectionData.cs
@@ -0,0 +1,9 @@
+namespace BotSharp.Abstraction.VectorStorage.Models;
+
+public class VectorCollectionData
+{
+ public string Id { get; set; }
+ public Dictionary Data { get; set; } = new();
+ public double? Score { get; set; }
+ public float[]? Vector { get; set; }
+}
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorFilter.cs b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorFilter.cs
new file mode 100644
index 00000000..91abcc25
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorFilter.cs
@@ -0,0 +1,7 @@
+namespace BotSharp.Abstraction.VectorStorage.Models;
+
+public class VectorFilter : StringIdPagination
+{
+ [JsonPropertyName("with_vector")]
+ public bool WithVector { get; set; }
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorSearchOptions.cs b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorSearchOptions.cs
new file mode 100644
index 00000000..64943dd1
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorSearchOptions.cs
@@ -0,0 +1,11 @@
+using BotSharp.Abstraction.Knowledges.Enums;
+
+namespace BotSharp.Abstraction.VectorStorage.Models;
+
+public class VectorSearchOptions
+{
+ public IEnumerable? Fields { get; set; } = new List { KnowledgePayloadName.Text, KnowledgePayloadName.Answer };
+ public int? Limit { get; set; } = 5;
+ public float? Confidence { get; set; } = 0.5f;
+ public bool WithVector { get; set; }
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorSearchResult.cs b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorSearchResult.cs
new file mode 100644
index 00000000..ce39edbf
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorSearchResult.cs
@@ -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
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs
index 59de3096..9e2e1d5f 100644
--- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs
+++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs
@@ -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) GetInstructionsFromFile(string fileDir)
{
- foreach (var dir in Directory.GetDirectories(filePath))
+ var defaultInstruction = string.Empty;
+ var channelInstructions = new List();
+
+ var instructionDir = Path.Combine(fileDir, "instructions");
+ if (!Directory.Exists(instructionDir))
{
- var agentJson = File.ReadAllText(Path.Combine(dir, "agent.json"));
- var agent = JsonSerializer.Deserialize(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 FetchTemplatesFromFile(string fileDir)
+ private List GetTemplatesFromFile(string fileDir)
{
var templates = new List();
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 FetchFunctionsFromFile(string fileDir)
+ private List GetFunctionsFromFile(string fileDir)
{
var functions = new List();
var functionDir = Path.Combine(fileDir, "functions");
@@ -125,7 +133,7 @@ public partial class AgentService
return functions;
}
- private List FetchResponsesFromFile(string fileDir)
+ private List GetResponsesFromFile(string fileDir)
{
var responses = new List();
var responseDir = Path.Combine(fileDir, "responses");
@@ -143,7 +151,7 @@ public partial class AgentService
return responses;
}
- private List FetchSamplesFromFile(string fileDir)
+ private List GetSamplesFromFile(string fileDir)
{
var file = Path.Combine(fileDir, "samples.txt");
if (!File.Exists(file)) return new List();
@@ -152,7 +160,7 @@ public partial class AgentService
return samples?.ToList() ?? new List();
}
- private List FetchTasksFromFile(string fileDir)
+ private List GetTasksFromFile(string fileDir)
{
var tasks = new List();
var taskDir = Path.Combine(fileDir, "tasks");
diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs
index 15ce6eca..04b5c22d 100644
--- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs
+++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs
@@ -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;
+ }
+ }
}
diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs
index 89c45510..f0a864a2 100644
--- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs
+++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs
@@ -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> AgentParameterTypes = new();
+
[MemoryCache(10 * 60, perInstanceCache: true)]
public async Task 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();
+ await InheritAgent(agent);
+ OverrideInstructionByChannel(agent);
+ AddOrUpdateParameters(agent);
// Populate state into dictionary
+ agent.TemplateDict = new Dictionary();
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();
+ 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 dict)
{
var conv = _services.GetRequiredService();
@@ -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 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 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);
+ }
}
diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs
index 0b61977e..10598d4e 100644
--- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs
+++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs
@@ -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)
diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs
index a0b2f130..e9617d17 100644
--- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs
+++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs
@@ -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();
+ 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();
var result = render.Render(def.VisibilityExpression, new Dictionary
{
{ "states", agent.TemplateDict }
});
- return result == "visible";
+ isRender = isRender && result == "visible";
}
- return true;
+ return isRender;
}
public FunctionParametersDef? RenderFunctionProperty(Agent agent, FunctionDef def)
diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs
index 6fd0f85e..fe532e28 100644
--- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs
+++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs
@@ -28,6 +28,7 @@ public partial class AgentService
record.Profiles = agent.Profiles ?? new List();
record.RoutingRules = agent.RoutingRules ?? new List();
record.Instruction = agent.Instruction ?? string.Empty;
+ record.ChannelInstructions = agent.ChannelInstructions ?? new List();
record.Functions = agent.Functions ?? new List();
record.Templates = agent.Templates ?? new List();
record.Responses = agent.Responses ?? new List();
@@ -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(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)
diff --git a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj
index 1b58a984..73bf035c 100644
--- a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj
+++ b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj
@@ -45,19 +45,28 @@
1701;1702
+
+
+
+
+
+
+
+
+
-
+
-
+
-
+
-
+
-
+
@@ -69,8 +78,9 @@
+
-
+
@@ -80,7 +90,7 @@
PreserveNewest
-
+
PreserveNewest
@@ -89,19 +99,19 @@
PreserveNewest
-
+
PreserveNewest
PreserveNewest
-
+
PreserveNewest
PreserveNewest
-
+
PreserveNewest
@@ -137,7 +147,7 @@
PreserveNewest
-
+
PreserveNewest
@@ -152,7 +162,10 @@
PreserveNewest
-
+
+ PreserveNewest
+
+
PreserveNewest
@@ -172,7 +185,6 @@
-
@@ -181,9 +193,4 @@
-
-
-
-
-
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs
index 26c62b62..36b588ae 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs
@@ -150,6 +150,7 @@ public partial class ConversationService
await HookEmitter.Emit(_services, async hook =>
await hook.OnConversationEnding(response)
);
+ response.FunctionName = "conversation_end";
}
}
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.TruncateMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.TruncateMessage.cs
index 451cdeed..ccc074e8 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.TruncateMessage.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.TruncateMessage.cs
@@ -5,10 +5,9 @@ public partial class ConversationService : IConversationService
public async Task TruncateConversation(string conversationId, string messageId, string? newMessageId = null)
{
var db = _services.GetRequiredService();
- var fileService = _services.GetRequiredService();
+ var fileStorage = _services.GetRequiredService();
var deleteMessageIds = db.TruncateConversation(conversationId, messageId, cleanLog: true);
-
- fileService.DeleteMessageFiles(conversationId, deleteMessageIds, messageId, newMessageId);
+ fileStorage.DeleteMessageFiles(conversationId, deleteMessageIds, messageId, newMessageId);
var hooks = _services.GetServices().ToList();
foreach (var hook in hooks)
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs
index 87beba41..f2a93b3f 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs
@@ -37,9 +37,9 @@ public partial class ConversationService : IConversationService
public async Task DeleteConversations(IEnumerable ids)
{
var db = _services.GetRequiredService();
- var fileService = _services.GetRequiredService();
+ var fileStorage = _services.GetRequiredService();
var isDeleted = db.DeleteConversations(ids);
- fileService.DeleteConversationFiles(ids);
+ fileStorage.DeleteConversationFiles(ids);
return await Task.FromResult(isDeleted);
}
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs
index 57f09c71..aa518ee7 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs
@@ -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;
+ }
}
diff --git a/src/Infrastructure/BotSharp.Core/Files/FileCorePlugin.cs b/src/Infrastructure/BotSharp.Core/Files/FileCorePlugin.cs
new file mode 100644
index 00000000..0b09d6aa
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core/Files/FileCorePlugin.cs
@@ -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();
+ }
+ services.AddScoped();
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Core/Files/FilePlugin.cs b/src/Infrastructure/BotSharp.Core/Files/FilePlugin.cs
deleted file mode 100644
index 90397b93..00000000
--- a/src/Infrastructure/BotSharp.Core/Files/FilePlugin.cs
+++ /dev/null
@@ -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();
- }
- }
-}
diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/BotSharpFileService.Common.cs b/src/Infrastructure/BotSharp.Core/Files/Services/BotSharpFileService.Common.cs
deleted file mode 100644
index be5f3180..00000000
--- a/src/Infrastructure/BotSharp.Core/Files/Services/BotSharpFileService.Common.cs
+++ /dev/null
@@ -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;
- }
-}
diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/BotSharpFileService.Pdf.cs b/src/Infrastructure/BotSharp.Core/Files/Services/BotSharpFileService.Pdf.cs
deleted file mode 100644
index daca7711..00000000
--- a/src/Infrastructure/BotSharp.Core/Files/Services/BotSharpFileService.Pdf.cs
+++ /dev/null
@@ -1,143 +0,0 @@
-using System.IO;
-
-namespace BotSharp.Core.Files.Services;
-
-public partial class BotSharpFileService
-{
- public async Task ReadPdf(string? provider, string? model, string? modelId, string prompt, List 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
- {
- 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> DownloadFiles(string dir, List files, string extension = "pdf")
- {
- if (string.IsNullOrWhiteSpace(dir) || files.IsNullOrEmpty())
- {
- return Enumerable.Empty();
- }
-
- var locs = new List();
- foreach (var file in files)
- {
- try
- {
- var bytes = new byte[0];
- if (!string.IsNullOrEmpty(file.FileUrl))
- {
- var http = _services.GetRequiredService();
- 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> ConvertPdfToImages(IEnumerable files)
- {
- var images = new List();
- 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
-}
diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/BotSharpFileService.Image.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Image.cs
similarity index 84%
rename from src/Infrastructure/BotSharp.Core/Files/Services/BotSharpFileService.Image.cs
rename to src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Image.cs
index 619360d2..c9d35cb7 100644
--- a/src/Infrastructure/BotSharp.Core/Files/Services/BotSharpFileService.Image.cs
+++ b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Image.cs
@@ -2,8 +2,24 @@ using System.IO;
namespace BotSharp.Core.Files.Services;
-public partial class BotSharpFileService
+public partial class FileInstructService
{
+ public async Task ReadImages(string? provider, string? model, string text, IEnumerable 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
+ {
+ new RoleDialogModel(AgentRole.User, text)
+ {
+ Files = images?.ToList() ?? new List()
+ }
+ });
+ return message;
+ }
+
public async Task 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;
diff --git a/src/Plugins/BotSharp.Plugin.TencentCos/Services/TencentCosService.Pdf.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Pdf.cs
similarity index 76%
rename from src/Plugins/BotSharp.Plugin.TencentCos/Services/TencentCosService.Pdf.cs
rename to src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Pdf.cs
index 1efbad6d..2aec257b 100644
--- a/src/Plugins/BotSharp.Plugin.TencentCos/Services/TencentCosService.Pdf.cs
+++ b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Pdf.cs
@@ -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 ReadPdf(string? provider, string? model, string? modelId, string prompt, List 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> DownloadFiles(string dir, List 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> ConvertPdfToImages(IEnumerable files)
{
var images = new List();
- var converter = GetPdf2ImageConverter();
+ var settings = _services.GetRequiredService();
+ var converter = _services.GetServices().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);
}
diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.SelectFile.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.SelectFile.cs
new file mode 100644
index 00000000..41e0becc
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.SelectFile.cs
@@ -0,0 +1,138 @@
+using BotSharp.Abstraction.MLTasks;
+using BotSharp.Abstraction.Templating;
+
+namespace BotSharp.Core.Files.Services;
+
+public partial class FileInstructService
+{
+ public async Task> SelectMessageFiles(string conversationId, SelectFileOptions options)
+ {
+ if (string.IsNullOrEmpty(conversationId))
+ {
+ return Enumerable.Empty();
+ }
+
+ var convService = _services.GetRequiredService();
+ 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();
+ }
+
+ return await SelectFiles(files, dialogs, options);
+ }
+
+ private IEnumerable MergeMessageFiles(IEnumerable messageIds, IEnumerable userFiles, IEnumerable botFiles)
+ {
+ var files = new List();
+
+ 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> SelectFiles(IEnumerable files, IEnumerable dialogs, SelectFileOptions options)
+ {
+ if (files.IsNullOrEmpty()) return new List();
+
+ var llmProviderService = _services.GetRequiredService();
+ var render = _services.GetRequiredService();
+ var db = _services.GetRequiredService();
+
+ 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
+ {
+ { "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 { message });
+ var content = response?.Content ?? string.Empty;
+ var selecteds = JsonSerializer.Deserialize(content, new JsonSerializerOptions
+ {
+ AllowTrailingCommas = true
+ });
+ var fids = selecteds?.Selecteds ?? new List();
+ 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();
+ }
+ }
+
+ private IEnumerable GetMessageIds(IEnumerable conversations, int? offset = null)
+ {
+ if (conversations.IsNullOrEmpty()) return Enumerable.Empty();
+
+ if (offset.HasValue && offset < 1)
+ {
+ offset = 1;
+ }
+
+ var messageIds = new List();
+ 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;
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.cs
new file mode 100644
index 00000000..acd0ddaa
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.cs
@@ -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 _logger;
+
+ private const string SESSION_FOLDER = "sessions";
+
+ public FileInstructService(
+ IFileStorageService fileStorate,
+ ILogger 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);
+ }
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Audio.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Audio.cs
new file mode 100644
index 00000000..e90c7812
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Audio.cs
@@ -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 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);
+ }
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Common.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Common.cs
new file mode 100644
index 00000000..c49edbce
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Common.cs
@@ -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);
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/BotSharpFileService.Conversation.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Conversation.cs
similarity index 63%
rename from src/Infrastructure/BotSharp.Core/Files/Services/BotSharpFileService.Conversation.cs
rename to src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Conversation.cs
index 1b98f0d9..8c2ed831 100644
--- a/src/Infrastructure/BotSharp.Core/Files/Services/BotSharpFileService.Conversation.cs
+++ b/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Conversation.cs
@@ -4,24 +4,22 @@ using System.IO;
namespace BotSharp.Core.Files.Services;
-public partial class BotSharpFileService
+public partial class LocalFileStorageService
{
- public async Task> GetChatFiles(string conversationId, string source,
- IEnumerable conversations, IEnumerable contentTypes,
- bool includeScreenShot = false, int? offset = null)
+ public async Task> GetMessageFileScreenshots(string conversationId, IEnumerable messageIds)
{
var files = new List();
- 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 GetMessageFiles(string conversationId, IEnumerable messageIds,
- string source, bool imageOnly = false)
+ string source, IEnumerable? contentTypes = null)
{
var files = new List();
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 GetMessageIds(IEnumerable conversations, int? offset = null)
+ private IEnumerable GetMessageIds(IEnumerable dialogs, int? offset = null)
{
- if (conversations.IsNullOrEmpty()) return Enumerable.Empty();
+ if (dialogs.IsNullOrEmpty()) return Enumerable.Empty();
- 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();
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> GetMessageFiles(string file, string fileDir, string contentType,
- string messageId, string source, bool includeScreenShot)
- {
- var files = new List();
-
- 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> ConvertPdfToImages(string pdfLoc, string imageLoc)
{
var converters = _services.GetServices();
@@ -364,8 +275,66 @@ public partial class BotSharpFileService
private IPdf2ImageConverter? GetPdf2ImageConverter()
{
- var converters = _services.GetServices();
- return converters.FirstOrDefault();
+ var settings = _services.GetRequiredService();
+ var converter = _services.GetServices().FirstOrDefault(x => x.Name == settings.Pdf2ImageConverter);
+ return converter;
+ }
+
+ private async Task> GetScreenshots(string file, string parentDir, string messageId, string source)
+ {
+ var files = new List();
+
+ 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
}
diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/BotSharpFileService.User.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.User.cs
similarity index 91%
rename from src/Infrastructure/BotSharp.Core/Files/Services/BotSharpFileService.User.cs
rename to src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.User.cs
index fa99b13f..43ff9eed 100644
--- a/src/Infrastructure/BotSharp.Core/Files/Services/BotSharpFileService.User.cs
+++ b/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.User.cs
@@ -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;
}
diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/BotSharpFileService.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.cs
similarity index 61%
rename from src/Infrastructure/BotSharp.Core/Files/Services/BotSharpFileService.cs
rename to src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.cs
index b7c9a946..750803c4 100644
--- a/src/Infrastructure/BotSharp.Core/Files/Services/BotSharpFileService.cs
+++ b/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.cs
@@ -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 _logger;
+ private readonly ILogger _logger;
private readonly string _baseDir;
- private readonly IEnumerable _imageTypes = new List
- {
- 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 logger,
+ ILogger 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
}
diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/CompletionProvider.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/CompletionProvider.cs
index dd6b99af..f6a12188 100644
--- a/src/Infrastructure/BotSharp.Core/Infrastructures/CompletionProvider.cs
+++ b/src/Infrastructure/BotSharp.Core/Infrastructures/CompletionProvider.cs
@@ -115,6 +115,22 @@ public class CompletionProvider
return completer;
}
+ public static ITextToSpeech GetTextToSpeech(
+ IServiceProvider services,
+ string provider,
+ string model)
+ {
+ var completions = services.GetServices();
+ var completer = completions.FirstOrDefault(x => x.Provider == provider);
+ if (completer == null)
+ {
+ var logger = services.GetRequiredService>();
+ 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,
diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/SettingService.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/SettingService.cs
index fe3d9007..cbde5f44 100644
--- a/src/Infrastructure/BotSharp.Core/Infrastructures/SettingService.cs
+++ b/src/Infrastructure/BotSharp.Core/Infrastructures/SettingService.cs
@@ -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)
{
diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs
index c0c59142..bb137279 100644
--- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs
+++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs
@@ -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 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 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(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 agents)
- {
- }
+ public void BulkInsertAgents(List agents) { }
- public void BulkInsertUserAgents(List userAgents)
- {
- }
+ public void BulkInsertUserAgents(List userAgents) { }
public bool DeleteAgents()
{
diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs
index 78bd5cc0..7e5b4d9d 100644
--- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs
+++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs
@@ -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(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) 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();
- 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 FetchFunctions(string fileDir)
@@ -298,13 +345,14 @@ public partial class FileRepository : IBotSharpRepository
var agent = JsonSerializer.Deserialize(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)
diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs
index 4a686c6b..e8431cce 100644
--- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs
+++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs
@@ -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")
diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs
index bf833c8a..2fa38215 100644
--- a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs
+++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs
@@ -205,11 +205,12 @@ public class UserService : IUserService
var config = _services.GetRequiredService();
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),
diff --git a/src/Infrastructure/BotSharp.Core/Using.cs b/src/Infrastructure/BotSharp.Core/Using.cs
index 88293d97..fa3e94d2 100644
--- a/src/Infrastructure/BotSharp.Core/Using.cs
+++ b/src/Infrastructure/BotSharp.Core/Using.cs
@@ -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;
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Core/data/agents/01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b/instruction.liquid b/src/Infrastructure/BotSharp.Core/data/agents/01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b/instructions/instruction.liquid
similarity index 100%
rename from src/Infrastructure/BotSharp.Core/data/agents/01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b/instruction.liquid
rename to src/Infrastructure/BotSharp.Core/data/agents/01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b/instructions/instruction.liquid
diff --git a/src/Infrastructure/BotSharp.Core/data/agents/01e2fc5c-2c89-4ec7-8470-7688608b496c/agent.json b/src/Infrastructure/BotSharp.Core/data/agents/01e2fc5c-2c89-4ec7-8470-7688608b496c/agent.json
index 536ca340..9fc0c389 100644
--- a/src/Infrastructure/BotSharp.Core/data/agents/01e2fc5c-2c89-4ec7-8470-7688608b496c/agent.json
+++ b/src/Infrastructure/BotSharp.Core/data/agents/01e2fc5c-2c89-4ec7-8470-7688608b496c/agent.json
@@ -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
+ }
}
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Core/data/agents/01e2fc5c-2c89-4ec7-8470-7688608b496c/instruction.liquid b/src/Infrastructure/BotSharp.Core/data/agents/01e2fc5c-2c89-4ec7-8470-7688608b496c/instructions/instruction.liquid
similarity index 100%
rename from src/Infrastructure/BotSharp.Core/data/agents/01e2fc5c-2c89-4ec7-8470-7688608b496c/instruction.liquid
rename to src/Infrastructure/BotSharp.Core/data/agents/01e2fc5c-2c89-4ec7-8470-7688608b496c/instructions/instruction.liquid
diff --git a/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-0af7-49e6-ad7a-a760bd12dc4d/instruction.liquid b/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-0af7-49e6-ad7a-a760bd12dc4d/instructions/instruction.liquid
similarity index 100%
rename from src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-0af7-49e6-ad7a-a760bd12dc4d/instruction.liquid
rename to src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-0af7-49e6-ad7a-a760bd12dc4d/instructions/instruction.liquid
diff --git a/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/instruction.liquid b/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/instructions/instruction.liquid
similarity index 100%
rename from src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/instruction.liquid
rename to src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/instructions/instruction.liquid
diff --git a/src/Infrastructure/BotSharp.Core/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/instruction.liquid b/src/Infrastructure/BotSharp.Core/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/instructions/instruction.liquid
similarity index 100%
rename from src/Infrastructure/BotSharp.Core/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/instruction.liquid
rename to src/Infrastructure/BotSharp.Core/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/instructions/instruction.liquid
diff --git a/src/Plugins/BotSharp.Plugin.EmailHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/select_attachment_prompt.liquid b/src/Infrastructure/BotSharp.Core/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/select_file_prompt.liquid
similarity index 97%
rename from src/Plugins/BotSharp.Plugin.EmailHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/select_attachment_prompt.liquid
rename to src/Infrastructure/BotSharp.Core/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/select_file_prompt.liquid
index f4295baa..57f9895c 100644
--- a/src/Plugins/BotSharp.Plugin.EmailHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/select_attachment_prompt.liquid
+++ b/src/Infrastructure/BotSharp.Core/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/select_file_prompt.liquid
@@ -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:
{
diff --git a/src/Infrastructure/BotSharp.Core/data/agents/dfd9b46d-d00c-40af-8a75-3fbdc2b89869/instruction.liquid b/src/Infrastructure/BotSharp.Core/data/agents/dfd9b46d-d00c-40af-8a75-3fbdc2b89869/instructions/instruction.liquid
similarity index 100%
rename from src/Infrastructure/BotSharp.Core/data/agents/dfd9b46d-d00c-40af-8a75-3fbdc2b89869/instruction.liquid
rename to src/Infrastructure/BotSharp.Core/data/agents/dfd9b46d-d00c-40af-8a75-3fbdc2b89869/instructions/instruction.liquid
diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs
index f2bbc2eb..f033e31c 100644
--- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs
+++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs
@@ -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 GetAgent([FromRoute] string id)
{
- var agents = await GetAgents(new AgentFilter
+ var pagedAgents = await _agentService.GetAgents(new AgentFilter
{
AgentIds = new List { 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();
+ 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> GetAgents([FromQuery] AgentFilter filter, [FromQuery] bool useHook = false)
+ public async Task> GetAgents([FromQuery] AgentFilter filter)
{
var agentSetting = _services.GetRequiredService();
var pagedAgents = await _agentService.GetAgents(filter);
-
- var items = new List();
- var agents = new List();
- 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();
return new PagedItems
{
Items = agents,
- Count = pagedAgents.Count
+ Count = pagedAgents?.Count ?? 0
};
}
diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs
index 22649d72..89cdc6e9 100644
--- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs
+++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs
@@ -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();
var agentService = _services.GetRequiredService();
- var fileService = _services.GetRequiredService();
+ var fileStorage = _services.GetRequiredService();
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();
foreach (var message in history)
@@ -348,15 +349,15 @@ public class ConversationController : ControllerBase
{
if (files != null && files.Length > 0)
{
- var fileService = _services.GetRequiredService();
- var dir = fileService.GetDirectory(conversationId);
+ var fileStorage = _services.GetRequiredService();
+ 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