diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs index c52f2d958..b45ff557a 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs @@ -1,4 +1,6 @@ using BotSharp.Abstraction.Agents.Options; +using BotSharp.Abstraction.Coding.Models; +using BotSharp.Abstraction.Coding.Options; using BotSharp.Abstraction.Functions.Models; using BotSharp.Abstraction.Plugins.Models; using BotSharp.Abstraction.Repositories.Filters; @@ -51,7 +53,7 @@ public interface IAgentService /// Original agent information Task GetAgent(string id); - Task DeleteAgent(string id); + Task DeleteAgent(string id, AgentDeleteOptions? options = null); Task UpdateAgent(Agent agent, AgentField updateField); /// @@ -75,4 +77,10 @@ Task> GetAgentCodeScripts(string agentId, AgentCodeScriptF Task UpdateAgentCodeScripts(string agentId, List codeScripts, AgentCodeScriptUpdateOptions? options = null) => Task.FromResult(false); + + Task DeleteAgentCodeScripts(string agentId, List? codeScripts = null) + => Task.FromResult(false); + + Task GenerateCodeScript(string agentId, string text, CodeProcessOptions? options = null) + => Task.FromResult(new CodeGenerationResult()); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentRule.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentRule.cs index 87a08a894..75c0985a8 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentRule.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentRule.cs @@ -3,11 +3,11 @@ namespace BotSharp.Abstraction.Agents.Models; public class AgentRule { [JsonPropertyName("trigger_name")] - public string TriggerName { get; set; } + public string TriggerName { get; set; } = string.Empty; [JsonPropertyName("disabled")] public bool Disabled { get; set; } [JsonPropertyName("criteria")] - public string Criteria { get; set; } + public string Criteria { get; set; } = string.Empty; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Options/AgentCodeScriptUpdateOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Options/AgentCodeScriptUpdateOptions.cs index 476e42fc9..153694e9d 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Options/AgentCodeScriptUpdateOptions.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Options/AgentCodeScriptUpdateOptions.cs @@ -4,5 +4,6 @@ namespace BotSharp.Abstraction.Agents.Options; public class AgentCodeScriptUpdateOptions : AgentCodeScriptDbUpdateOptions { + [JsonPropertyName("delete_if_not_included")] public bool DeleteIfNotIncluded { get; set; } -} +} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Options/AgentDeleteOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Options/AgentDeleteOptions.cs new file mode 100644 index 000000000..5df6737c7 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Options/AgentDeleteOptions.cs @@ -0,0 +1,14 @@ +namespace BotSharp.Abstraction.Agents.Options; + +public class AgentDeleteOptions +{ + [JsonPropertyName("delete_role_agents")] + public bool DeleteRoleAgents { get; set; } + + [JsonPropertyName("delete_user_agents")] + public bool DeleteUserAgents { get; set; } + + [JsonPropertyName("to_delete_code_scripts")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public List? ToDeleteCodeScripts { get; set; } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Chart/IBotSharpChartService.cs b/src/Infrastructure/BotSharp.Abstraction/Chart/IBotSharpChartService.cs deleted file mode 100644 index 0e1cab8c4..000000000 --- a/src/Infrastructure/BotSharp.Abstraction/Chart/IBotSharpChartService.cs +++ /dev/null @@ -1,15 +0,0 @@ -using BotSharp.Abstraction.Chart.Models; -using BotSharp.Abstraction.Chart.Options; - -namespace BotSharp.Abstraction.Chart; - -public interface IBotSharpChartService -{ - public string Provider { get; } - - Task GetConversationChartData(string conversationId, string messageId, ChartDataOptions options) - => throw new NotImplementedException(); - - Task GetConversationChartCode(string conversationId, string messageId, ChartCodeOptions options) - => throw new NotImplementedException(); -} diff --git a/src/Infrastructure/BotSharp.Abstraction/Chart/IChartProcessor.cs b/src/Infrastructure/BotSharp.Abstraction/Chart/IChartProcessor.cs new file mode 100644 index 000000000..7f8cfd4d8 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Chart/IChartProcessor.cs @@ -0,0 +1,12 @@ +using BotSharp.Abstraction.Chart.Models; +using BotSharp.Abstraction.Chart.Options; + +namespace BotSharp.Abstraction.Chart; + +public interface IChartProcessor +{ + public string Provider { get; } + + Task GetConversationChartDataAsync(string conversationId, string messageId, ChartDataOptions? options = null) + => throw new NotImplementedException(); +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Chart/Models/ChartCodeResult.cs b/src/Infrastructure/BotSharp.Abstraction/Chart/Models/ChartCodeResult.cs deleted file mode 100644 index 143ba218d..000000000 --- a/src/Infrastructure/BotSharp.Abstraction/Chart/Models/ChartCodeResult.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace BotSharp.Abstraction.Chart.Models; - -public class ChartCodeResult -{ - public string Code { get; set; } - public string Language { get; set; } -} diff --git a/src/Infrastructure/BotSharp.Abstraction/Coding/ICodeProcessor.cs b/src/Infrastructure/BotSharp.Abstraction/Coding/ICodeProcessor.cs index 1fe083f01..0ca0c54d3 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Coding/ICodeProcessor.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Coding/ICodeProcessor.cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.Coding.Models; using BotSharp.Abstraction.Coding.Options; using BotSharp.Abstraction.Coding.Responses; @@ -7,6 +8,23 @@ public interface ICodeProcessor { string Provider { get; } + /// + /// Run code script + /// + /// The code scirpt to run + /// Code script execution options + /// + /// Task RunAsync(string codeScript, CodeInterpretOptions? options = null) => throw new NotImplementedException(); + + /// + /// Generate code script + /// + /// User requirement to generate code script + /// Code script generation options + /// + /// + Task GenerateCodeScriptAsync(string text, CodeGenerationOptions? options = null) + => throw new NotImplementedException(); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Coding/Models/CodeGenerationResult.cs b/src/Infrastructure/BotSharp.Abstraction/Coding/Models/CodeGenerationResult.cs new file mode 100644 index 000000000..83544f985 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Coding/Models/CodeGenerationResult.cs @@ -0,0 +1,7 @@ +namespace BotSharp.Abstraction.Coding.Models; + +public class CodeGenerationResult : ResponseBase +{ + public string Content { get; set; } + public string Language { get; set; } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Coding/Options/CodeGenerationOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Coding/Options/CodeGenerationOptions.cs new file mode 100644 index 000000000..be6437041 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Coding/Options/CodeGenerationOptions.cs @@ -0,0 +1,32 @@ +namespace BotSharp.Abstraction.Coding.Options; + +public class CodeGenerationOptions : LlmConfigBase +{ + /// + /// Agent id to get instruction + /// + [JsonPropertyName("agent_id")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? AgentId { get; set; } + + /// + /// Template (prompt) name + /// + [JsonPropertyName("template_name")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? TemplateName { get; set; } + + /// + /// The programming language + /// + [JsonPropertyName("language")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Language { get; set; } = "python"; + + /// + /// Data that can be used to fill in the prompt + /// + [JsonPropertyName("data")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public Dictionary? Data { get; set; } +} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Abstraction/Coding/Options/CodeProcessOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Coding/Options/CodeProcessOptions.cs new file mode 100644 index 000000000..343447b6b --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Coding/Options/CodeProcessOptions.cs @@ -0,0 +1,28 @@ +namespace BotSharp.Abstraction.Coding.Options; + +public class CodeProcessOptions : CodeGenerationOptions +{ + /// + /// Code processor provider + /// + [JsonPropertyName("processor")] + public string? Processor { get; set; } + + /// + /// Whether to save the generated code script to db + /// + [JsonPropertyName("save_to_db")] + public bool SaveToDb { get; set; } + + /// + /// Code script name (e.g., demo.py) + /// + [JsonPropertyName("script_name")] + public string? ScriptName { get; set; } + + /// + /// Code script type (i.e., src, test) + /// + [JsonPropertyName("script_type")] + public string? ScriptType { get; set; } = AgentCodeScriptType.Src; +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Coding/Responses/CodeInterpretResponse.cs b/src/Infrastructure/BotSharp.Abstraction/Coding/Responses/CodeInterpretResponse.cs index a07c7a1fe..ef1881c11 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Coding/Responses/CodeInterpretResponse.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Coding/Responses/CodeInterpretResponse.cs @@ -1,8 +1,6 @@ namespace BotSharp.Abstraction.Coding.Responses; -public class CodeInterpretResponse +public class CodeInterpretResponse : ResponseBase { public string Result { get; set; } = string.Empty; - public bool Success { get; set; } - public string? ErrorMsg { get; set; } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Files/Options/SelectFileOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Files/Options/SelectFileOptions.cs index 2f8d348ea..2c769cb8d 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Files/Options/SelectFileOptions.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Files/Options/SelectFileOptions.cs @@ -13,7 +13,7 @@ public class SelectFileOptions : LlmConfigBase public string? TemplateName { get; set; } /// - /// Description that user provides to select files + /// User description to select files /// public string? Description { get; set; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Files/Responses/FileHandleResponse.cs b/src/Infrastructure/BotSharp.Abstraction/Files/Responses/FileHandleResponse.cs index 6957ed0fc..67de6eb7d 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Files/Responses/FileHandleResponse.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Files/Responses/FileHandleResponse.cs @@ -1,8 +1,6 @@ namespace BotSharp.Abstraction.Files.Responses; -public class FileHandleResponse +public class FileHandleResponse : ResponseBase { public string Result { get; set; } = string.Empty; - public bool Success { get; set; } - public string? ErrorMsg { get; set; } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Instructs/Contexts/CodeInstructContext.cs b/src/Infrastructure/BotSharp.Abstraction/Instructs/Contexts/CodeInstructContext.cs index d6dae85f6..77e37efaa 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Instructs/Contexts/CodeInstructContext.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Instructs/Contexts/CodeInstructContext.cs @@ -3,5 +3,6 @@ namespace BotSharp.Abstraction.Instructs.Contexts; public class CodeInstructContext { public string CodeScript { get; set; } + public string ScriptType { get; set; } public List Arguments { get; set; } = []; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Instructs/Options/CodeInstructOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Instructs/Options/CodeInstructOptions.cs index 2ea99275f..182196753 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Instructs/Options/CodeInstructOptions.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Instructs/Options/CodeInstructOptions.cs @@ -2,14 +2,30 @@ namespace BotSharp.Abstraction.Instructs.Options; public class CodeInstructOptions { + /// + /// Code processor provider + /// [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("processor")] public string? Processor { get; set; } + /// + /// Code script name + /// [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("code_script_name")] - public string? CodeScriptName { get; set; } + [JsonPropertyName("script_name")] + public string? ScriptName { get; set; } + /// + /// Code script name + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("script_type")] + public string? ScriptType { get; set; } + + /// + /// Arguments + /// [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("arguments")] public List? Arguments { get; set; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Instructs/Options/FileInstructOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Instructs/Options/FileInstructOptions.cs index 6e6ac1fd1..b673589db 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Instructs/Options/FileInstructOptions.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Instructs/Options/FileInstructOptions.cs @@ -2,6 +2,9 @@ namespace BotSharp.Abstraction.Instructs.Options; public class FileInstructOptions { + /// + /// File processor provider + /// [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("processor")] public string? Processor { get; set; } diff --git a/src/Infrastructure/BotSharp.Abstraction/MLTasks/Filters/LlmConfigFilter.cs b/src/Infrastructure/BotSharp.Abstraction/MLTasks/Filters/LlmConfigFilter.cs new file mode 100644 index 000000000..89ec04103 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/MLTasks/Filters/LlmConfigFilter.cs @@ -0,0 +1,13 @@ +using BotSharp.Abstraction.MLTasks.Settings; + +namespace BotSharp.Abstraction.MLTasks.Filters; + +public class LlmConfigFilter +{ + public List? Providers { get; set; } + public List? ModelIds { get; set; } + public List? ModelNames { get; set; } + public List? ModelTypes { get; set; } + public List? ModelCapabilities { get; set; } + public bool? MultiModal { get; set; } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/MLTasks/ILlmProviderService.cs b/src/Infrastructure/BotSharp.Abstraction/MLTasks/ILlmProviderService.cs index f70a9496e..da8a8f534 100644 --- a/src/Infrastructure/BotSharp.Abstraction/MLTasks/ILlmProviderService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/MLTasks/ILlmProviderService.cs @@ -1,12 +1,13 @@ +using BotSharp.Abstraction.MLTasks.Filters; using BotSharp.Abstraction.MLTasks.Settings; namespace BotSharp.Abstraction.MLTasks; public interface ILlmProviderService { - LlmModelSetting GetSetting(string provider, string model); + LlmModelSetting? GetSetting(string provider, string model); List GetProviders(); - LlmModelSetting GetProviderModel(string provider, string id, bool? multiModal = null, LlmModelType? modelType = null, bool imageGenerate = false); + LlmModelSetting? GetProviderModel(string provider, string id, bool? multiModal = null, LlmModelType? modelType = null, IEnumerable? capabilities = null); List GetProviderModels(string provider); - List GetLlmConfigs(LlmConfigOptions? options = null); + List GetLlmConfigs(LlmConfigFilter? filter = null); } diff --git a/src/Infrastructure/BotSharp.Abstraction/MLTasks/Settings/LlmConfigOptions.cs b/src/Infrastructure/BotSharp.Abstraction/MLTasks/Settings/LlmConfigOptions.cs deleted file mode 100644 index eae417a41..000000000 --- a/src/Infrastructure/BotSharp.Abstraction/MLTasks/Settings/LlmConfigOptions.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace BotSharp.Abstraction.MLTasks.Settings; - -public class LlmConfigOptions -{ - public LlmModelType? Type { get; set; } - public bool? MultiModal { get; set; } - public bool? ImageGeneration { get; set; } -} diff --git a/src/Infrastructure/BotSharp.Abstraction/MLTasks/Settings/LlmModelSetting.cs b/src/Infrastructure/BotSharp.Abstraction/MLTasks/Settings/LlmModelSetting.cs index 3249b9eb4..ff2c1d3e6 100644 --- a/src/Infrastructure/BotSharp.Abstraction/MLTasks/Settings/LlmModelSetting.cs +++ b/src/Infrastructure/BotSharp.Abstraction/MLTasks/Settings/LlmModelSetting.cs @@ -31,17 +31,13 @@ public class LlmModelSetting public string ApiKey { get; set; } = null!; public string? Endpoint { get; set; } public LlmModelType Type { get; set; } = LlmModelType.Chat; + public List Capabilities { get; set; } = []; /// - /// If true, allow sending images/vidoes to this model + /// If true, allow sending images/videos to this model /// public bool MultiModal { get; set; } - /// - /// If true, allow generating images - /// - public bool ImageGeneration { get; set; } - /// /// Settings for embedding /// @@ -173,10 +169,29 @@ public class LlmCostSetting public enum LlmModelType { + All = 0, Text = 1, Chat = 2, Image = 3, Embedding = 4, Audio = 5, Realtime = 6, + Web = 7 } + +public enum LlmModelCapability +{ + All = 0, + Text = 1, + Chat = 2, + ImageReading = 3, + ImageGeneration = 4, + ImageEdit = 5, + ImageVariation = 6, + Embedding = 7, + AudioTranscription = 8, + AudioGeneration = 9, + Realtime = 10, + WebSearch = 11, + PdfReading = 12 +} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Abstraction/Models/ResponseBase.cs b/src/Infrastructure/BotSharp.Abstraction/Models/ResponseBase.cs new file mode 100644 index 000000000..cb381aebe --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Models/ResponseBase.cs @@ -0,0 +1,11 @@ +namespace BotSharp.Abstraction.Models; + +public class ResponseBase +{ + [JsonPropertyName("success")] + public bool Success { get; set; } + + [JsonPropertyName("error_message")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? ErrorMsg { get; set; } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentCodeScriptFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentCodeScriptFilter.cs index 530bb4aa5..14848d717 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentCodeScriptFilter.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentCodeScriptFilter.cs @@ -7,6 +7,6 @@ public class AgentCodeScriptFilter public static AgentCodeScriptFilter Empty() { - return new AgentCodeScriptFilter(); + return new(); } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs index 31b1da97a..52f43fb6b 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.Agents.Options; using BotSharp.Abstraction.Knowledges.Filters; using BotSharp.Abstraction.Loggers.Models; using BotSharp.Abstraction.Plugins.Models; @@ -81,7 +82,7 @@ void BulkInsertUserAgents(List userAgents) => throw new NotImplementedException(); bool DeleteAgents() => throw new NotImplementedException(); - bool DeleteAgent(string agentId) + bool DeleteAgent(string agentId, AgentDeleteOptions? options = null) => throw new NotImplementedException(); List GetAgentResponses(string agentId, string prefix, string intent) => throw new NotImplementedException(); diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Options/AgentCodeScriptDbUpdateOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Options/AgentCodeScriptDbUpdateOptions.cs index 5f6045129..2dde049bf 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/Options/AgentCodeScriptDbUpdateOptions.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Options/AgentCodeScriptDbUpdateOptions.cs @@ -2,5 +2,6 @@ namespace BotSharp.Abstraction.Repositories.Options; public class AgentCodeScriptDbUpdateOptions { + [JsonPropertyName("is_upsert")] public bool IsUpsert { get; set; } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Utilities/ObjectExtensions.cs b/src/Infrastructure/BotSharp.Abstraction/Utilities/ObjectExtensions.cs index bed3b71ab..a36516c8a 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Utilities/ObjectExtensions.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Utilities/ObjectExtensions.cs @@ -6,7 +6,7 @@ namespace BotSharp.Abstraction.Utilities; public static class ObjectExtensions { - private static readonly JsonSerializerOptions DefaultJsonOptions = new() + private static readonly JsonSerializerOptions _defaultJsonOptions = new() { PropertyNameCaseInsensitive = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, @@ -14,28 +14,55 @@ public static class ObjectExtensions ReferenceHandler = ReferenceHandler.IgnoreCycles }; - public static T? DeepClone(this T? obj, Action? modifier = null, BotSharpOptions? options = null) where T : class + public static T? DeepClone(this T? inputObj, Action? modifier = null, BotSharpOptions? options = null) where T : class { - if (obj == null) + if (inputObj == null) { return null; } try { - var json = JsonSerializer.Serialize(obj, options?.JsonSerializerOptions ?? DefaultJsonOptions); - var newObj = JsonSerializer.Deserialize(json, options?.JsonSerializerOptions ?? DefaultJsonOptions); - if (modifier != null && newObj != null) + var json = JsonSerializer.Serialize(inputObj, options?.JsonSerializerOptions ?? _defaultJsonOptions); + var outputObj = JsonSerializer.Deserialize(json, options?.JsonSerializerOptions ?? _defaultJsonOptions); + if (modifier != null && outputObj != null) { - modifier(newObj); + modifier(outputObj); } - return newObj; + return outputObj; } catch (Exception ex) { - Console.WriteLine($"DeepClone Error in {nameof(DeepClone)}: {ex}"); + Console.WriteLine($"DeepClone Error in {nameof(DeepClone)} for {typeof(T).Name}: {ex}"); return null; } } + + public static TOutput? DeepClone(this TInput? inputObj, Action? modifier = null, BotSharpOptions? options = null) + where TInput : class + where TOutput : class + { + if (inputObj == null) + { + return null; + } + + try + { + var json = JsonSerializer.Serialize(inputObj, options?.JsonSerializerOptions ?? _defaultJsonOptions); + var outputObj = JsonSerializer.Deserialize(json, options?.JsonSerializerOptions ?? _defaultJsonOptions); + if (modifier != null && outputObj != null) + { + modifier(outputObj); + } + + return outputObj; + } + catch (Exception ex) + { + Console.WriteLine($"DeepClone Error in {nameof(DeepClone)} for {typeof(TInput).Name} and {typeof(TOutput).Name}: {ex}"); + return null; + } + } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Utilities/StringExtensions.cs b/src/Infrastructure/BotSharp.Abstraction/Utilities/StringExtensions.cs index 8e2fd9bbf..e26f663d1 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Utilities/StringExtensions.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Utilities/StringExtensions.cs @@ -11,12 +11,18 @@ public static class StringExtensions public static string SubstringMax(this string str, int maxLength) { if (string.IsNullOrEmpty(str)) + { return str; + } if (str.Length > maxLength) + { return str.Substring(0, maxLength); + } else + { return str; + } } public static string[] SplitByNewLine(this string input) @@ -39,14 +45,20 @@ public static string RemoveNewLine(this string input) public static bool IsEqualTo(this string? str1, string? str2, StringComparison option = StringComparison.OrdinalIgnoreCase) { - if (str1 == null) return str2 == null; + if (str1 == null) + { + return str2 == null; + } return str1.Equals(str2, option); } public static string CleanStr(this string? str) { - if (string.IsNullOrWhiteSpace(str)) return string.Empty; + if (string.IsNullOrWhiteSpace(str)) + { + return string.Empty; + } return str.Replace(" ", "").Replace("\t", "").Replace("\n", "").Replace("\r", ""); } diff --git a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorSearchParamModel.cs b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorSearchParamModel.cs new file mode 100644 index 000000000..8e14b05ca --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorSearchParamModel.cs @@ -0,0 +1,7 @@ +namespace BotSharp.Abstraction.VectorStorage.Models; + +public class VectorSearchParamModel +{ + [JsonPropertyName("exact_search")] + public bool? ExactSearch { get; set; } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Options/VectorSearchOptions.cs b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Options/VectorSearchOptions.cs index 1e0141a86..4ba0c81c8 100644 --- a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Options/VectorSearchOptions.cs +++ b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Options/VectorSearchOptions.cs @@ -10,6 +10,7 @@ public class VectorSearchOptions public int? Limit { get; set; } = 5; public float? Confidence { get; set; } = 0.5f; public bool WithVector { get; set; } + public VectorSearchParamModel? SearchParam { get; set; } public static VectorSearchOptions Default() { diff --git a/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs b/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs index 288458430..ccd89cf45 100644 --- a/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs +++ b/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs @@ -36,7 +36,8 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) await RunCronChecker(scope.ServiceProvider); await Task.Delay(1000, stoppingToken); }); - if (isLocked == false) + + if (!isLocked) { await Task.Delay(1000, stoppingToken); } diff --git a/src/Infrastructure/BotSharp.Core/Agents/AgentPlugin.cs b/src/Infrastructure/BotSharp.Core/Agents/AgentPlugin.cs index 41e13da4c..449db38da 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/AgentPlugin.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/AgentPlugin.cs @@ -5,6 +5,7 @@ using BotSharp.Abstraction.Templating; using BotSharp.Abstraction.Users.Enums; using BotSharp.Core.Agents.Hooks; +using BotSharp.Core.Coding; using Microsoft.Extensions.Configuration; namespace BotSharp.Core.Agents; @@ -48,6 +49,8 @@ public void RegisterDI(IServiceCollection services, IConfiguration config) render.RegisterType(typeof(AgentSettings)); return settingService.Bind("Agent"); }); + + services.AddSingleton(); } public bool AttachMenu(List menu) @@ -57,9 +60,10 @@ public bool AttachMenu(List menu) { SubMenu = new List { + new PluginMenuDef("Agents", link: "page/agent"), // icon: "bx bx-bot", new PluginMenuDef("Routing", link: "page/agent/router"), // icon: "bx bx-map-pin" - new PluginMenuDef("Evaluating", link: "page/agent/evaluator") { Roles = new List { UserRole.Root, UserRole.Admin } }, // icon: "bx bx-task" - new PluginMenuDef("Agents", link: "page/agent"), // icon: "bx bx-bot" + new PluginMenuDef("Evaluating", link: "page/agent/evaluator") { Roles = [UserRole.Root, UserRole.Admin] }, // icon: "bx bx-task" + new PluginMenuDef("Coding", link: "page/agent/code-scripts") { Roles = [UserRole.Root, UserRole.Admin] }, } }); diff --git a/src/Infrastructure/BotSharp.Core/Agents/Hooks/BasicAgentHook.cs b/src/Infrastructure/BotSharp.Core/Agents/Hooks/BasicAgentHook.cs index a8bafdd25..cadeb3b47 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Hooks/BasicAgentHook.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Hooks/BasicAgentHook.cs @@ -70,12 +70,18 @@ public override void OnAgentUtilityLoaded(Agent agent) foreach (var utility in innerUtilities) { var isVisible = agentService.RenderVisibility(utility.VisibilityExpression, renderDict); - if (!isVisible || utility.Items.IsNullOrEmpty()) continue; + if (!isVisible || utility.Items.IsNullOrEmpty()) + { + continue; + } foreach (var item in utility.Items) { isVisible = agentService.RenderVisibility(item.VisibilityExpression, renderDict); - if (!isVisible) continue; + if (!isVisible) + { + continue; + } if (item.FunctionName?.StartsWith(UTIL_PREFIX) == true) { diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CodeScripts.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CodeScripts.cs deleted file mode 100644 index 5c7c8bc00..000000000 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CodeScripts.cs +++ /dev/null @@ -1,46 +0,0 @@ -using BotSharp.Abstraction.Agents.Options; - -namespace BotSharp.Core.Agents.Services; - -public partial class AgentService -{ - public async Task> GetAgentCodeScripts(string agentId, AgentCodeScriptFilter? filter = null) - { - var db = _services.GetRequiredService(); - var scripts = db.GetAgentCodeScripts(agentId, filter); - return await Task.FromResult(scripts); - } - - public async Task GetAgentCodeScript(string agentId, string scriptName, string scriptType = AgentCodeScriptType.Src) - { - var db = _services.GetRequiredService(); - var script = db.GetAgentCodeScript(agentId, scriptName, scriptType); - return await Task.FromResult(script); - } - - public async Task UpdateAgentCodeScripts(string agentId, List codeScripts, AgentCodeScriptUpdateOptions? options = null) - { - if (string.IsNullOrWhiteSpace(agentId) || codeScripts.IsNullOrEmpty()) - { - return false; - } - - var db = _services.GetRequiredService(); - - var toDeleteScripts = new List(); - if (options?.DeleteIfNotIncluded == true) - { - var curDbScripts = await GetAgentCodeScripts(agentId); - var codePaths = codeScripts.Select(x => x.CodePath).ToList(); - toDeleteScripts = curDbScripts.Where(x => !codePaths.Contains(x.CodePath)).ToList(); - } - - var updateResult = db.UpdateAgentCodeScripts(agentId, codeScripts, options); - if (!toDeleteScripts.IsNullOrEmpty()) - { - db.DeleteAgentCodeScripts(agentId, toDeleteScripts); - } - - return updateResult; - } -} diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Coding.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Coding.cs new file mode 100644 index 000000000..aee641c8c --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Coding.cs @@ -0,0 +1,110 @@ +using BotSharp.Abstraction.Agents.Options; +using BotSharp.Abstraction.Coding; +using BotSharp.Abstraction.Coding.Options; + +namespace BotSharp.Core.Agents.Services; + +public partial class AgentService +{ + public async Task> GetAgentCodeScripts(string agentId, AgentCodeScriptFilter? filter = null) + { + var db = _services.GetRequiredService(); + var scripts = db.GetAgentCodeScripts(agentId, filter); + return await Task.FromResult(scripts); + } + + public async Task GetAgentCodeScript(string agentId, string scriptName, string scriptType = AgentCodeScriptType.Src) + { + var db = _services.GetRequiredService(); + var script = db.GetAgentCodeScript(agentId, scriptName, scriptType); + return await Task.FromResult(script); + } + + public async Task UpdateAgentCodeScripts(string agentId, List codeScripts, AgentCodeScriptUpdateOptions? options = null) + { + if (string.IsNullOrWhiteSpace(agentId)) + { + return false; + } + + codeScripts ??= new(); + var db = _services.GetRequiredService(); + + if (options?.DeleteIfNotIncluded == true && codeScripts.IsNullOrEmpty()) + { + // Delete all code scripts in this agent + db.DeleteAgentCodeScripts(agentId); + return true; + } + + var toDeleteScripts = new List(); + if (options?.DeleteIfNotIncluded == true) + { + var curDbScripts = await GetAgentCodeScripts(agentId); + var codePaths = codeScripts.Select(x => x.CodePath).ToList(); + toDeleteScripts = curDbScripts.Where(x => !codePaths.Contains(x.CodePath)).ToList(); + } + + var updateResult = db.UpdateAgentCodeScripts(agentId, codeScripts, options); + if (!toDeleteScripts.IsNullOrEmpty()) + { + db.DeleteAgentCodeScripts(agentId, toDeleteScripts); + } + + return updateResult; + } + + public async Task DeleteAgentCodeScripts(string agentId, List? codeScripts = null) + { + if (string.IsNullOrWhiteSpace(agentId)) + { + return false; + } + + var db = _services.GetRequiredService(); + var deleted = db.DeleteAgentCodeScripts(agentId, codeScripts); + return await Task.FromResult(deleted); + } + + public async Task GenerateCodeScript(string agentId, string text, CodeProcessOptions? options = null) + { + if (string.IsNullOrWhiteSpace(agentId)) + { + return new CodeGenerationResult + { + ErrorMsg = "Agent id cannot be empty." + }; + } + + var processor = options?.Processor ?? "botsharp-py-interpreter"; + var codeProcessor = _services.GetServices().FirstOrDefault(x => x.Provider.IsEqualTo(processor)); + if (codeProcessor == null) + { + var errorMsg = $"Unable to find code processor {processor}."; + _logger.LogWarning(errorMsg); + return new CodeGenerationResult + { + ErrorMsg = errorMsg + }; + } + + var result = await codeProcessor.GenerateCodeScriptAsync(text, options); + if (result.Success && options?.SaveToDb == true) + { + var db = _services.GetRequiredService(); + var scripts = new List + { + new AgentCodeScript + { + Name = options?.ScriptName ?? $"{Guid.NewGuid()}.py", + Content = result.Content, + ScriptType = options?.ScriptType ?? AgentCodeScriptType.Src + } + }; + var saved = db.UpdateAgentCodeScripts(agentId, scripts, new() { IsUpsert = true }); + result.Success = saved; + } + + return result; + } +} diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.DeleteAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.DeleteAgent.cs index 6783bf916..5237dbdc9 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.DeleteAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.DeleteAgent.cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.Agents.Options; using BotSharp.Abstraction.Users.Enums; using BotSharp.Abstraction.Users.Models; @@ -5,7 +6,7 @@ namespace BotSharp.Core.Agents.Services; public partial class AgentService { - public async Task DeleteAgent(string id) + public async Task DeleteAgent(string id, AgentDeleteOptions? options = null) { var userService = _services.GetRequiredService(); var auth = await userService.GetUserAuthorizations(new List { id }); @@ -15,7 +16,7 @@ public async Task DeleteAgent(string id) return false; } - var deleted = _db.DeleteAgent(id); + var deleted = _db.DeleteAgent(id, options); return await Task.FromResult(deleted); } } diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs index 045c71cf5..266f69935 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs @@ -62,7 +62,13 @@ public async Task RefreshAgents(IEnumerable? agentIds = null) var tasks = GetTasksFromFile(dir); var codeScripts = GetCodeScriptsFromFile(dir); - var isAgentDeleted = _db.DeleteAgent(agent.Id); + var isAgentDeleted = _db.DeleteAgent(agent.Id, options: new() + { + DeleteRoleAgents = false, + DeleteUserAgents = false, + ToDeleteCodeScripts = codeScripts + }); + if (isAgentDeleted) { await Task.Delay(100); diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs index 55980e963..83e83ef5a 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs @@ -155,7 +155,7 @@ public bool RenderVisibility(string? visibilityExpression, IDictionary(); - var copy = new Dictionary(dict); + var copy = dict != null ? new Dictionary(dict) : []; var result = render.Render(visibilityExpression, new Dictionary { { "states", copy } diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Audio.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Audio.cs index a62744a51..0cd8a8c70 100644 --- a/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Audio.cs +++ b/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Audio.cs @@ -15,7 +15,10 @@ public bool SaveSpeechFile(string conversationId, string fileName, BinaryData da } var filePath = Path.Combine(dir, fileName); - if (File.Exists(filePath)) return false; + if (File.Exists(filePath)) + { + return false; + } using var fs = File.Create(filePath); using var ds = data.ToStream(); diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Common.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Common.cs index 80a10deeb..afed03f54 100644 --- a/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Common.cs +++ b/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Common.cs @@ -40,7 +40,10 @@ public BinaryData GetFileBytes(string fileStorageUrl) public bool SaveFileStreamToPath(string filePath, Stream stream) { - if (string.IsNullOrEmpty(filePath)) return false; + if (string.IsNullOrEmpty(filePath)) + { + return false; + } using (var fileStream = new FileStream(filePath, FileMode.Create)) { diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Conversation.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Conversation.cs index e4f5fc5c9..23135e8c9 100644 --- a/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Conversation.cs +++ b/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Conversation.cs @@ -34,9 +34,9 @@ public async Task> GetMessageFileScreenshotsAsync( continue; } - foreach (var subDir in Directory.GetDirectories(dir)) + foreach (var subDir in Directory.EnumerateDirectories(dir)) { - var file = Directory.GetFiles(subDir).FirstOrDefault(); + var file = Directory.EnumerateFiles(subDir).FirstOrDefault(); if (file == null) { continue; @@ -79,7 +79,7 @@ public IEnumerable GetMessageFiles(string conversationId, IEnu var sources = options?.Sources != null ? options.Sources - : Directory.GetDirectories(baseDir).Select(x => x.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries).Last()); + : Directory.EnumerateDirectories(baseDir).Select(x => x.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries).Last()); if (sources.IsNullOrEmpty()) { continue; @@ -93,11 +93,11 @@ public IEnumerable GetMessageFiles(string conversationId, IEnu continue; } - foreach (var subDir in Directory.GetDirectories(dir)) + foreach (var subDir in Directory.EnumerateDirectories(dir)) { var fileIndex = subDir.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries).Last(); - foreach (var file in Directory.GetFiles(subDir)) + foreach (var file in Directory.EnumerateFiles(subDir)) { var contentType = FileUtility.GetFileContentType(file); if (options?.ContentTypes != null && !options.ContentTypes.Contains(contentType)) @@ -143,7 +143,7 @@ public string GetMessageFile(string conversationId, string messageId, string sou return string.Empty; } - var found = Directory.GetFiles(dir).FirstOrDefault(f => Path.GetFileNameWithoutExtension(f).IsEqualTo(fileName)); + var found = Directory.EnumerateFiles(dir).FirstOrDefault(f => Path.GetFileNameWithoutExtension(f).IsEqualTo(fileName)); return found; } @@ -308,9 +308,9 @@ private async Task> GetScreenshotsAsync(string fil var contentType = FileUtility.GetFileContentType(file); var screenshotDir = Path.Combine(parentDir, SCREENSHOT_FILE_FOLDER); - if (ExistDirectory(screenshotDir) && !Directory.GetFiles(screenshotDir).IsNullOrEmpty()) + if (ExistDirectory(screenshotDir)) { - foreach (var screenshot in Directory.GetFiles(screenshotDir)) + foreach (var screenshot in Directory.EnumerateFiles(screenshotDir)) { var fileName = Path.GetFileNameWithoutExtension(screenshot); var fileExtension = Path.GetExtension(screenshot).Substring(1); diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.KnowledgeBase.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.KnowledgeBase.cs index 8fc6bb0ac..72170fea0 100644 --- a/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.KnowledgeBase.cs +++ b/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.KnowledgeBase.cs @@ -46,7 +46,10 @@ public bool DeleteKnowledgeFile(string collectionName, string vectorStoreProvide } var dir = BuildKnowledgeCollectionFileDir(collectionName, vectorStoreProvider); - if (!ExistDirectory(dir)) return false; + if (!ExistDirectory(dir)) + { + return false; + } if (fileId == null) { diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.User.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.User.cs index 9384bd837..6935a1ad2 100644 --- a/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.User.cs +++ b/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.User.cs @@ -10,7 +10,10 @@ public string GetUserAvatar() var user = db.GetUserById(_user.Id); var dir = GetUserAvatarDir(user?.Id); - if (!ExistDirectory(dir)) return string.Empty; + if (!ExistDirectory(dir)) + { + return string.Empty; + } var found = Directory.GetFiles(dir).FirstOrDefault() ?? string.Empty; return found; @@ -18,7 +21,10 @@ public string GetUserAvatar() public bool SaveUserAvatar(FileDataModel file) { - if (file == null || string.IsNullOrEmpty(file.FileData)) return false; + if (file == null || string.IsNullOrEmpty(file.FileData)) + { + return false; + } try { @@ -26,7 +32,10 @@ public bool SaveUserAvatar(FileDataModel file) var user = db.GetUserById(_user.Id); var dir = GetUserAvatarDir(user?.Id); - if (string.IsNullOrEmpty(dir)) return false; + if (string.IsNullOrEmpty(dir)) + { + return false; + } if (Directory.Exists(dir)) { diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/CompletionProvider.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/CompletionProvider.cs index 60645bcf2..b5d989298 100644 --- a/src/Infrastructure/BotSharp.Core/Infrastructures/CompletionProvider.cs +++ b/src/Infrastructure/BotSharp.Core/Infrastructures/CompletionProvider.cs @@ -5,7 +5,8 @@ namespace BotSharp.Core.Infrastructures; public class CompletionProvider { - public static object GetCompletion(IServiceProvider services, + public static object GetCompletion( + IServiceProvider services, string? provider = null, string? model = null, AgentLlmConfig? agentConfig = null) @@ -42,7 +43,8 @@ public static object GetCompletion(IServiceProvider services, } } - public static IChatCompletion GetChatCompletion(IServiceProvider services, + public static IChatCompletion GetChatCompletion( + IServiceProvider services, string? provider = null, string? model = null, string? modelId = null, @@ -66,7 +68,8 @@ public static IChatCompletion GetChatCompletion(IServiceProvider services, return completer; } - public static ITextCompletion GetTextCompletion(IServiceProvider services, + public static ITextCompletion GetTextCompletion( + IServiceProvider services, string? provider = null, string? model = null, AgentLlmConfig? agentConfig = null) @@ -86,15 +89,16 @@ public static ITextCompletion GetTextCompletion(IServiceProvider services, return completer; } - public static IImageCompletion GetImageCompletion(IServiceProvider services, + public static IImageCompletion GetImageCompletion( + IServiceProvider services, string? provider = null, string? model = null, string? modelId = null, - bool imageGenerate = false) + IEnumerable? capabilities = null) { var completions = services.GetServices(); (provider, model) = GetProviderAndModel(services, provider: provider, - model: model, modelId: modelId, imageGenerate: imageGenerate); + model: model, modelId: modelId, capabilities: capabilities); var completer = completions.FirstOrDefault(x => x.Provider == provider); if (completer == null) @@ -107,7 +111,8 @@ public static IImageCompletion GetImageCompletion(IServiceProvider services, return completer; } - public static ITextEmbedding GetTextEmbedding(IServiceProvider services, + public static ITextEmbedding GetTextEmbedding( + IServiceProvider services, string? provider = null, string? model = null) { @@ -166,7 +171,8 @@ public static IAudioSynthesis GetAudioSynthesizer( return completer; } - public static IRealTimeCompletion GetRealTimeCompletion(IServiceProvider services, + public static IRealTimeCompletion GetRealTimeCompletion( + IServiceProvider services, string? provider = null, string? model = null, string? modelId = null, @@ -176,7 +182,7 @@ public static IRealTimeCompletion GetRealTimeCompletion(IServiceProvider service var completions = services.GetServices(); (provider, model) = GetProviderAndModel(services, provider: provider, model: model, modelId: modelId, multiModal: multiModal, - modelType: LlmModelType.Realtime, + modelType: LlmModelType.Realtime, agentConfig: agentConfig); var completer = completions.FirstOrDefault(x => x.Provider == provider); @@ -190,13 +196,14 @@ public static IRealTimeCompletion GetRealTimeCompletion(IServiceProvider service return completer; } - private static (string, string) GetProviderAndModel(IServiceProvider services, + private static (string, string) GetProviderAndModel( + IServiceProvider services, string? provider = null, string? model = null, string? modelId = null, bool? multiModal = null, LlmModelType? modelType = null, - bool imageGenerate = false, + IEnumerable? capabilities = null, AgentLlmConfig? agentConfig = null) { var agentSetting = services.GetRequiredService(); @@ -220,9 +227,9 @@ private static (string, string) GetProviderAndModel(IServiceProvider services, var modelIdentity = state.ContainsState("model_id") ? state.GetState("model_id") : modelId; var llmProviderService = services.GetRequiredService(); model = llmProviderService.GetProviderModel(provider, modelIdentity, - multiModal: multiModal, + multiModal: multiModal, modelType: modelType, - imageGenerate: imageGenerate)?.Name; + capabilities: capabilities)?.Name; } } diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/LlmProviderService.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/LlmProviderService.cs index 95cfd9c04..3445d3c00 100644 --- a/src/Infrastructure/BotSharp.Core/Infrastructures/LlmProviderService.cs +++ b/src/Infrastructure/BotSharp.Core/Infrastructures/LlmProviderService.cs @@ -1,4 +1,5 @@ using BotSharp.Abstraction.MLTasks; +using BotSharp.Abstraction.MLTasks.Filters; using BotSharp.Abstraction.MLTasks.Settings; using BotSharp.Abstraction.Settings; @@ -40,14 +41,12 @@ public List GetProviderModels(string provider) { var settingService = _services.GetRequiredService(); return settingService.Bind>($"LlmProviders") - .FirstOrDefault(x => x.Provider.Equals(provider)) - ?.Models ?? new List(); + .FirstOrDefault(x => x.Provider.Equals(provider))?.Models ?? []; } - public LlmModelSetting GetProviderModel(string provider, string id, bool? multiModal = null, LlmModelType? modelType = null, bool imageGenerate = false) + public LlmModelSetting? GetProviderModel(string provider, string id, bool? multiModal = null, LlmModelType? modelType = null, IEnumerable? capabilities = null) { - var models = GetProviderModels(provider) - .Where(x => x.Id == id); + var models = GetProviderModels(provider).Where(x => x.Id == id); if (multiModal.HasValue) { @@ -59,7 +58,15 @@ public LlmModelSetting GetProviderModel(string provider, string id, bool? multiM models = models.Where(x => x.Type == modelType.Value); } - models = models.Where(x => x.ImageGeneration == imageGenerate); + if (capabilities != null) + { + models = models.Where(x => x.Capabilities != null && capabilities.Any(y => x.Capabilities.Contains(y))); + } + + if (models.IsNullOrEmpty()) + { + return null; + } var random = new Random(); var index = random.Next(0, models.Count()); @@ -72,14 +79,14 @@ public LlmModelSetting GetProviderModel(string provider, string id, bool? multiM var settings = _services.GetRequiredService>(); var providerSetting = settings.FirstOrDefault(p => p.Provider.Equals(provider, StringComparison.CurrentCultureIgnoreCase)); + if (providerSetting == null) { _logger.LogError($"Can't find provider settings for {provider}"); return null; } - var modelSetting = providerSetting.Models.FirstOrDefault(m => - m.Name.Equals(model, StringComparison.CurrentCultureIgnoreCase)); + var modelSetting = providerSetting.Models.FirstOrDefault(m => m.Name.Equals(model, StringComparison.CurrentCultureIgnoreCase)); if (modelSetting == null) { _logger.LogError($"Can't find model settings for {provider}.{model}"); @@ -95,42 +102,67 @@ public LlmModelSetting GetProviderModel(string provider, string id, bool? multiM m.Group.Equals(modelSetting.Group, StringComparison.CurrentCultureIgnoreCase)) .ToList(); - // pick one model randomly - var random = new Random(); - var index = random.Next(0, models.Count()); - modelSetting = models.ElementAt(index); + if (!models.IsNullOrEmpty()) + { + // pick one model randomly + var random = new Random(); + var index = random.Next(0, models.Count()); + modelSetting = models.ElementAt(index); + } } return modelSetting; } - public List GetLlmConfigs(LlmConfigOptions? options = null) + public List GetLlmConfigs(LlmConfigFilter? filter = null) { var settingService = _services.GetRequiredService(); var providers = settingService.Bind>($"LlmProviders"); var configs = new List(); + var comparer = StringComparer.OrdinalIgnoreCase; + + if (providers.IsNullOrEmpty()) + { + return configs; + } - if (providers.IsNullOrEmpty()) return configs; + if (filter == null) + { + return providers ?? []; + } - if (options == null) return providers ?? []; + if (filter.Providers != null) + { + providers = providers.Where(x => filter.Providers.Contains(x.Provider, comparer)).ToList(); + } foreach (var provider in providers) { - var models = provider.Models ?? []; - if (options.Type.HasValue) + IEnumerable models = provider.Models ?? []; + if (filter.ModelTypes != null) + { + models = models.Where(x => filter.ModelTypes.Contains(x.Type)); + } + + if (filter.ModelIds != null) + { + models = models.Where(x => filter.ModelIds.Contains(x.Id, comparer)); + } + + if (filter.ModelNames != null) { - models = models.Where(x => x.Type == options.Type.Value).ToList(); + models = models.Where(x => filter.ModelNames.Contains(x.Name, comparer)); } - if (options.MultiModal.HasValue) + if (filter.ModelCapabilities != null) { - models = models.Where(x => x.MultiModal == options.MultiModal.Value).ToList(); + models = models.Where(x => x.Capabilities != null && filter.ModelCapabilities.Any(y => x.Capabilities.Contains(y))); } - if (options.ImageGeneration.HasValue) + if (filter.MultiModal.HasValue) { - models = models.Where(x => x.ImageGeneration == options.ImageGeneration.Value).ToList(); + models = models.Where(x => x.MultiModal == filter.MultiModal.Value); } if (models.IsNullOrEmpty()) @@ -138,7 +170,7 @@ public List GetLlmConfigs(LlmConfigOptions? options = null) continue; } - provider.Models = models; + provider.Models = models.ToList(); configs.Add(provider); } diff --git a/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs b/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs index 43accacce..8960ecf63 100644 --- a/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs +++ b/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs @@ -193,9 +193,9 @@ await hook.OnResponseGenerated(new InstructResponseModel // Get code script name var scriptName = string.Empty; - if (!string.IsNullOrEmpty(codeOptions?.CodeScriptName)) + if (!string.IsNullOrEmpty(codeOptions?.ScriptName)) { - scriptName = codeOptions.CodeScriptName; + scriptName = codeOptions.ScriptName; } else if (!string.IsNullOrEmpty(templateName)) { @@ -211,7 +211,8 @@ await hook.OnResponseGenerated(new InstructResponseModel } // Get code script - var codeScript = await agentService.GetAgentCodeScript(agent.Id, scriptName, scriptType: AgentCodeScriptType.Src); + var scriptType = codeOptions?.ScriptType ?? AgentCodeScriptType.Src; + var codeScript = await agentService.GetAgentCodeScript(agent.Id, scriptName, scriptType); if (string.IsNullOrWhiteSpace(codeScript)) { #if DEBUG @@ -230,6 +231,7 @@ await hook.OnResponseGenerated(new InstructResponseModel var context = new CodeInstructContext { CodeScript = codeScript, + ScriptType = scriptType, Arguments = arguments }; diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs index bd2599526..c6b1e3a45 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.Agents.Options; using BotSharp.Abstraction.Routing.Models; using System.IO; using System.Text.RegularExpressions; @@ -91,10 +92,16 @@ public void UpdateAgent(Agent agent, AgentField field) #region Update Agent Fields private void UpdateAgentName(string agentId, string name) { - if (string.IsNullOrWhiteSpace(name)) return; + if (string.IsNullOrWhiteSpace(name)) + { + return; + } var (agent, agentFile) = GetAgentFromFile(agentId); - if (agent == null) return; + if (agent == null) + { + return; + } agent.Name = name; agent.UpdatedDateTime = DateTime.UtcNow; @@ -104,10 +111,16 @@ private void UpdateAgentName(string agentId, string name) private void UpdateAgentDescription(string agentId, string description) { - if (string.IsNullOrWhiteSpace(description)) return; + if (string.IsNullOrWhiteSpace(description)) + { + return; + } var (agent, agentFile) = GetAgentFromFile(agentId); - if (agent == null) return; + if (agent == null) + { + return; + } agent.Description = description; agent.UpdatedDateTime = DateTime.UtcNow; @@ -118,7 +131,10 @@ private void UpdateAgentDescription(string agentId, string description) private void UpdateAgentIsPublic(string agentId, bool isPublic) { var (agent, agentFile) = GetAgentFromFile(agentId); - if (agent == null) return; + if (agent == null) + { + return; + } agent.IsPublic = isPublic; agent.UpdatedDateTime = DateTime.UtcNow; @@ -129,7 +145,10 @@ private void UpdateAgentIsPublic(string agentId, bool isPublic) private void UpdateAgentDisabled(string agentId, bool disabled) { var (agent, agentFile) = GetAgentFromFile(agentId); - if (agent == null) return; + if (agent == null) + { + return; + } agent.Disabled = disabled; agent.UpdatedDateTime = DateTime.UtcNow; @@ -140,7 +159,10 @@ private void UpdateAgentDisabled(string agentId, bool disabled) private void UpdateAgentType(string agentId, string type) { var (agent, agentFile) = GetAgentFromFile(agentId); - if (agent == null) return; + if (agent == null) + { + return; + } agent.Type = type; agent.UpdatedDateTime = DateTime.UtcNow; @@ -151,7 +173,10 @@ private void UpdateAgentType(string agentId, string type) private void UpdateAgentRoutingMode(string agentId, string? mode) { var (agent, agentFile) = GetAgentFromFile(agentId); - if (agent == null) return; + if (agent == null) + { + return; + } agent.Mode = mode; agent.UpdatedDateTime = DateTime.UtcNow; @@ -162,7 +187,10 @@ private void UpdateAgentRoutingMode(string agentId, string? mode) private void UpdateAgentFuncVisMode(string agentId, string? visMode) { var (agent, agentFile) = GetAgentFromFile(agentId); - if (agent == null) return; + if (agent == null) + { + return; + } agent.FuncVisMode = visMode; agent.UpdatedDateTime = DateTime.UtcNow; @@ -173,7 +201,10 @@ private void UpdateAgentFuncVisMode(string agentId, string? visMode) private void UpdateAgentInheritAgentId(string agentId, string? inheritAgentId) { var (agent, agentFile) = GetAgentFromFile(agentId); - if (agent == null) return; + if (agent == null) + { + return; + } agent.InheritAgentId = inheritAgentId; agent.UpdatedDateTime = DateTime.UtcNow; @@ -183,10 +214,16 @@ private void UpdateAgentInheritAgentId(string agentId, string? inheritAgentId) private void UpdateAgentProfiles(string agentId, List profiles) { - if (profiles == null) return; + if (profiles == null) + { + return; + } var (agent, agentFile) = GetAgentFromFile(agentId); - if (agent == null) return; + if (agent == null) + { + return; + } agent.Profiles = profiles; agent.UpdatedDateTime = DateTime.UtcNow; @@ -196,10 +233,16 @@ private void UpdateAgentProfiles(string agentId, List profiles) public bool UpdateAgentLabels(string agentId, List labels) { - if (labels == null) return false; + if (labels == null) + { + return false; + } var (agent, agentFile) = GetAgentFromFile(agentId); - if (agent == null) return false; + if (agent == null) + { + return false; + } agent.Labels = labels; agent.UpdatedDateTime = DateTime.UtcNow; @@ -210,10 +253,16 @@ public bool UpdateAgentLabels(string agentId, List labels) private void UpdateAgentUtilities(string agentId, bool mergeUtility, List utilities) { - if (utilities == null) return; + if (utilities == null) + { + return; + } var (agent, agentFile) = GetAgentFromFile(agentId); - if (agent == null) return; + if (agent == null) + { + return; + } agent.MergeUtility = mergeUtility; agent.Utilities = utilities; @@ -224,11 +273,16 @@ private void UpdateAgentUtilities(string agentId, bool mergeUtility, List mcptools) { - if (mcptools == null) return; + if (mcptools == null) + { + return; + } var (agent, agentFile) = GetAgentFromFile(agentId); - if (agent == null) return; - + if (agent == null) + { + return; + } agent.McpTools = mcptools; agent.UpdatedDateTime = DateTime.UtcNow; @@ -238,10 +292,16 @@ private void UpdateAgentMcpTools(string agentId, List mcptools) private void UpdateAgentKnowledgeBases(string agentId, List knowledgeBases) { - if (knowledgeBases == null) return; + if (knowledgeBases == null) + { + return; + } var (agent, agentFile) = GetAgentFromFile(agentId); - if (agent == null) return; + if (agent == null) + { + return; + } agent.KnowledgeBases = knowledgeBases; agent.UpdatedDateTime = DateTime.UtcNow; @@ -251,10 +311,16 @@ private void UpdateAgentKnowledgeBases(string agentId, List private void UpdateAgentRules(string agentId, List rules) { - if (rules == null) return; + if (rules == null) + { + return; + } var (agent, agentFile) = GetAgentFromFile(agentId); - if (agent == null) return; + if (agent == null) + { + return; + } agent.Rules = rules; agent.UpdatedDateTime = DateTime.UtcNow; @@ -264,10 +330,16 @@ private void UpdateAgentRules(string agentId, List rules) private void UpdateAgentRoutingRules(string agentId, List rules) { - if (rules == null) return; + if (rules == null) + { + return; + } var (agent, agentFile) = GetAgentFromFile(agentId); - if (agent == null) return; + if (agent == null) + { + return; + } agent.RoutingRules = rules; agent.UpdatedDateTime = DateTime.UtcNow; @@ -277,10 +349,16 @@ private void UpdateAgentRoutingRules(string agentId, List rules) private void UpdateAgentInstructions(string agentId, string instruction, List channelInstructions) { - if (string.IsNullOrWhiteSpace(instruction)) return; + if (string.IsNullOrWhiteSpace(instruction)) + { + return; + } var (agent, agentFile) = GetAgentFromFile(agentId); - if (agent == null) return; + if (agent == null) + { + return; + } var instructionDir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agentId, AGENT_INSTRUCTIONS_FOLDER); DeleteBeforeCreateDirectory(instructionDir); @@ -293,7 +371,10 @@ private void UpdateAgentInstructions(string agentId, string instruction, List inputFunctions) { - if (inputFunctions == null) return; + if (inputFunctions == null) + { + return; + } var (agent, agentFile) = GetAgentFromFile(agentId); - if (agent == null) return; + if (agent == null) + { + return; + } var functionDir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agentId, AGENT_FUNCTIONS_FOLDER); DeleteBeforeCreateDirectory(functionDir); foreach (var func in inputFunctions) { - if (string.IsNullOrWhiteSpace(func.Name)) continue; + if (string.IsNullOrWhiteSpace(func.Name)) + { + continue; + } var text = JsonSerializer.Serialize(func, _options); var file = Path.Combine(functionDir, $"{func.Name}.json"); @@ -324,10 +414,16 @@ private void UpdateAgentFunctions(string agentId, List inputFunctio private void UpdateAgentTemplates(string agentId, List templates) { - if (templates == null) return; + if (templates == null) + { + return; + } var (agent, agentFile) = GetAgentFromFile(agentId); - if (agent == null) return; + if (agent == null) + { + return; + } var templateDir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agentId, AGENT_TEMPLATES_FOLDER); DeleteBeforeCreateDirectory(templateDir); @@ -341,10 +437,16 @@ private void UpdateAgentTemplates(string agentId, List templates) private void UpdateAgentResponses(string agentId, List responses) { - if (responses == null) return; + if (responses == null) + { + return; + } var (agent, agentFile) = GetAgentFromFile(agentId); - if (agent == null) return; + if (agent == null) + { + return; + } var responseDir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agentId, AGENT_RESPONSES_FOLDER); DeleteBeforeCreateDirectory(responseDir); @@ -360,10 +462,16 @@ private void UpdateAgentResponses(string agentId, List responses) private void UpdateAgentSamples(string agentId, List samples) { - if (samples == null) return; + if (samples == null) + { + return; + } var (agent, agentFile) = GetAgentFromFile(agentId); - if (agent == null) return; + if (agent == null) + { + return; + } var file = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agentId, AGENT_SAMPLES_FILE); File.WriteAllLines(file, samples); @@ -372,7 +480,10 @@ private void UpdateAgentSamples(string agentId, List samples) private void UpdateAgentLlmConfig(string agentId, AgentLlmConfig? config) { var (agent, agentFile) = GetAgentFromFile(agentId); - if (agent == null) return; + if (agent == null) + { + return; + } agent.LlmConfig = config; agent.UpdatedDateTime = DateTime.UtcNow; @@ -383,7 +494,10 @@ private void UpdateAgentLlmConfig(string agentId, AgentLlmConfig? config) private void UpdateAgentMaxMessageCount(string agentId, int? maxMessageCount) { var (agent, agentFile) = GetAgentFromFile(agentId); - if (agent == null) return; + if (agent == null) + { + return; + } agent.MaxMessageCount = maxMessageCount; agent.UpdatedDateTime = DateTime.UtcNow; @@ -394,7 +508,10 @@ private void UpdateAgentMaxMessageCount(string agentId, int? maxMessageCount) private void UpdateAgentAllFields(Agent inputAgent) { var (agent, agentFile) = GetAgentFromFile(inputAgent.Id); - if (agent == null) return; + if (agent == null) + { + return; + } agent.Name = inputAgent.Name; agent.Type = inputAgent.Type; @@ -429,9 +546,12 @@ public List GetAgentResponses(string agentId, string prefix, string inte { var responses = new List(); var dir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agentId, AGENT_RESPONSES_FOLDER); - if (!Directory.Exists(dir)) return responses; + if (!Directory.Exists(dir)) + { + return responses; + } - foreach (var file in Directory.GetFiles(dir)) + foreach (var file in Directory.EnumerateFiles(dir)) { if (file.Split(Path.DirectorySeparatorChar) .Last() @@ -447,17 +567,26 @@ public List GetAgentResponses(string agentId, string prefix, string inte public Agent? GetAgent(string agentId, bool basicsOnly = false) { var agentDir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir); - var dir = Directory.GetDirectories(agentDir).FirstOrDefault(x => x.Split(Path.DirectorySeparatorChar).Last() == agentId); + var dir = Directory.EnumerateDirectories(agentDir).FirstOrDefault(x => x.Split(Path.DirectorySeparatorChar).Last() == agentId); if (!string.IsNullOrEmpty(dir)) { var json = File.ReadAllText(Path.Combine(dir, AGENT_FILE)); - if (string.IsNullOrEmpty(json)) return null; + if (string.IsNullOrEmpty(json)) + { + return null; + } var record = JsonSerializer.Deserialize(json, _options); - if (record == null) return null; + if (record == null) + { + return null; + } - if (basicsOnly) return record; + if (basicsOnly) + { + return record; + } var (defaultInstruction, channelInstructions) = FetchInstructions(dir); var functions = FetchFunctions(dir); @@ -529,14 +658,20 @@ join u in Users on ua.UserId equals u.Id where ua.UserId == userId || u.ExternalId == userId select ua).ToList(); - if (found.IsNullOrEmpty()) return []; + if (found.IsNullOrEmpty()) + { + return []; + } var agentIds = found.Select(x => x.AgentId).Distinct().ToList(); var agents = GetAgents(new AgentFilter { AgentIds = agentIds }); foreach (var item in found) { var agent = agents.FirstOrDefault(x => x.Id == item.AgentId); - if (agent == null) continue; + if (agent == null) + { + continue; + } item.Agent = agent; } @@ -554,9 +689,12 @@ public string GetAgentTemplate(string agentId, string templateName) } var dir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agentId, AGENT_TEMPLATES_FOLDER); - if (!Directory.Exists(dir)) return string.Empty; + if (!Directory.Exists(dir)) + { + return string.Empty; + } - foreach (var file in Directory.GetFiles(dir)) + foreach (var file in Directory.EnumerateFiles(dir)) { var fileName = file.Split(Path.DirectorySeparatorChar).Last(); var splitIdx = fileName.LastIndexOf("."); @@ -573,19 +711,28 @@ public string GetAgentTemplate(string agentId, string templateName) public bool PatchAgentTemplate(string agentId, AgentTemplate template) { - if (string.IsNullOrEmpty(agentId) || template == null) return false; + if (string.IsNullOrEmpty(agentId) || template == null) + { + return false; + } var dir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agentId, AGENT_TEMPLATES_FOLDER); - if (!Directory.Exists(dir)) return false; + if (!Directory.Exists(dir)) + { + return false; + } - var foundTemplate = Directory.GetFiles(dir).FirstOrDefault(f => + var foundTemplate = Directory.EnumerateFiles(dir).FirstOrDefault(f => { var fileName = Path.GetFileNameWithoutExtension(f); var extension = Path.GetExtension(f).Substring(1); return fileName.IsEqualTo(template.Name) && extension.IsEqualTo(_agentSettings.TemplateFormat); }); - if (foundTemplate == null) return false; + if (foundTemplate == null) + { + return false; + } File.WriteAllText(foundTemplate, template.Content); return true; @@ -593,10 +740,16 @@ public bool PatchAgentTemplate(string agentId, AgentTemplate template) public bool AppendAgentLabels(string agentId, List labels) { - if (labels.IsNullOrEmpty()) return false; + if (labels.IsNullOrEmpty()) + { + return false; + } var (agent, agentFile) = GetAgentFromFile(agentId); - if (agent == null) return false; + if (agent == null) + { + return false; + } var prevLabels = agent.Labels ?? []; var curLabels = prevLabels.Concat(labels).Distinct().ToList(); @@ -609,13 +762,19 @@ public bool AppendAgentLabels(string agentId, List labels) public void BulkInsertAgents(List agents) { - if (agents.IsNullOrEmpty()) return; + if (agents.IsNullOrEmpty()) + { + return; + } var baseDir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir); foreach (var agent in agents) { var dir = Path.Combine(baseDir, agent.Id); - if (Directory.Exists(dir)) continue; + if (Directory.Exists(dir)) + { + continue; + } Directory.CreateDirectory(dir); Thread.Sleep(50); @@ -638,7 +797,10 @@ public void BulkInsertAgents(List agents) public void BulkInsertUserAgents(List userAgents) { - if (userAgents.IsNullOrEmpty()) return; + if (userAgents.IsNullOrEmpty()) + { + return; + } var groups = userAgents.GroupBy(x => x.UserId); var usersDir = Path.Combine(_dbSettings.FileRepository, USERS_FOLDER); @@ -646,12 +808,18 @@ public void BulkInsertUserAgents(List userAgents) foreach (var group in groups) { var filtered = group.Where(x => !string.IsNullOrEmpty(x.UserId) && !string.IsNullOrEmpty(x.AgentId)).ToList(); - if (filtered.IsNullOrEmpty()) continue; + if (filtered.IsNullOrEmpty()) + { + continue; + } filtered.ForEach(x => x.Id = Guid.NewGuid().ToString()); var userId = filtered.First().UserId; var userDir = Path.Combine(usersDir, userId); - if (!Directory.Exists(userDir)) continue; + if (!Directory.Exists(userDir)) + { + continue; + } var userAgentFile = Path.Combine(userDir, USER_AGENT_FILE); var list = new List(); @@ -674,48 +842,72 @@ public bool DeleteAgents() return false; } - public bool DeleteAgent(string agentId) + public bool DeleteAgent(string agentId, AgentDeleteOptions? options = null) { - if (string.IsNullOrEmpty(agentId)) return false; + if (string.IsNullOrEmpty(agentId)) + { + return false; + } try { var agentDir = GetAgentDataDir(agentId); - if (string.IsNullOrEmpty(agentDir)) return false; + if (string.IsNullOrEmpty(agentDir)) + { + return false; + } - // Delete user agents - var usersDir = Path.Combine(_dbSettings.FileRepository, USERS_FOLDER); - if (Directory.Exists(usersDir)) + if (options == null || options.DeleteUserAgents) { - foreach (var userDir in Directory.GetDirectories(usersDir)) + // Delete user agents + var usersDir = Path.Combine(_dbSettings.FileRepository, USERS_FOLDER); + if (Directory.Exists(usersDir)) { - var userAgentFile = Directory.GetFiles(userDir).FirstOrDefault(x => Path.GetFileName(x) == USER_AGENT_FILE); - if (string.IsNullOrEmpty(userAgentFile)) continue; - - var text = File.ReadAllText(userAgentFile); - var userAgents = JsonSerializer.Deserialize>(text, _options); - if (userAgents.IsNullOrEmpty()) continue; - - userAgents = userAgents?.Where(x => x.AgentId != agentId)?.ToList() ?? []; - File.WriteAllText(userAgentFile, JsonSerializer.Serialize(userAgents, _options)); + foreach (var userDir in Directory.EnumerateDirectories(usersDir)) + { + var userAgentFile = Directory.GetFiles(userDir).FirstOrDefault(x => Path.GetFileName(x) == USER_AGENT_FILE); + if (string.IsNullOrEmpty(userAgentFile)) + { + continue; + } + + var text = File.ReadAllText(userAgentFile); + var userAgents = JsonSerializer.Deserialize>(text, _options); + if (userAgents.IsNullOrEmpty()) + { + continue; + } + + userAgents = userAgents?.Where(x => x.AgentId != agentId)?.ToList() ?? []; + File.WriteAllText(userAgentFile, JsonSerializer.Serialize(userAgents, _options)); + } } } - - // Delete role agents - var rolesDir = Path.Combine(_dbSettings.FileRepository, ROLES_FOLDER); - if (Directory.Exists(rolesDir)) + + if (options == null || options.DeleteRoleAgents) { - foreach (var roleDir in Directory.GetDirectories(rolesDir)) + // Delete role agents + var rolesDir = Path.Combine(_dbSettings.FileRepository, ROLES_FOLDER); + if (Directory.Exists(rolesDir)) { - var roleAgentFile = Directory.GetFiles(roleDir).FirstOrDefault(x => Path.GetFileName(x) == ROLE_AGENT_FILE); - if (string.IsNullOrEmpty(roleAgentFile)) continue; - - var text = File.ReadAllText(roleAgentFile); - var roleAgents = JsonSerializer.Deserialize>(text, _options); - if (roleAgents.IsNullOrEmpty()) continue; - - roleAgents = roleAgents?.Where(x => x.AgentId != agentId)?.ToList() ?? []; - File.WriteAllText(roleAgentFile, JsonSerializer.Serialize(roleAgents, _options)); + foreach (var roleDir in Directory.EnumerateDirectories(rolesDir)) + { + var roleAgentFile = Directory.GetFiles(roleDir).FirstOrDefault(x => Path.GetFileName(x) == ROLE_AGENT_FILE); + if (string.IsNullOrEmpty(roleAgentFile)) + { + continue; + } + + var text = File.ReadAllText(roleAgentFile); + var roleAgents = JsonSerializer.Deserialize>(text, _options); + if (roleAgents.IsNullOrEmpty()) + { + continue; + } + + roleAgents = roleAgents?.Where(x => x.AgentId != agentId)?.ToList() ?? []; + File.WriteAllText(roleAgentFile, JsonSerializer.Serialize(roleAgents, _options)); + } } } diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentCodeScript.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentCodeScript.cs index b085ad34b..7ebfdc84f 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentCodeScript.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentCodeScript.cs @@ -65,7 +65,7 @@ public List GetAgentCodeScripts(string agentId, AgentCodeScript return null; } - var foundFile = Directory.GetFiles(dir).FirstOrDefault(file => scriptName.IsEqualTo(Path.GetFileName(file))); + var foundFile = Directory.EnumerateFiles(dir).FirstOrDefault(file => scriptName.IsEqualTo(Path.GetFileName(file))); if (!string.IsNullOrEmpty(foundFile)) { return File.ReadAllText(foundFile); @@ -91,7 +91,14 @@ public bool UpdateAgentCodeScripts(string agentId, List scripts var dir = BuildAgentCodeScriptDir(agentId, script.ScriptType); if (!Directory.Exists(dir)) { - continue; + if (options?.IsUpsert == true) + { + Directory.CreateDirectory(dir); + } + else + { + continue; + } } var file = Path.Combine(dir, script.Name); @@ -106,7 +113,7 @@ public bool UpdateAgentCodeScripts(string agentId, List scripts public bool BulkInsertAgentCodeScripts(string agentId, List scripts) { - return UpdateAgentCodeScripts(agentId, scripts); + return UpdateAgentCodeScripts(agentId, scripts, options: new() { IsUpsert = true }); } public bool DeleteAgentCodeScripts(string agentId, List? scripts = null) @@ -117,14 +124,12 @@ public bool DeleteAgentCodeScripts(string agentId, List? script } var dir = BuildAgentCodeScriptDir(agentId); - if (!Directory.Exists(dir)) - { - return false; - } - if (scripts == null) { - Directory.Delete(dir, true); + if (Directory.Exists(dir)) + { + Directory.Delete(dir, true); + } return true; } else if (!scripts.Any()) @@ -132,6 +137,11 @@ public bool DeleteAgentCodeScripts(string agentId, List? script return false; } + if (!Directory.Exists(dir)) + { + return false; + } + var dict = scripts.DistinctBy(x => x.CodePath).ToDictionary(x => x.CodePath, x => x); foreach (var pair in dict) { diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentTask.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentTask.cs index b3926e66f..b847b0be6 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentTask.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentTask.cs @@ -21,12 +21,18 @@ public async ValueTask> GetAgentTasks(AgentTaskFilter filt var matched = true; var dir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir); - if (!Directory.Exists(dir)) return new PagedItems(); + if (!Directory.Exists(dir)) + { + return new PagedItems(); + } - foreach (var agentDir in Directory.GetDirectories(dir)) + foreach (var agentDir in Directory.EnumerateDirectories(dir)) { var taskDir = Path.Combine(agentDir, AGENT_TASKS_FOLDER); - if (!Directory.Exists(taskDir)) continue; + if (!Directory.Exists(taskDir)) + { + continue; + } var agentId = agentDir.Split(Path.DirectorySeparatorChar).Last(); @@ -36,13 +42,19 @@ public async ValueTask> GetAgentTasks(AgentTaskFilter filt matched = agentId == filter.AgentId; } - if (!matched) continue; + if (!matched) + { + continue; + } var curTasks = new List(); - foreach (var taskFile in Directory.GetFiles(taskDir)) + foreach (var taskFile in Directory.EnumerateFiles(taskDir)) { var task = ParseAgentTask(taskFile); - if (task == null) continue; + if (task == null) + { + continue; + } matched = true; if (filter?.Enabled != null) @@ -55,10 +67,16 @@ public async ValueTask> GetAgentTasks(AgentTaskFilter filt matched = matched && task.Status == filter.Status; } - if (!matched) continue; + if (!matched) + { + continue; + } totalCount++; - if (takeCount >= pager.Size) continue; + if (takeCount >= pager.Size) + { + continue; + } if (skipCount < pager.Offset) { @@ -71,7 +89,10 @@ public async ValueTask> GetAgentTasks(AgentTaskFilter filt } } - if (curTasks.IsNullOrEmpty()) continue; + if (curTasks.IsNullOrEmpty()) + { + continue; + } var agent = ParseAgent(agentDir); curTasks.ForEach(t => @@ -92,16 +113,28 @@ public async ValueTask> GetAgentTasks(AgentTaskFilter filt public AgentTask? GetAgentTask(string agentId, string taskId) { var agentDir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agentId); - if (!Directory.Exists(agentDir)) return null; + if (!Directory.Exists(agentDir)) + { + return null; + } var taskDir = Path.Combine(agentDir, AGENT_TASKS_FOLDER); - if (!Directory.Exists(taskDir)) return null; + if (!Directory.Exists(taskDir)) + { + return null; + } var taskFile = FindTaskFileById(taskDir, taskId); - if (taskFile == null) return null; + if (taskFile == null) + { + return null; + } var task = ParseAgentTask(taskFile); - if (task == null) return null; + if (task == null) + { + return null; + } var agent = ParseAgent(agentDir); task.AgentId = agentId; @@ -111,10 +144,16 @@ public async ValueTask> GetAgentTasks(AgentTaskFilter filt public void InsertAgentTask(AgentTask task) { - if (task == null || string.IsNullOrEmpty(task.AgentId)) return; + if (task == null || string.IsNullOrEmpty(task.AgentId)) + { + return; + } var agentDir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, task.AgentId); - if (!Directory.Exists(agentDir)) return; + if (!Directory.Exists(agentDir)) + { + return; + } var taskDir = Path.Combine(agentDir, AGENT_TASKS_FOLDER); if (!Directory.Exists(taskDir)) @@ -144,19 +183,34 @@ public void BulkInsertAgentTasks(string agentId, List tasks) public void UpdateAgentTask(AgentTask task, AgentTaskField field) { - if (task == null || string.IsNullOrEmpty(task.Id)) return; + if (task == null || string.IsNullOrEmpty(task.Id)) + { + return; + } var agentDir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, task.AgentId); - if (!Directory.Exists(agentDir)) return; + if (!Directory.Exists(agentDir)) + { + return; + } var taskDir = Path.Combine(agentDir, AGENT_TASKS_FOLDER); - if (!Directory.Exists(taskDir)) return; + if (!Directory.Exists(taskDir)) + { + return; + } var taskFile = FindTaskFileById(taskDir, task.Id); - if (string.IsNullOrEmpty(taskFile)) return; + if (string.IsNullOrEmpty(taskFile)) + { + return; + } var parsedTask = ParseAgentTask(taskFile); - if (parsedTask == null) return; + if (parsedTask == null) + { + return; + } var metaData = new AgentTask { @@ -218,7 +272,10 @@ public bool DeleteAgentTasks(string agentId, List? taskIds = null) foreach (var taskId in taskIds) { var taskFile = FindTaskFileById(taskDir, taskId); - if (string.IsNullOrWhiteSpace(taskFile)) continue; + if (string.IsNullOrWhiteSpace(taskFile)) + { + continue; + } File.Delete(taskFile); deletedTasks.Add(taskId); @@ -229,9 +286,12 @@ public bool DeleteAgentTasks(string agentId, List? taskIds = null) private string? FindTaskFileById(string taskDir, string taskId) { - if (!Directory.Exists(taskDir) || string.IsNullOrEmpty(taskId)) return null; + if (!Directory.Exists(taskDir) || string.IsNullOrEmpty(taskId)) + { + return null; + } - var taskFile = Directory.GetFiles(taskDir).FirstOrDefault(file => + var taskFile = Directory.EnumerateFiles(taskDir).FirstOrDefault(file => { var fileName = file.Split(Path.DirectorySeparatorChar).Last(); var id = fileName.Split('.').First(); diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs index 68881ea37..a3cece2c1 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs @@ -1,6 +1,4 @@ using BotSharp.Abstraction.Loggers.Models; -using BotSharp.Abstraction.Users.Models; -using System; using System.IO; namespace BotSharp.Core.Repository; @@ -53,12 +51,18 @@ public void CreateNewConversation(Conversation conversation) public bool DeleteConversations(IEnumerable conversationIds) { - if (conversationIds.IsNullOrEmpty()) return false; + if (conversationIds.IsNullOrEmpty()) + { + return false; + } foreach (var conversationId in conversationIds) { var convDir = FindConversationDirectory(conversationId); - if (string.IsNullOrEmpty(convDir)) continue; + if (string.IsNullOrEmpty(convDir)) + { + continue; + } Directory.Delete(convDir, true); } @@ -161,13 +165,22 @@ public void UpdateConversationTitleAlias(string conversationId, string titleAlia public bool UpdateConversationTags(string conversationId, List toAddTags, List toDeleteTags) { - if (string.IsNullOrEmpty(conversationId)) return false; + if (string.IsNullOrEmpty(conversationId)) + { + return false; + } var convDir = FindConversationDirectory(conversationId); - if (string.IsNullOrEmpty(convDir)) return false; + if (string.IsNullOrEmpty(convDir)) + { + return false; + } var convFile = Path.Combine(convDir, CONVERSATION_FILE); - if (!File.Exists(convFile)) return false; + if (!File.Exists(convFile)) + { + return false; + } var json = File.ReadAllText(convFile); var conv = JsonSerializer.Deserialize(json, _options); @@ -183,13 +196,22 @@ public bool UpdateConversationTags(string conversationId, List toAddTags public bool AppendConversationTags(string conversationId, List tags) { - if (string.IsNullOrEmpty(conversationId) || tags.IsNullOrEmpty()) return false; + if (string.IsNullOrEmpty(conversationId) || tags.IsNullOrEmpty()) + { + return false; + } var convDir = FindConversationDirectory(conversationId); - if (string.IsNullOrEmpty(convDir)) return false; + if (string.IsNullOrEmpty(convDir)) + { + return false; + } var convFile = Path.Combine(convDir, CONVERSATION_FILE); - if (!File.Exists(convFile)) return false; + if (!File.Exists(convFile)) + { + return false; + } var json = File.ReadAllText(convFile); var conv = JsonSerializer.Deserialize(json, _options); @@ -204,14 +226,20 @@ public bool AppendConversationTags(string conversationId, List tags) public bool UpdateConversationMessage(string conversationId, UpdateMessageRequest request) { - if (string.IsNullOrEmpty(conversationId)) return false; + if (string.IsNullOrEmpty(conversationId)) + { + return false; + } var dialogs = GetConversationDialogs(conversationId); var candidates = dialogs.Where(x => x.MetaData.MessageId == request.Message.MetaData.MessageId && x.MetaData.Role == request.Message.MetaData.Role).ToList(); var found = candidates.Where((_, idx) => idx == request.InnderIndex).FirstOrDefault(); - if (found == null) return false; + if (found == null) + { + return false; + } found.Content = request.Message.Content; found.RichContent = request.Message.RichContent; @@ -227,7 +255,10 @@ public bool UpdateConversationMessage(string conversationId, UpdateMessageReques } var convDir = FindConversationDirectory(conversationId); - if (string.IsNullOrEmpty(convDir)) return false; + if (string.IsNullOrEmpty(convDir)) + { + return false; + } var dialogFile = Path.Combine(convDir, DIALOG_FILE); File.WriteAllText(dialogFile, JsonSerializer.Serialize(dialogs, _options)); @@ -309,10 +340,16 @@ public ConversationState GetConversationStates(string conversationId) [SideCar] public void UpdateConversationStates(string conversationId, List states) { - if (states.IsNullOrEmpty()) return; + if (states.IsNullOrEmpty()) + { + return; + } var convDir = FindConversationDirectory(conversationId); - if (string.IsNullOrEmpty(convDir)) return; + if (string.IsNullOrEmpty(convDir)) + { + return; + } var stateFile = Path.Combine(convDir, STATE_FILE); if (File.Exists(stateFile)) @@ -350,7 +387,10 @@ public void UpdateConversationStatus(string conversationId, string status) public Conversation GetConversation(string conversationId, bool isLoadStates = false) { var convDir = FindConversationDirectory(conversationId); - if (string.IsNullOrEmpty(convDir)) return null; + if (string.IsNullOrEmpty(convDir)) + { + return null; + } var convFile = Path.Combine(convDir, CONVERSATION_FILE); var content = File.ReadAllText(convFile); @@ -401,15 +441,20 @@ public async ValueTask> GetConversations(ConversationFi filter.AgentIds.Add(filter.AgentId); } - var totalDirs = Directory.GetDirectories(dir); - foreach (var d in totalDirs) + foreach (var d in Directory.EnumerateDirectories(dir)) { var convFile = Path.Combine(d, CONVERSATION_FILE); - if (!File.Exists(convFile)) continue; + if (!File.Exists(convFile)) + { + continue; + } var json = File.ReadAllText(convFile); var record = JsonSerializer.Deserialize(json, _options); - if (record == null) continue; + if (record == null) + { + continue; + } var matched = true; if (filter?.Id != null) @@ -475,7 +520,10 @@ public async ValueTask> GetConversations(ConversationFi { foreach (var pair in filter.States) { - if (pair == null || string.IsNullOrWhiteSpace(pair.Key)) continue; + if (pair == null || string.IsNullOrWhiteSpace(pair.Key)) + { + continue; + } var components = pair.Key.Split(".").ToList(); var primaryKey = components[0]; @@ -518,12 +566,18 @@ public async ValueTask> GetConversations(ConversationFi matched = false; } - if (!matched) break; + if (!matched) + { + break; + } } } } - if (!matched) continue; + if (!matched) + { + continue; + } if (filter.IsLoadLatestStates) { @@ -555,14 +609,20 @@ public List GetLastConversations() var records = new List(); var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir); - foreach (var d in Directory.GetDirectories(dir)) + foreach (var d in Directory.EnumerateDirectories(dir)) { var path = Path.Combine(d, CONVERSATION_FILE); - if (!File.Exists(path)) continue; + if (!File.Exists(path)) + { + continue; + } var json = File.ReadAllText(path); var record = JsonSerializer.Deserialize(json, _options); - if (record == null) continue; + if (record == null) + { + continue; + } records.Add(record); } @@ -588,7 +648,7 @@ public List GetIdleConversations(int batchSize, int messageLimit, int bu batchSize = batchLimit; } - foreach (var d in Directory.GetDirectories(dir)) + foreach (var d in Directory.EnumerateDirectories(dir)) { var convFile = Path.Combine(d, CONVERSATION_FILE); if (!File.Exists(convFile)) @@ -687,12 +747,15 @@ public List TruncateConversation(string conversationId, string messageId public List GetConversationStateSearchKeys(ConversationStateKeysFilter filter) { var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir); - if (!Directory.Exists(dir)) return []; + if (!Directory.Exists(dir)) + { + return []; + } var count = 0; var keys = new List(); - foreach (var d in Directory.GetDirectories(dir)) + foreach (var d in Directory.EnumerateDirectories(dir)) { var convFile = Path.Combine(d, CONVERSATION_FILE); var latestStateFile = Path.Combine(d, CONV_LATEST_STATE_FILE); @@ -733,18 +796,25 @@ public List GetConversationStateSearchKeys(ConversationStateKeysFilter f public List GetConversationsToMigrate(int batchSize = 100) { var baseDir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir); - if (!Directory.Exists(baseDir)) return []; + if (!Directory.Exists(baseDir)) + { + return []; + } var convIds = new List(); - var dirs = Directory.GetDirectories(baseDir); - - foreach (var dir in dirs) + foreach (var dir in Directory.EnumerateDirectories(baseDir)) { var latestStateFile = Path.Combine(dir, CONV_LATEST_STATE_FILE); - if (File.Exists(latestStateFile)) continue; + if (File.Exists(latestStateFile)) + { + continue; + } var convId = dir.Split(Path.DirectorySeparatorChar).Last(); - if (string.IsNullOrEmpty(convId)) continue; + if (string.IsNullOrEmpty(convId)) + { + continue; + } convIds.Add(convId); if (convIds.Count >= batchSize) @@ -759,7 +829,10 @@ public List GetConversationsToMigrate(int batchSize = 100) public bool MigrateConvsersationLatestStates(string conversationId) { - if (string.IsNullOrEmpty(conversationId)) return false; + if (string.IsNullOrEmpty(conversationId)) + { + return false; + } var convDir = FindConversationDirectory(conversationId); if (string.IsNullOrEmpty(convDir)) @@ -774,7 +847,6 @@ public bool MigrateConvsersationLatestStates(string conversationId) var latestStateFile = Path.Combine(convDir, CONV_LATEST_STATE_FILE); var stateStr = JsonSerializer.Serialize(latestStates, _options); File.WriteAllText(latestStateFile, stateStr); - return true; } @@ -782,10 +854,16 @@ public bool MigrateConvsersationLatestStates(string conversationId) #region Private methods private string? FindConversationDirectory(string conversationId) { - if (string.IsNullOrEmpty(conversationId)) return null; + if (string.IsNullOrEmpty(conversationId)) + { + return null; + } var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir, conversationId); - if (!Directory.Exists(dir)) return null; + if (!Directory.Exists(dir)) + { + return null; + } return dir; } @@ -794,7 +872,10 @@ private List CollectDialogElements(string dialogDir) { var dialogs = new List(); - if (!File.Exists(dialogDir)) return dialogs; + if (!File.Exists(dialogDir)) + { + return dialogs; + } var texts = File.ReadAllText(dialogDir); dialogs = JsonSerializer.Deserialize>(texts) ?? new List(); @@ -803,7 +884,10 @@ private List CollectDialogElements(string dialogDir) private string ParseDialogElements(List dialogs) { - if (dialogs.IsNullOrEmpty()) return "[]"; + if (dialogs.IsNullOrEmpty()) + { + return "[]"; + } return JsonSerializer.Serialize(dialogs, _options) ?? "[]"; } @@ -811,10 +895,16 @@ private string ParseDialogElements(List dialogs) private List CollectConversationStates(string stateFile) { var states = new List(); - if (!File.Exists(stateFile)) return states; + if (!File.Exists(stateFile)) + { + return states; + } var stateStr = File.ReadAllText(stateFile); - if (string.IsNullOrEmpty(stateStr)) return states; + if (string.IsNullOrEmpty(stateStr)) + { + return states; + } states = JsonSerializer.Deserialize>(stateStr, _options); return states ?? new List(); @@ -823,10 +913,16 @@ private List CollectConversationStates(string stateFile) private List CollectConversationBreakpoints(string breakpointFile) { var breakpoints = new List(); - if (!File.Exists(breakpointFile)) return breakpoints; + if (!File.Exists(breakpointFile)) + { + return breakpoints; + } var content = File.ReadAllText(breakpointFile); - if (string.IsNullOrEmpty(content)) return breakpoints; + if (string.IsNullOrEmpty(content)) + { + return breakpoints; + } breakpoints = JsonSerializer.Deserialize>(content, _options); return breakpoints ?? new List(); @@ -861,7 +957,10 @@ private bool HandleTruncatedStates(string stateDir, string latestStateDir, List< var values = state.Values.Where(x => x.MessageId != refMsgId) .Where(x => x.UpdateTime < refTime) .ToList(); - if (values.Count == 0) continue; + if (values.Count == 0) + { + continue; + } state.Values = values; truncatedStates.Add(state); @@ -891,11 +990,14 @@ private bool HandleTruncatedLogs(string convDir, DateTime refTime) if (Directory.Exists(contentLogDir)) { - foreach (var file in Directory.GetFiles(contentLogDir)) + foreach (var file in Directory.EnumerateFiles(contentLogDir)) { var text = File.ReadAllText(file); var log = JsonSerializer.Deserialize(text); - if (log == null) continue; + if (log == null) + { + continue; + } if (log.CreatedTime >= refTime) { @@ -906,11 +1008,14 @@ private bool HandleTruncatedLogs(string convDir, DateTime refTime) if (Directory.Exists(stateLogDir)) { - foreach (var file in Directory.GetFiles(stateLogDir)) + foreach (var file in Directory.EnumerateFiles(stateLogDir)) { var text = File.ReadAllText(file); var log = JsonSerializer.Deserialize(text); - if (log == null) continue; + if (log == null) + { + continue; + } if (log.CreatedTime >= refTime) { @@ -924,7 +1029,10 @@ private bool HandleTruncatedLogs(string convDir, DateTime refTime) private bool SaveTruncatedDialogs(string dialogDir, List dialogs) { - if (string.IsNullOrEmpty(dialogDir) || dialogs == null) return false; + if (string.IsNullOrEmpty(dialogDir) || dialogs == null) + { + return false; + } var texts = ParseDialogElements(dialogs); File.WriteAllText(dialogDir, texts); @@ -933,7 +1041,10 @@ private bool SaveTruncatedDialogs(string dialogDir, List dialogs) private bool SaveTruncatedStates(string stateDir, List states) { - if (string.IsNullOrEmpty(stateDir) || states == null) return false; + if (string.IsNullOrEmpty(stateDir) || states == null) + { + return false; + } var stateStr = JsonSerializer.Serialize(states, _options); File.WriteAllText(stateDir, stateStr); @@ -942,7 +1053,10 @@ private bool SaveTruncatedStates(string stateDir, List states) private bool SaveTruncatedLatestStates(string latestStateDir, List states) { - if (string.IsNullOrEmpty(latestStateDir) || states == null) return false; + if (string.IsNullOrEmpty(latestStateDir) || states == null) + { + return false; + } var latestStates = BuildLatestStates(states); var stateStr = JsonSerializer.Serialize(latestStates, _options); @@ -952,7 +1066,10 @@ private bool SaveTruncatedLatestStates(string latestStateDir, List breakpoints) { - if (string.IsNullOrEmpty(breakpointDir) || breakpoints == null) return false; + if (string.IsNullOrEmpty(breakpointDir) || breakpoints == null) + { + return false; + } var breakpointStr = JsonSerializer.Serialize(breakpoints, _options); File.WriteAllText(breakpointDir, breakpointStr); @@ -961,7 +1078,10 @@ private bool SaveTruncatedBreakpoints(string breakpointDir, List CollectConversationLatestStates(string latestStateDir) { - if (string.IsNullOrEmpty(latestStateDir) || !File.Exists(latestStateDir)) return []; + if (string.IsNullOrEmpty(latestStateDir) || !File.Exists(latestStateDir)) + { + return []; + } var str = File.ReadAllText(latestStateDir); var states = JsonSerializer.Deserialize>(str, _options); @@ -979,7 +1099,10 @@ private Dictionary BuildLatestStates(List s foreach (var pair in states) { var value = pair.Values?.LastOrDefault(); - if (value == null || !value.Active) continue; + if (value == null || !value.Active) + { + continue; + } try { @@ -1009,7 +1132,10 @@ private Dictionary BuildLatestStates(List s for (int i = 0; i < paths.Count(); i++) { - if (elem == null) return null; + if (elem == null) + { + return null; + } var field = paths.ElementAt(i); if (elem.Value.ValueKind == JsonValueKind.Array) diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Crontab.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Crontab.cs index 84f2794c3..ded762c85 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Crontab.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Crontab.cs @@ -65,7 +65,6 @@ public bool DeleteCrontabItem(string conversationId) public async ValueTask> GetCrontabItems(CrontabItemFilter filter) { - if (filter == null) { filter = CrontabItemFilter.Empty(); @@ -79,15 +78,20 @@ public async ValueTask> GetCrontabItems(CrontabItemFilte Directory.CreateDirectory(baseDir); } - var totalDirs = Directory.GetDirectories(baseDir); - foreach (var d in totalDirs) + foreach (var d in Directory.EnumerateDirectories(baseDir)) { var file = Path.Combine(d, CRON_FILE); - if (!File.Exists(file)) continue; + if (!File.Exists(file)) + { + continue; + } var json = File.ReadAllText(file); var record = JsonSerializer.Deserialize(json, _options); - if (record == null) continue; + if (record == null) + { + continue; + } var matched = true; if (filter?.AgentIds != null) @@ -103,7 +107,10 @@ public async ValueTask> GetCrontabItems(CrontabItemFilte matched = matched && filter.UserIds.Contains(record.UserId); } - if (!matched) continue; + if (!matched) + { + continue; + } records.Add(record); } diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.KnowledgeBase.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.KnowledgeBase.cs index f245477f2..3c3ba64bc 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.KnowledgeBase.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.KnowledgeBase.cs @@ -54,11 +54,17 @@ public bool AddKnowledgeCollectionConfigs(List configs, public bool DeleteKnowledgeCollectionConfig(string collectionName) { - if (string.IsNullOrWhiteSpace(collectionName)) return false; + if (string.IsNullOrWhiteSpace(collectionName)) + { + return false; + } var vectorDir = BuildKnowledgeCollectionConfigDir(); var configFile = Path.Combine(vectorDir, COLLECTION_CONFIG_FILE); - if (!File.Exists(configFile)) return false; + if (!File.Exists(configFile)) + { + return false; + } var str = File.ReadAllText(configFile); var savedConfigs = JsonSerializer.Deserialize>(str, _options) ?? new(); @@ -139,7 +145,10 @@ public bool DeleteKnolwedgeBaseFileMeta(string collectionName, string vectorStor } var dir = BuildKnowledgeCollectionFileDir(collectionName, vectorStoreProvider); - if (!Directory.Exists(dir)) return false; + if (!Directory.Exists(dir)) + { + return false; + } if (fileId == null) { @@ -172,14 +181,20 @@ public async ValueTask> GetKnowledgeBaseFileMet } var records = new List(); - foreach (var folder in Directory.GetDirectories(dir)) + foreach (var folder in Directory.EnumerateDirectories(dir)) { var metaFile = Path.Combine(folder, KNOWLEDGE_DOC_META_FILE); - if (!File.Exists(metaFile)) continue; + if (!File.Exists(metaFile)) + { + continue; + } var content = File.ReadAllText(metaFile); var metaData = JsonSerializer.Deserialize(content, _options); - if (metaData == null) continue; + if (metaData == null) + { + continue; + } var matched = true; @@ -208,7 +223,10 @@ public async ValueTask> GetKnowledgeBaseFileMet } - if (!matched) continue; + if (!matched) + { + continue; + } records.Add(metaData); } diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Log.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Log.cs index c08514012..396e338f7 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Log.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Log.cs @@ -1,5 +1,4 @@ using BotSharp.Abstraction.Loggers.Models; -using Microsoft.IdentityModel.Logging; using System.IO; namespace BotSharp.Core.Repository @@ -9,7 +8,10 @@ public partial class FileRepository #region LLM Completion Log public void SaveLlmCompletionLog(LlmCompletionLog log) { - if (log == null) return; + if (log == null) + { + return; + } log.ConversationId = log.ConversationId.IfNullOrEmptyAs(Guid.NewGuid().ToString()); log.MessageId = log.MessageId.IfNullOrEmptyAs(Guid.NewGuid().ToString()); @@ -36,13 +38,19 @@ public void SaveLlmCompletionLog(LlmCompletionLog log) #region Conversation Content Log public void SaveConversationContentLog(ContentLogOutputModel log) { - if (log == null) return; + if (log == null) + { + return; + } log.ConversationId = log.ConversationId.IfNullOrEmptyAs(Guid.NewGuid().ToString()); log.MessageId = log.MessageId.IfNullOrEmptyAs(Guid.NewGuid().ToString()); var convDir = FindConversationDirectory(log.ConversationId); - if (string.IsNullOrEmpty(convDir)) return; + if (string.IsNullOrEmpty(convDir)) + { + return; + } var logDir = Path.Combine(convDir, "content_log"); if (!Directory.Exists(logDir)) @@ -57,20 +65,32 @@ public void SaveConversationContentLog(ContentLogOutputModel log) public DateTimePagination GetConversationContentLogs(string conversationId, ConversationLogFilter filter) { - if (string.IsNullOrEmpty(conversationId)) return new(); + if (string.IsNullOrEmpty(conversationId)) + { + return new(); + } var convDir = FindConversationDirectory(conversationId); - if (string.IsNullOrEmpty(convDir)) return new(); + if (string.IsNullOrEmpty(convDir)) + { + return new(); + } var logDir = Path.Combine(convDir, "content_log"); - if (!Directory.Exists(logDir)) return new(); + if (!Directory.Exists(logDir)) + { + return new(); + } var logs = new List(); - foreach (var file in Directory.GetFiles(logDir)) + foreach (var file in Directory.EnumerateFiles(logDir)) { var text = File.ReadAllText(file); var log = JsonSerializer.Deserialize(text); - if (log == null || log.CreatedTime >= filter.StartTime) continue; + if (log == null || log.CreatedTime >= filter.StartTime) + { + continue; + } logs.Add(log); } @@ -89,13 +109,19 @@ public DateTimePagination GetConversationContentLogs(stri #region Conversation State Log public void SaveConversationStateLog(ConversationStateLogModel log) { - if (log == null) return; + if (log == null) + { + return; + } log.ConversationId = log.ConversationId.IfNullOrEmptyAs(Guid.NewGuid().ToString()); log.MessageId = log.MessageId.IfNullOrEmptyAs(Guid.NewGuid().ToString()); var convDir = FindConversationDirectory(log.ConversationId); - if (string.IsNullOrEmpty(convDir)) return; + if (string.IsNullOrEmpty(convDir)) + { + return; + } var logDir = Path.Combine(convDir, "state_log"); if (!Directory.Exists(logDir)) @@ -110,20 +136,32 @@ public void SaveConversationStateLog(ConversationStateLogModel log) public DateTimePagination GetConversationStateLogs(string conversationId, ConversationLogFilter filter) { - if (string.IsNullOrEmpty(conversationId)) return new(); + if (string.IsNullOrEmpty(conversationId)) + { + return new(); + } var convDir = FindConversationDirectory(conversationId); - if (string.IsNullOrEmpty(convDir)) return new(); + if (string.IsNullOrEmpty(convDir)) + { + return new(); + } var logDir = Path.Combine(convDir, "state_log"); - if (!Directory.Exists(logDir)) return new(); + if (!Directory.Exists(logDir)) + { + return new(); + } var logs = new List(); - foreach (var file in Directory.GetFiles(logDir)) + foreach (var file in Directory.EnumerateFiles(logDir)) { var text = File.ReadAllText(file); var log = JsonSerializer.Deserialize(text); - if (log == null || log.CreatedTime >= filter.StartTime) continue; + if (log == null || log.CreatedTime >= filter.StartTime) + { + continue; + } logs.Add(log); } @@ -142,7 +180,10 @@ public DateTimePagination GetConversationStateLogs(st #region Instruction Log public bool SaveInstructionLogs(IEnumerable logs) { - if (logs.IsNullOrEmpty()) return false; + if (logs.IsNullOrEmpty()) + { + return false; + } var baseDir = Path.Combine(_dbSettings.FileRepository, INSTRUCTION_LOG_FOLDER); if (!Directory.Exists(baseDir)) @@ -174,12 +215,14 @@ public async ValueTask> GetInstructionLogs(Instr } var logs = new List(); - var files = Directory.GetFiles(baseDir); - foreach (var file in files) + foreach (var file in Directory.EnumerateFiles(baseDir)) { var json = File.ReadAllText(file); var log = JsonSerializer.Deserialize(json, _options); - if (log == null) continue; + if (log == null) + { + continue; + } var matched = true; if (!filter.AgentIds.IsNullOrEmpty()) @@ -223,7 +266,10 @@ public async ValueTask> GetInstructionLogs(Instr { foreach (var pair in filter.States) { - if (pair == null || string.IsNullOrWhiteSpace(pair.Key)) continue; + if (pair == null || string.IsNullOrWhiteSpace(pair.Key)) + { + continue; + } var components = pair.Key.Split(".").ToList(); var primaryKey = components[0]; @@ -266,12 +312,18 @@ public async ValueTask> GetInstructionLogs(Instr matched = false; } - if (!matched) break; + if (!matched) + { + break; + } } } } - if (!matched) continue; + if (!matched) + { + continue; + } log.Id = Path.GetFileNameWithoutExtension(file); logs.Add(log); @@ -306,12 +358,14 @@ public List GetInstructionLogSearchKeys(InstructLogKeysFilter filter) } var count = 0; - var files = Directory.GetFiles(baseDir); - foreach (var file in files) + foreach (var file in Directory.EnumerateFiles(baseDir)) { var json = File.ReadAllText(file); var log = JsonSerializer.Deserialize(json, _options); - if (log == null) continue; + if (log == null) + { + continue; + } if (log == null || log.InnerStates.IsNullOrEmpty() @@ -340,11 +394,7 @@ public List GetInstructionLogSearchKeys(InstructLogKeysFilter filter) #region Private methods private int GetNextLogIndex(string logDir, string id) { - var files = Directory.GetFiles(logDir); - if (files.IsNullOrEmpty()) - return 0; - - var logIndexes = files.Where(file => + var logIndexes = Directory.EnumerateFiles(logDir).Where(file => { var fileName = ParseFileNameByPath(file); return fileName[0].IsEqualTo(id); diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Role.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Role.cs index c797ce5aa..825df02f0 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Role.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Role.cs @@ -6,11 +6,17 @@ public partial class FileRepository { public bool RefreshRoles(IEnumerable roles) { - if (roles.IsNullOrEmpty()) return false; + if (roles.IsNullOrEmpty()) + { + return false; + } var validRoles = roles.Where(x => !string.IsNullOrWhiteSpace(x.Id) && !string.IsNullOrWhiteSpace(x.Name)).ToList(); - if (validRoles.IsNullOrEmpty()) return false; + if (validRoles.IsNullOrEmpty()) + { + return false; + } var baseDir = Path.Combine(_dbSettings.FileRepository, ROLES_FOLDER); if (Directory.Exists(baseDir)) @@ -58,10 +64,16 @@ public IEnumerable GetRoles(RoleFilter filter) public Role? GetRoleDetails(string roleId, bool includeAgent = false) { - if (string.IsNullOrWhiteSpace(roleId)) return null; + if (string.IsNullOrWhiteSpace(roleId)) + { + return null; + } var role = Roles.FirstOrDefault(x => x.Id == roleId); - if (role == null) return null; + if (role == null) + { + return null; + } var agentActions = new List(); var roleAgents = RoleAgents?.Where(x => x.RoleId == roleId)?.ToList() ?? []; diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Translation.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Translation.cs index e52e09c24..e4f016a02 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Translation.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Translation.cs @@ -112,7 +112,10 @@ public bool SaveTranslationMemories(IEnumerable inputs) else { var foundItem = foundMemory.Translations?.FirstOrDefault(x => x.Language.Equals(input.Language)); - if (foundItem != null) continue; + if (foundItem != null) + { + continue; + } if (foundMemory.Translations == null) { diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs index 09212e202..16520b232 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs @@ -254,7 +254,10 @@ public List SearchLoginUsers(User filter, string source = UserSource.Inter public bool UpdateUser(User user, bool updateUserAgents = false) { - if (string.IsNullOrEmpty(user?.Id)) return false; + if (string.IsNullOrEmpty(user?.Id)) + { + return false; + } var dir = Path.Combine(_dbSettings.FileRepository, USERS_FOLDER, user.Id); if (!Directory.Exists(dir)) @@ -265,7 +268,10 @@ public bool UpdateUser(User user, bool updateUserAgents = false) var userFile = Path.Combine(dir, USER_FILE); var userJson = File.ReadAllText(userFile); var curUser = JsonSerializer.Deserialize(userJson, _options); - if (curUser == null) return false; + if (curUser == null) + { + return false; + } curUser.Type = user.Type; curUser.Role = user.Role; @@ -297,13 +303,19 @@ public bool UpdateUser(User user, bool updateUserAgents = false) public void AddDashboardConversation(string userId, string conversationId) { var user = GetUserById(userId); - if (user == null) return; + if (user == null) + { + return; + } // one user only has one dashboard currently var dash = Dashboards.FirstOrDefault(); dash ??= new(); var existingConv = dash.ConversationList.FirstOrDefault(x => string.Equals(x.ConversationId, conversationId, StringComparison.OrdinalIgnoreCase)); - if (existingConv != null) return; + if (existingConv != null) + { + return; + } var dashconv = new DashboardConversation { @@ -321,15 +333,24 @@ public void AddDashboardConversation(string userId, string conversationId) public void RemoveDashboardConversation(string userId, string conversationId) { var user = GetUserById(userId); - if (user == null) return; + if (user == null) + { + return; + } // one user only has one dashboard currently var dash = Dashboards.FirstOrDefault(); - if (dash == null) return; + if (dash == null) + { + return; + } var dashconv = dash.ConversationList.FirstOrDefault( c => string.Equals(c.ConversationId, conversationId, StringComparison.OrdinalIgnoreCase)); - if (dashconv == null) return; + if (dashconv == null) + { + return; + } dash.ConversationList.Remove(dashconv); @@ -341,15 +362,24 @@ public void RemoveDashboardConversation(string userId, string conversationId) public void UpdateDashboardConversation(string userId, DashboardConversation dashConv) { var user = GetUserById(userId); - if (user == null) return; + if (user == null) + { + return; + } // one user only has one dashboard currently var dash = Dashboards.FirstOrDefault(); - if (dash == null) return; + if (dash == null) + { + return; + } var curIdx = dash.ConversationList.ToList().FindIndex( x => string.Equals(x.ConversationId, dashConv.ConversationId, StringComparison.OrdinalIgnoreCase)); - if (curIdx < 0) return; + if (curIdx < 0) + { + return; + } dash.ConversationList[curIdx] = dashConv; diff --git a/src/Infrastructure/BotSharp.Core/Using.cs b/src/Infrastructure/BotSharp.Core/Using.cs index 07b0f1642..de3fa1e78 100644 --- a/src/Infrastructure/BotSharp.Core/Using.cs +++ b/src/Infrastructure/BotSharp.Core/Using.cs @@ -5,6 +5,7 @@ global using BotSharp.Abstraction.Conversations; global using BotSharp.Abstraction.Conversations.Models; global using BotSharp.Abstraction.Conversations.Settings; +global using BotSharp.Abstraction.Coding.Models; global using BotSharp.Abstraction.Crontab.Models; global using BotSharp.Abstraction.Files; global using BotSharp.Abstraction.Files.Enums; diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/Agent/AgentController.Coding.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Agent/AgentController.Coding.cs new file mode 100644 index 000000000..10a53f2e3 --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Agent/AgentController.Coding.cs @@ -0,0 +1,63 @@ +using BotSharp.Abstraction.Coding.Models; +using BotSharp.Abstraction.Infrastructures.Attributes; + +namespace BotSharp.OpenAPI.Controllers; + +public partial class AgentController +{ + /// + /// Get agent code scripts + /// + /// + /// + /// + [HttpGet("/agent/{agentId}/code-scripts")] + public async Task> GetAgentCodeScripts([FromRoute] string agentId, [FromQuery] AgentCodeScriptFilter request) + { + var scripts = await _agentService.GetAgentCodeScripts(agentId, request); + return scripts.Select(x => AgentCodeScriptViewModel.From(x)).ToList(); + } + + /// + /// Update agent code scripts + /// + /// + /// + /// + [BotSharpAuth] + [HttpPost("/agent/{agentId}/code-scripts")] + public async Task UpdateAgentCodeScripts([FromRoute] string agentId, [FromBody] AgentCodeScriptUpdateModel request) + { + var scripts = request?.CodeScripts?.Select(x => AgentCodeScriptViewModel.To(x))?.ToList() ?? []; + var updated = await _agentService.UpdateAgentCodeScripts(agentId, scripts, request?.Options); + return updated; + } + + /// + /// Delete agent code scripts + /// + /// + /// + /// + [BotSharpAuth] + [HttpDelete("/agent/{agentId}/code-scripts")] + public async Task DeleteAgentCodeScripts([FromRoute] string agentId, [FromBody] AgentCodeScriptDeleteModel request) + { + var scripts = request?.CodeScripts?.Select(x => AgentCodeScriptViewModel.To(x))?.ToList(); + var updated = await _agentService.DeleteAgentCodeScripts(agentId, scripts); + return updated; + } + + [HttpPost("/agent/{agentId}/code-script/generate")] + public async Task GenerateAgentCodeScript([FromRoute] string agentId, [FromBody] AgentCodeScriptGenerationRequest request) + { + request ??= new(); + var states = request.Options?.Data?.ToList(); + var state = _services.GetRequiredService(); + states?.ForEach(x => state.SetState(x.Key, x.Value, source: StateSource.External)); + state.SetState("programming_language", request.Options?.Language, source: StateSource.External); + + var result = await _agentService.GenerateCodeScript(agentId, request.Text, request?.Options); + return result; + } +} diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/RulesController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Agent/AgentController.Rule.cs similarity index 73% rename from src/Infrastructure/BotSharp.OpenAPI/Controllers/RulesController.cs rename to src/Infrastructure/BotSharp.OpenAPI/Controllers/Agent/AgentController.Rule.cs index 0f58d9e56..4daefc717 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/RulesController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Agent/AgentController.Rule.cs @@ -3,18 +3,8 @@ namespace BotSharp.OpenAPI.Controllers; -[Authorize] -[ApiController] -public class RulesController +public partial class AgentController { - private readonly IServiceProvider _services; - - public RulesController( - IServiceProvider services) - { - _services = services; - } - [HttpGet("/rule/triggers")] public IEnumerable GetRuleTriggers() { diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentTaskController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Agent/AgentController.Task.cs similarity index 87% rename from src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentTaskController.cs rename to src/Infrastructure/BotSharp.OpenAPI/Controllers/Agent/AgentController.Task.cs index 2322dae3c..6e6ecc3c0 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentTaskController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Agent/AgentController.Task.cs @@ -1,20 +1,7 @@ -using BotSharp.Abstraction.Tasks; - namespace BotSharp.OpenAPI.Controllers; -[Authorize] -[ApiController] -public class AgentTaskController : ControllerBase +public partial class AgentController { - private readonly IAgentTaskService _agentTaskService; - private readonly IServiceProvider _services; - - public AgentTaskController(IAgentTaskService agentTaskService, IServiceProvider services) - { - _agentTaskService = agentTaskService; - _services = services; - } - /// /// Get an agent task /// @@ -25,7 +12,10 @@ public AgentTaskController(IAgentTaskService agentTaskService, IServiceProvider public async Task GetAgentTask([FromRoute] string agentId, [FromRoute] string taskId) { var task = await _agentTaskService.GetTask(agentId, taskId); - if (task == null) return null; + if (task == null) + { + return null; + } return AgentTaskViewModel.From(task); } diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Agent/AgentController.cs similarity index 92% rename from src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs rename to src/Infrastructure/BotSharp.OpenAPI/Controllers/Agent/AgentController.cs index 6aff56aee..1fcdb1285 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Agent/AgentController.cs @@ -1,22 +1,26 @@ using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Infrastructures.Attributes; +using BotSharp.Abstraction.Tasks; namespace BotSharp.OpenAPI.Controllers; [Authorize] [ApiController] -public class AgentController : ControllerBase +public partial class AgentController : ControllerBase { private readonly IAgentService _agentService; + private readonly IAgentTaskService _agentTaskService; private readonly IUserIdentity _user; private readonly IServiceProvider _services; public AgentController( IAgentService agentService, + IAgentTaskService agentTaskService, IUserIdentity user, IServiceProvider services) { _agentService = agentService; + _agentTaskService = agentTaskService; _user = user; _services = services; } @@ -140,9 +144,9 @@ public async Task PatchAgentTemplates([FromRoute] string agentId, [FromB } [HttpDelete("/agent/{agentId}")] - public async Task DeleteAgent([FromRoute] string agentId) + public async Task DeleteAgent([FromRoute] string agentId, [FromBody] AgentDeleteRequest request) { - return await _agentService.DeleteAgent(agentId); + return await _agentService.DeleteAgent(agentId, request?.Options); } [HttpGet("/agent/options")] @@ -159,12 +163,12 @@ public async Task> GetAgentUtilityOptions() } [HttpGet("/agent/labels")] - public async Task> GetAgentLabels() + public async Task> GetAgentLabels([FromQuery] int? size = null) { var agentService = _services.GetRequiredService(); var agents = await agentService.GetAgents(new AgentFilter { - Pager = new Pagination { Size = 1000 } + Pager = new Pagination { Size = size ?? 1000 } }); var labels = agents.Items?.SelectMany(x => x.Labels) diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ApplicationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Application/ApplicationController.cs similarity index 100% rename from src/Infrastructure/BotSharp.OpenAPI/Controllers/ApplicationController.cs rename to src/Infrastructure/BotSharp.OpenAPI/Controllers/Application/ApplicationController.cs diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.File.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.File.cs new file mode 100644 index 000000000..0a259304b --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.File.cs @@ -0,0 +1,117 @@ +using BotSharp.Abstraction.Files.Enums; +using BotSharp.Abstraction.Files.Utilities; + +namespace BotSharp.OpenAPI.Controllers; + +public partial class ConversationController +{ + #region Files and attachments + [HttpGet("/conversation/{conversationId}/attachments")] + public List ListAttachments([FromRoute] string conversationId) + { + var fileStorage = _services.GetRequiredService(); + var dir = fileStorage.GetDirectory(conversationId); + + // List files in the directory + var files = Directory.Exists(dir) + ? Directory.GetFiles(dir).Select(f => new MessageFileViewModel + { + FileName = Path.GetFileName(f), + FileExtension = Path.GetExtension(f).TrimStart('.').ToLower(), + ContentType = FileUtility.GetFileContentType(f), + FileDownloadUrl = $"/conversation/{conversationId}/attachments/file/{Path.GetFileName(f)}", + }).ToList() + : new List(); + + return files; + } + + [AllowAnonymous] + [HttpGet("/conversation/{conversationId}/attachments/file/{fileName}")] + public IActionResult GetAttachment([FromRoute] string conversationId, [FromRoute] string fileName) + { + var fileStorage = _services.GetRequiredService(); + var dir = fileStorage.GetDirectory(conversationId); + var filePath = Path.Combine(dir, fileName); + if (!System.IO.File.Exists(filePath)) + { + return NotFound(); + } + return BuildFileResult(filePath); + } + + [HttpPost("/conversation/{conversationId}/attachments")] + public IActionResult UploadAttachments([FromRoute] string conversationId, IFormFile[] files) + { + if (files != null && files.Length > 0) + { + 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); + + fileStorage.SaveFileStreamToPath(filePath, file.OpenReadStream()); + } + + return Ok(new { message = "File uploaded successfully." }); + } + + return BadRequest(new { message = "Invalid file." }); + } + + [HttpPost("/agent/{agentId}/conversation/{conversationId}/upload")] + public async Task UploadConversationMessageFiles([FromRoute] string agentId, [FromRoute] string conversationId, [FromBody] InputMessageFiles input) + { + var convService = _services.GetRequiredService(); + convService.SetConversationId(conversationId, input.States); + var conv = await convService.GetConversationRecordOrCreateNew(agentId); + var fileStorage = _services.GetRequiredService(); + var messageId = Guid.NewGuid().ToString(); + var isSaved = fileStorage.SaveMessageFiles(conv.Id, messageId, FileSource.User, input.Files); + return Ok(new { messageId = isSaved ? messageId : string.Empty }); + } + + [HttpGet("/conversation/{conversationId}/files/{messageId}/{source}")] + public IEnumerable GetConversationMessageFiles([FromRoute] string conversationId, [FromRoute] string messageId, [FromRoute] string source) + { + var fileStorage = _services.GetRequiredService(); + var files = fileStorage.GetMessageFiles(conversationId, [messageId], options: new() { Sources = [source] }); + return files?.Select(x => MessageFileViewModel.Transform(x))?.ToList() ?? []; + } + + [HttpGet("/conversation/{conversationId}/message/{messageId}/{source}/file/{index}/{fileName}")] + public IActionResult GetMessageFile([FromRoute] string conversationId, [FromRoute] string messageId, [FromRoute] string source, [FromRoute] string index, [FromRoute] string fileName) + { + var fileStorage = _services.GetRequiredService(); + var file = fileStorage.GetMessageFile(conversationId, messageId, source, index, fileName); + if (string.IsNullOrEmpty(file)) + { + return NotFound(); + } + return BuildFileResult(file); + } + + [HttpGet("/conversation/{conversationId}/message/{messageId}/{source}/file/{index}/{fileName}/download")] + public IActionResult DownloadMessageFile([FromRoute] string conversationId, [FromRoute] string messageId, [FromRoute] string source, [FromRoute] string index, [FromRoute] string fileName) + { + var fileStorage = _services.GetRequiredService(); + var file = fileStorage.GetMessageFile(conversationId, messageId, source, index, fileName); + if (string.IsNullOrEmpty(file)) + { + return NotFound(); + } + + var fName = file.Split(Path.DirectorySeparatorChar).Last(); + var contentType = FileUtility.GetFileContentType(fName); + var stream = System.IO.File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read); + var bytes = new byte[stream.Length]; + stream.Read(bytes, 0, (int)stream.Length); + stream.Position = 0; + + return new FileStreamResult(stream, contentType) { FileDownloadName = fName }; + } + #endregion +} diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.State.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.State.cs new file mode 100644 index 000000000..be01ca7e3 --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.State.cs @@ -0,0 +1,24 @@ +namespace BotSharp.OpenAPI.Controllers; + +public partial class ConversationController +{ + #region Search state keys + [HttpGet("/conversation/state/keys")] + public async Task> GetConversationStateKeys([FromQuery] ConversationStateKeysFilter request) + { + var convService = _services.GetRequiredService(); + var keys = await convService.GetConversationStateSearhKeys(request); + return keys; + } + #endregion + + #region Migrate Latest States + [HttpPost("/conversation/latest-state/migrate")] + public async Task MigrateConversationLatestStates([FromBody] MigrateLatestStateRequest request) + { + var convService = _services.GetRequiredService(); + var res = await convService.MigrateLatestStates(request.BatchSize, request.ErrorLimit); + return res; + } + #endregion +} diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.Visualization.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.Visualization.cs new file mode 100644 index 000000000..42930b66e --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.Visualization.cs @@ -0,0 +1,40 @@ +using BotSharp.Abstraction.Chart; + +namespace BotSharp.OpenAPI.Controllers; + +public partial class ConversationController +{ + #region Chart + [AllowAnonymous] + [HttpGet("/conversation/{conversationId}/message/{messageId}/user/chart/data")] + public async Task GetConversationChartData( + [FromRoute] string conversationId, + [FromRoute] string messageId, + [FromQuery] ConversationChartDataRequest request) + { + var chart = _services.GetServices().FirstOrDefault(x => x.Provider == request?.ChartProvider); + if (chart == null) return null; + + var result = await chart.GetConversationChartDataAsync(conversationId, messageId, request); + return ConversationChartDataResponse.From(result); + } + #endregion + + #region Dashboard + [HttpPut("/agent/{agentId}/conversation/{conversationId}/dashboard")] + public async Task PinConversationToDashboard([FromRoute] string agentId, [FromRoute] string conversationId) + { + var userService = _services.GetRequiredService(); + var pinned = await userService.AddDashboardConversation(conversationId); + return pinned; + } + + [HttpDelete("/agent/{agentId}/conversation/{conversationId}/dashboard")] + public async Task UnpinConversationFromDashboard([FromRoute] string agentId, [FromRoute] string conversationId) + { + var userService = _services.GetRequiredService(); + var unpinned = await userService.RemoveDashboardConversation(conversationId); + return unpinned; + } + #endregion +} diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.cs similarity index 71% rename from src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs rename to src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.cs index 7a2de810c..0931466ac 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.cs @@ -1,7 +1,5 @@ -using BotSharp.Abstraction.Chart; using BotSharp.Abstraction.Files.Constants; using BotSharp.Abstraction.Files.Enums; -using BotSharp.Abstraction.Files.Utilities; using BotSharp.Abstraction.MessageHub.Models; using BotSharp.Abstraction.MessageHub.Services; using BotSharp.Abstraction.Options; @@ -13,13 +11,14 @@ namespace BotSharp.OpenAPI.Controllers; [Authorize] [ApiController] -public class ConversationController : ControllerBase +public partial class ConversationController : ControllerBase { private readonly IServiceProvider _services; private readonly IUserIdentity _user; private readonly JsonSerializerOptions _jsonOptions; - public ConversationController(IServiceProvider services, + public ConversationController( + IServiceProvider services, IUserIdentity user, BotSharpOptions options) { @@ -459,183 +458,6 @@ private async Task OnReceiveToolCallIndication(string conversationId, RoleDialog } #endregion - #region Files and attachments - [HttpGet("/conversation/{conversationId}/attachments")] - public List ListAttachments([FromRoute] string conversationId) - { - var fileStorage = _services.GetRequiredService(); - var dir = fileStorage.GetDirectory(conversationId); - - // List files in the directory - var files = Directory.Exists(dir) - ? Directory.GetFiles(dir).Select(f => new MessageFileViewModel - { - FileName = Path.GetFileName(f), - FileExtension = Path.GetExtension(f).TrimStart('.').ToLower(), - ContentType = FileUtility.GetFileContentType(f), - FileDownloadUrl = $"/conversation/{conversationId}/attachments/file/{Path.GetFileName(f)}", - }).ToList() - : new List(); - - return files; - } - - [AllowAnonymous] - [HttpGet("/conversation/{conversationId}/attachments/file/{fileName}")] - public IActionResult GetAttachment([FromRoute] string conversationId, [FromRoute] string fileName) - { - var fileStorage = _services.GetRequiredService(); - var dir = fileStorage.GetDirectory(conversationId); - var filePath = Path.Combine(dir, fileName); - if (!System.IO.File.Exists(filePath)) - { - return NotFound(); - } - return BuildFileResult(filePath); - } - - [HttpPost("/conversation/{conversationId}/attachments")] - public IActionResult UploadAttachments([FromRoute] string conversationId, IFormFile[] files) - { - if (files != null && files.Length > 0) - { - 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); - - fileStorage.SaveFileStreamToPath(filePath, file.OpenReadStream()); - } - - return Ok(new { message = "File uploaded successfully." }); - } - - return BadRequest(new { message = "Invalid file." }); - } - - [HttpPost("/agent/{agentId}/conversation/{conversationId}/upload")] - public async Task UploadConversationMessageFiles([FromRoute] string agentId, [FromRoute] string conversationId, [FromBody] InputMessageFiles input) - { - var convService = _services.GetRequiredService(); - convService.SetConversationId(conversationId, input.States); - var conv = await convService.GetConversationRecordOrCreateNew(agentId); - var fileStorage = _services.GetRequiredService(); - var messageId = Guid.NewGuid().ToString(); - var isSaved = fileStorage.SaveMessageFiles(conv.Id, messageId, FileSource.User, input.Files); - return Ok(new { messageId = isSaved ? messageId : string.Empty }); - } - - [HttpGet("/conversation/{conversationId}/files/{messageId}/{source}")] - public IEnumerable GetConversationMessageFiles([FromRoute] string conversationId, [FromRoute] string messageId, [FromRoute] string source) - { - var fileStorage = _services.GetRequiredService(); - var files = fileStorage.GetMessageFiles(conversationId, [messageId], options: new() { Sources = [source] }); - return files?.Select(x => MessageFileViewModel.Transform(x))?.ToList() ?? []; - } - - [HttpGet("/conversation/{conversationId}/message/{messageId}/{source}/file/{index}/{fileName}")] - public IActionResult GetMessageFile([FromRoute] string conversationId, [FromRoute] string messageId, [FromRoute] string source, [FromRoute] string index, [FromRoute] string fileName) - { - var fileStorage = _services.GetRequiredService(); - var file = fileStorage.GetMessageFile(conversationId, messageId, source, index, fileName); - if (string.IsNullOrEmpty(file)) - { - return NotFound(); - } - return BuildFileResult(file); - } - - [HttpGet("/conversation/{conversationId}/message/{messageId}/{source}/file/{index}/{fileName}/download")] - public IActionResult DownloadMessageFile([FromRoute] string conversationId, [FromRoute] string messageId, [FromRoute] string source, [FromRoute] string index, [FromRoute] string fileName) - { - var fileStorage = _services.GetRequiredService(); - var file = fileStorage.GetMessageFile(conversationId, messageId, source, index, fileName); - if (string.IsNullOrEmpty(file)) - { - return NotFound(); - } - - var fName = file.Split(Path.DirectorySeparatorChar).Last(); - var contentType = FileUtility.GetFileContentType(fName); - var stream = System.IO.File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read); - var bytes = new byte[stream.Length]; - stream.Read(bytes, 0, (int)stream.Length); - stream.Position = 0; - - return new FileStreamResult(stream, contentType) { FileDownloadName = fName }; - } - #endregion - - #region Chart - [AllowAnonymous] - [HttpGet("/conversation/{conversationId}/message/{messageId}/user/chart/data")] - public async Task GetConversationChartData( - [FromRoute] string conversationId, - [FromRoute] string messageId, - [FromQuery] ConversationChartDataRequest request) - { - var chart = _services.GetServices().FirstOrDefault(x => x.Provider == request?.ChartProvider); - if (chart == null) return null; - - var result = await chart.GetConversationChartData(conversationId, messageId, request); - return ConversationChartDataResponse.From(result); - } - - [HttpPost("/conversation/{conversationId}/message/{messageId}/user/chart/code")] - public async Task GetConversationChartCode( - [FromRoute] string conversationId, - [FromRoute] string messageId, - [FromBody] ConversationChartCodeRequest request) - { - var chart = _services.GetServices().FirstOrDefault(x => x.Provider == request?.ChartProvider); - if (chart == null) return null; - - var result = await chart.GetConversationChartCode(conversationId, messageId, request); - return ConversationChartCodeResponse.From(result); - } - #endregion - - #region Dashboard - [HttpPut("/agent/{agentId}/conversation/{conversationId}/dashboard")] - public async Task PinConversationToDashboard([FromRoute] string agentId, [FromRoute] string conversationId) - { - var userService = _services.GetRequiredService(); - var pinned = await userService.AddDashboardConversation(conversationId); - return pinned; - } - - [HttpDelete("/agent/{agentId}/conversation/{conversationId}/dashboard")] - public async Task UnpinConversationFromDashboard([FromRoute] string agentId, [FromRoute] string conversationId) - { - var userService = _services.GetRequiredService(); - var unpinned = await userService.RemoveDashboardConversation(conversationId); - return unpinned; - } - #endregion - - #region Search state keys - [HttpGet("/conversation/state/keys")] - public async Task> GetConversationStateKeys([FromQuery] ConversationStateKeysFilter request) - { - var convService = _services.GetRequiredService(); - var keys = await convService.GetConversationStateSearhKeys(request); - return keys; - } - #endregion - - #region Migrate Latest States - [HttpPost("/conversation/latest-state/migrate")] - public async Task MigrateConversationLatestStates([FromBody] MigrateLatestStateRequest request) - { - var convService = _services.GetRequiredService(); - var res = await convService.MigrateLatestStates(request.BatchSize, request.ErrorLimit); - return res; - } - #endregion - #region Private methods private void SetStates(IConversationService conv, NewMessageModel input) { diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/DashboardController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/DashboardController.cs similarity index 99% rename from src/Infrastructure/BotSharp.OpenAPI/Controllers/DashboardController.cs rename to src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/DashboardController.cs index 93cfefde9..ee7818897 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/DashboardController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/DashboardController.cs @@ -17,6 +17,7 @@ public DashboardController(IServiceProvider services, _user = user; } + #region User Components [HttpGet("/dashboard/components")] public async Task GetComponents() diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/EvaluatorController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/EvaluatorController.cs similarity index 100% rename from src/Infrastructure/BotSharp.OpenAPI/Controllers/EvaluatorController.cs rename to src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/EvaluatorController.cs diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/GoogleController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/GoogleController.cs similarity index 100% rename from src/Infrastructure/BotSharp.OpenAPI/Controllers/GoogleController.cs rename to src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/GoogleController.cs diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/TranslationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/TranslationController.cs similarity index 100% rename from src/Infrastructure/BotSharp.OpenAPI/Controllers/TranslationController.cs rename to src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/TranslationController.cs diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/TextEmbeddingController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Embedding/TextEmbeddingController.cs similarity index 100% rename from src/Infrastructure/BotSharp.OpenAPI/Controllers/TextEmbeddingController.cs rename to src/Infrastructure/BotSharp.OpenAPI/Controllers/Embedding/TextEmbeddingController.cs diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/FileController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/File/FileController.cs similarity index 100% rename from src/Infrastructure/BotSharp.OpenAPI/Controllers/FileController.cs rename to src/Infrastructure/BotSharp.OpenAPI/Controllers/File/FileController.cs diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/Instruct/InstructModeController.Audio.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Instruct/InstructModeController.Audio.cs new file mode 100644 index 000000000..027e9e40b --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Instruct/InstructModeController.Audio.cs @@ -0,0 +1,101 @@ +using BotSharp.Abstraction.Files.Utilities; +using BotSharp.Abstraction.Instructs.Models; +using BotSharp.Abstraction.Instructs.Options; +using BotSharp.Core.Infrastructures; +using BotSharp.OpenAPI.ViewModels.Instructs; + +namespace BotSharp.OpenAPI.Controllers; + +public partial class InstructModeController +{ + #region Audio + [HttpPost("/instruct/speech-to-text")] + public async Task SpeechToText([FromBody] SpeechToTextFileRequest request) + { + var fileInstruct = _services.GetRequiredService(); + var state = _services.GetRequiredService(); + request.States.ForEach(x => state.SetState(x.Key, x.Value, source: StateSource.External)); + var viewModel = new SpeechToTextViewModel(); + + try + { + var audio = request.File; + if (audio == null) + { + return new SpeechToTextViewModel { ErrorMsg = "Error! Cannot find a valid audio file!" }; + } + var content = await fileInstruct.SpeechToText(audio, request.Text, new InstructOptions + { + Provider = request.Provider, + Model = request.Model, + AgentId = request.AgentId, + TemplateName = request.TemplateName + }); + + viewModel.Success = true; + viewModel.Content = content; + return viewModel; + } + catch (Exception ex) + { + var error = $"Error in speech to text. {ex.Message}"; + _logger.LogError(ex, error); + viewModel.ErrorMsg = error; + return viewModel; + } + } + + [HttpPost("/instruct/speech-to-text/form")] + public async Task SpeechToText(IFormFile file, [FromForm] SpeechToTextRequest request) + { + var fileInstruct = _services.GetRequiredService(); + var state = _services.GetRequiredService(); + request?.States?.ForEach(x => state.SetState(x.Key, x.Value, source: StateSource.External)); + var viewModel = new SpeechToTextViewModel(); + + try + { + var audioData = FileUtility.BuildFileDataFromFile(file); + var content = await fileInstruct.SpeechToText(new InstructFileModel + { + FileData = audioData, + FileName = Path.GetFileNameWithoutExtension(file.FileName), + FileExtension = Path.GetExtension(file.FileName) + }, + request?.Text ?? string.Empty, + new InstructOptions + { + Provider = request?.Provider, + Model = request?.Model, + AgentId = request?.AgentId, + TemplateName = request?.TemplateName + }); + + viewModel.Success = true; + viewModel.Content = content; + return viewModel; + } + catch (Exception ex) + { + var error = $"Error in speech-to-text. {ex.Message}"; + _logger.LogError(ex, error); + viewModel.ErrorMsg = error; + return viewModel; + } + } + + [HttpPost("/instruct/text-to-speech")] + public async Task TextToSpeech([FromBody] TextToSpeechRequest request) + { + var state = _services.GetRequiredService(); + request.States.ForEach(x => state.SetState(x.Key, x.Value, source: StateSource.External)); + + var completion = CompletionProvider.GetAudioSynthesizer(_services, provider: request.Provider, model: request.Model); + var binaryData = await completion.GenerateAudioAsync(request.Text); + var stream = binaryData.ToStream(); + stream.Position = 0; + + return new FileStreamResult(stream, "audio/mpeg") { FileDownloadName = "output.mp3" }; + } + #endregion +} diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/Instruct/InstructModeController.File.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Instruct/InstructModeController.File.cs new file mode 100644 index 000000000..031821433 --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Instruct/InstructModeController.File.cs @@ -0,0 +1,81 @@ +using BotSharp.Abstraction.Files.Utilities; +using BotSharp.Abstraction.Instructs.Models; +using BotSharp.Abstraction.Instructs.Options; +using BotSharp.OpenAPI.ViewModels.Instructs; + +namespace BotSharp.OpenAPI.Controllers; + +public partial class InstructModeController +{ + #region Pdf + [HttpPost("/instruct/pdf-completion")] + public async Task PdfCompletion([FromBody] PdfReadFileRequest request) + { + var state = _services.GetRequiredService(); + request.States.ForEach(x => state.SetState(x.Key, x.Value, source: StateSource.External)); + var viewModel = new PdfCompletionViewModel(); + + try + { + var fileInstruct = _services.GetRequiredService(); + var content = await fileInstruct.ReadPdf(request.Text, request.Files, new InstructOptions + { + Provider = request.Provider, + Model = request.Model, + AgentId = request.AgentId, + TemplateName = request.TemplateName, + ImageConvertProvider = request.ImageConvertProvider + }); + + viewModel.Success = true; + viewModel.Content = content; + return viewModel; + } + catch (Exception ex) + { + var error = $"Error in pdf completion. {ex.Message}"; + _logger.LogError(ex, error); + viewModel.ErrorMsg = error; + return viewModel; + } + } + + [HttpPost("/instruct/pdf-completion/form")] + public async Task PdfCompletion([FromForm] IEnumerable files, [FromForm] PdfReadRequest request) + { + var state = _services.GetRequiredService(); + request?.States?.ForEach(x => state.SetState(x.Key, x.Value, source: StateSource.External)); + var viewModel = new PdfCompletionViewModel(); + + try + { + var fileModels = files.Select(x => new InstructFileModel + { + FileData = FileUtility.BuildFileDataFromFile(x), + ContentType = x.ContentType + }).ToList(); + + var fileInstruct = _services.GetRequiredService(); + var content = await fileInstruct.ReadPdf(request?.Text ?? string.Empty, fileModels, new InstructOptions + { + Provider = request?.Provider, + Model = request?.Model, + AgentId = request?.AgentId, + TemplateName = request?.TemplateName, + ImageConvertProvider = request?.ImageConvertProvider + }); + + viewModel.Success = true; + viewModel.Content = content; + return viewModel; + } + catch (Exception ex) + { + var error = $"Error in pdf completion. {ex.Message}"; + _logger.LogError(ex, error); + viewModel.ErrorMsg = error; + return viewModel; + } + } + #endregion +} diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ImageGenerationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Instruct/InstructModeController.Image.cs similarity index 77% rename from src/Infrastructure/BotSharp.OpenAPI/Controllers/ImageGenerationController.cs rename to src/Infrastructure/BotSharp.OpenAPI/Controllers/Instruct/InstructModeController.Image.cs index 4d82e92d1..3a3f62b3b 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ImageGenerationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Instruct/InstructModeController.Image.cs @@ -5,19 +5,9 @@ namespace BotSharp.OpenAPI.Controllers; -[Authorize] -[ApiController] -public class ImageGenerationController -{ - private readonly IServiceProvider _services; - private readonly ILogger _logger; - - public ImageGenerationController(IServiceProvider services, ILogger logger) - { - _services = services; - _logger = logger; - } +public partial class InstructModeController +{ #region Image composition [HttpPost("/instruct/image-composition")] public async Task ComposeImages([FromBody] ImageCompositionRequest request) @@ -31,7 +21,7 @@ public async Task ComposeImages([FromBody] ImageCompos { if (request.Files.IsNullOrEmpty()) { - return new ImageGenerationViewModel { Message = "No image found" }; + return new ImageGenerationViewModel { ErrorMsg = "No image found" }; } var message = await fileInstruct.ComposeImages(request.Text, request.Files, new InstructOptions @@ -42,6 +32,8 @@ public async Task ComposeImages([FromBody] ImageCompos TemplateName = request.TemplateName, ImageConvertProvider = request.ImageConvertProvider }); + + imageViewModel.Success = true; imageViewModel.Content = message.Content; imageViewModel.Images = message.GeneratedImages?.Select(x => ImageViewModel.ToViewModel(x)) ?? []; return imageViewModel; @@ -50,7 +42,7 @@ public async Task ComposeImages([FromBody] ImageCompos { var error = $"Error in image composition. {ex.Message}"; _logger.LogError(ex, error); - imageViewModel.Message = error; + imageViewModel.ErrorMsg = error; return imageViewModel; } } @@ -74,6 +66,8 @@ public async Task ImageGeneration([FromBody] ImageGene AgentId = request.AgentId, TemplateName = request.TemplateName }); + + imageViewModel.Success = true; imageViewModel.Content = message.Content; imageViewModel.Images = message.GeneratedImages?.Select(x => ImageViewModel.ToViewModel(x)) ?? []; return imageViewModel; @@ -82,7 +76,7 @@ public async Task ImageGeneration([FromBody] ImageGene { var error = $"Error in image generation. {ex.Message}"; _logger.LogError(ex, error); - imageViewModel.Message = error; + imageViewModel.ErrorMsg = error; return imageViewModel; } } @@ -100,7 +94,7 @@ public async Task ImageVariation([FromBody] ImageVaria { if (request.File == null) { - return new ImageGenerationViewModel { Message = "Error! Cannot find an image!" }; + return new ImageGenerationViewModel { ErrorMsg = "Error! Cannot find an image!" }; } var fileInstruct = _services.GetRequiredService(); @@ -112,6 +106,7 @@ public async Task ImageVariation([FromBody] ImageVaria ImageConvertProvider = request.ImageConvertProvider }); + imageViewModel.Success = true; imageViewModel.Content = message.Content; imageViewModel.Images = message.GeneratedImages?.Select(x => ImageViewModel.ToViewModel(x)) ?? []; return imageViewModel; @@ -120,7 +115,7 @@ public async Task ImageVariation([FromBody] ImageVaria { var error = $"Error in image variation. {ex.Message}"; _logger.LogError(ex, error); - imageViewModel.Message = error; + imageViewModel.ErrorMsg = error; return imageViewModel; } } @@ -150,6 +145,7 @@ public async Task ImageVariation(IFormFile file, [From ImageConvertProvider = request?.ImageConvertProvider }); + imageViewModel.Success = true; imageViewModel.Content = message.Content; imageViewModel.Images = message.GeneratedImages?.Select(x => ImageViewModel.ToViewModel(x)) ?? []; return imageViewModel; @@ -158,7 +154,7 @@ public async Task ImageVariation(IFormFile file, [From { var error = $"Error in image variation upload. {ex.Message}"; _logger.LogError(ex, error); - imageViewModel.Message = error; + imageViewModel.ErrorMsg = error; return imageViewModel; } } @@ -177,8 +173,9 @@ public async Task ImageEdit([FromBody] ImageEditFileRe { if (request.File == null) { - return new ImageGenerationViewModel { Message = "Error! Cannot find a valid image file!" }; + return new ImageGenerationViewModel { ErrorMsg = "Error! Cannot find a valid image file!" }; } + var message = await fileInstruct.EditImage(request.Text, request.File, new InstructOptions { Provider = request.Provider, @@ -187,6 +184,8 @@ public async Task ImageEdit([FromBody] ImageEditFileRe TemplateName = request.TemplateName, ImageConvertProvider = request.ImageConvertProvider }); + + imageViewModel.Success = true; imageViewModel.Content = message.Content; imageViewModel.Images = message.GeneratedImages?.Select(x => ImageViewModel.ToViewModel(x)) ?? []; return imageViewModel; @@ -195,7 +194,7 @@ public async Task ImageEdit([FromBody] ImageEditFileRe { var error = $"Error in image edit. {ex.Message}"; _logger.LogError(ex, error); - imageViewModel.Message = error; + imageViewModel.ErrorMsg = error; return imageViewModel; } } @@ -226,6 +225,7 @@ public async Task ImageEdit(IFormFile file, [FromForm] ImageConvertProvider = request?.ImageConvertProvider }); + imageViewModel.Success = true; imageViewModel.Content = message.Content; imageViewModel.Images = message.GeneratedImages?.Select(x => ImageViewModel.ToViewModel(x)) ?? []; return imageViewModel; @@ -234,7 +234,7 @@ public async Task ImageEdit(IFormFile file, [FromForm] { var error = $"Error in image edit upload. {ex.Message}"; _logger.LogError(ex, error); - imageViewModel.Message = error; + imageViewModel.ErrorMsg = error; return imageViewModel; } } @@ -255,8 +255,9 @@ public async Task ImageMaskEdit([FromBody] ImageMaskEd var mask = request.Mask; if (image == null || mask == null) { - return new ImageGenerationViewModel { Message = "Error! Cannot find a valid image or mask!" }; + return new ImageGenerationViewModel { ErrorMsg = "Error! Cannot find a valid image or mask!" }; } + var message = await fileInstruct.EditImage(request.Text, image, mask, new InstructOptions { Provider = request.Provider, @@ -265,6 +266,8 @@ public async Task ImageMaskEdit([FromBody] ImageMaskEd TemplateName = request.TemplateName, ImageConvertProvider = request.ImageConvertProvider }); + + imageViewModel.Success = true; imageViewModel.Content = message.Content; imageViewModel.Images = message.GeneratedImages?.Select(x => ImageViewModel.ToViewModel(x)) ?? []; return imageViewModel; @@ -273,7 +276,7 @@ public async Task ImageMaskEdit([FromBody] ImageMaskEd { var error = $"Error in image mask edit. {ex.Message}"; _logger.LogError(ex, error); - imageViewModel.Message = error; + imageViewModel.ErrorMsg = error; return imageViewModel; } } @@ -312,6 +315,7 @@ public async Task ImageMaskEdit(IFormFile image, IForm ImageConvertProvider = request?.ImageConvertProvider }); + imageViewModel.Success = true; imageViewModel.Content = message.Content; imageViewModel.Images = message.GeneratedImages?.Select(x => ImageViewModel.ToViewModel(x)) ?? []; return imageViewModel; @@ -320,9 +324,73 @@ public async Task ImageMaskEdit(IFormFile image, IForm { var error = $"Error in image mask edit upload. {ex.Message}"; _logger.LogError(ex, error); - imageViewModel.Message = error; + imageViewModel.ErrorMsg = error; return imageViewModel; } } #endregion + + #region Read image + [HttpPost("/instruct/multi-modal")] + public async Task MultiModalCompletion([FromBody] MultiModalFileRequest input) + { + var state = _services.GetRequiredService(); + input.States.ForEach(x => state.SetState(x.Key, x.Value, source: StateSource.External)); + + try + { + var fileInstruct = _services.GetRequiredService(); + var content = await fileInstruct.ReadImages(input.Text, input.Files, new InstructOptions + { + Provider = input.Provider, + Model = input.Model, + AgentId = input.AgentId, + TemplateName = input.TemplateName + }); + return content; + } + catch (Exception ex) + { + var error = $"Error in reading multi-modal files. {ex.Message}"; + _logger.LogError(ex, error); + return error; + } + } + + [HttpPost("/instruct/multi-modal/form")] + public async Task MultiModalCompletion([FromForm] IEnumerable files, [FromForm] MultiModalRequest request) + { + var state = _services.GetRequiredService(); + request?.States?.ForEach(x => state.SetState(x.Key, x.Value, source: StateSource.External)); + var viewModel = new MultiModalViewModel(); + + try + { + var fileModels = files.Select(x => new InstructFileModel + { + FileData = FileUtility.BuildFileDataFromFile(x), + ContentType = x.ContentType + }).ToList(); + + var fileInstruct = _services.GetRequiredService(); + var content = await fileInstruct.ReadImages(request?.Text ?? string.Empty, fileModels, new InstructOptions + { + Provider = request?.Provider, + Model = request?.Model, + AgentId = request?.AgentId, + TemplateName = request?.TemplateName + }); + viewModel.Success = true; + viewModel.Content = content; + return viewModel; + } + catch (Exception ex) + { + var error = $"Error in reading multi-modal files. {ex.Message}"; + _logger.LogError(ex, error); + viewModel.ErrorMsg = error; + return viewModel; + } + } + #endregion } diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/Instruct/InstructModeController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Instruct/InstructModeController.cs new file mode 100644 index 000000000..3816d260f --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Instruct/InstructModeController.cs @@ -0,0 +1,123 @@ +using BotSharp.Abstraction.Agents.Models; +using BotSharp.Abstraction.Files.Utilities; +using BotSharp.Abstraction.Instructs; +using BotSharp.Abstraction.Instructs.Models; +using BotSharp.Abstraction.Instructs.Options; +using BotSharp.Core.Infrastructures; +using BotSharp.OpenAPI.ViewModels.Instructs; + +namespace BotSharp.OpenAPI.Controllers; + +[Authorize] +[ApiController] +public partial class InstructModeController : ControllerBase +{ + private readonly IServiceProvider _services; + private readonly ILogger _logger; + + public InstructModeController(IServiceProvider services, ILogger logger) + { + _services = services; + _logger = logger; + } + + [HttpPost("/instruct/{agentId}")] + public async Task InstructCompletion([FromRoute] string agentId, [FromBody] InstructMessageModel input) + { + var state = _services.GetRequiredService(); + input.States.ForEach(x => state.SetState(x.Key, x.Value, activeRounds: x.ActiveRounds, source: StateSource.External)); + state.SetState("provider", input.Provider, source: StateSource.External) + .SetState("model", input.Model, source: StateSource.External) + .SetState("model_id", input.ModelId, source: StateSource.External) + .SetState("instruction", input.Instruction, source: StateSource.External) + .SetState("input_text", input.Text, source: StateSource.External) + .SetState("template_name", input.Template, source: StateSource.External) + .SetState("channel", input.Channel, source: StateSource.External) + .SetState("code_options", input.CodeOptions, source: StateSource.External) + .SetState("file_options", input.FileOptions, source: StateSource.External); + + var instructor = _services.GetRequiredService(); + var result = await instructor.Execute(agentId, + new RoleDialogModel(AgentRole.User, input.Text), + instruction: input.Instruction, + templateName: input.Template, + files: input.Files, + codeOptions: input.CodeOptions, + fileOptions: input.FileOptions); + + result.States = state.GetStates(); + return result; + } + + [HttpPost("/instruct/text-completion")] + public async Task TextCompletion([FromBody] IncomingInstructRequest input) + { + var state = _services.GetRequiredService(); + input.States.ForEach(x => state.SetState(x.Key, x.Value, activeRounds: x.ActiveRounds, source: StateSource.External)); + state.SetState("provider", input.Provider ?? "azure-openai", source: StateSource.External) + .SetState("model", input.Model, source: StateSource.External) + .SetState("model_id", input.ModelId, source: StateSource.External); + + var agentId = input.AgentId ?? Guid.Empty.ToString(); + var textCompletion = CompletionProvider.GetTextCompletion(_services); + var response = await textCompletion.GetCompletion(input.Text, agentId, Guid.NewGuid().ToString()); + + await HookEmitter.Emit(_services, async hook => + await hook.OnResponseGenerated(new InstructResponseModel + { + AgentId = agentId, + Provider = textCompletion.Provider, + Model = textCompletion.Model, + TemplateName = input.Template, + UserMessage = input.Text, + CompletionText = response + }), agentId); + + return response; + } + + #region Chat + [HttpPost("/instruct/chat-completion")] + public async Task ChatCompletion([FromBody] IncomingInstructRequest input) + { + var state = _services.GetRequiredService(); + input.States.ForEach(x => state.SetState(x.Key, x.Value, activeRounds: x.ActiveRounds, source: StateSource.External)); + state.SetState("provider", input.Provider, source: StateSource.External) + .SetState("model", input.Model, source: StateSource.External) + .SetState("model_id", input.ModelId, source: StateSource.External); + + var agentId = input.AgentId ?? Guid.Empty.ToString(); + var completion = CompletionProvider.GetChatCompletion(_services); + var message = await completion.GetChatCompletions(new Agent() + { + Id = agentId, + Instruction = input.Instruction + }, new List + { + new RoleDialogModel(AgentRole.User, input.Text) + { + Files = input.Files?.Select(x => new BotSharpFile + { + FileUrl = x.FileUrl, + FileData = x.FileData, + ContentType = x.ContentType + }).ToList() ?? [] + } + }); + + await HookEmitter.Emit(_services, async hook => + await hook.OnResponseGenerated(new InstructResponseModel + { + AgentId = agentId, + Provider = completion.Provider, + Model = completion.Model, + TemplateName = input.Template, + UserMessage = input.Text, + SystemInstruction = message.RenderedInstruction, + CompletionText = message.Content + }), agentId); + + return message.Content; + } + #endregion +} diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs deleted file mode 100644 index 986d5dd00..000000000 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs +++ /dev/null @@ -1,342 +0,0 @@ -using BotSharp.Abstraction.Agents.Models; -using BotSharp.Abstraction.Files.Utilities; -using BotSharp.Abstraction.Instructs; -using BotSharp.Abstraction.Instructs.Models; -using BotSharp.Abstraction.Instructs.Options; -using BotSharp.Core.Infrastructures; -using BotSharp.OpenAPI.ViewModels.Instructs; - -namespace BotSharp.OpenAPI.Controllers; - -[Authorize] -[ApiController] -public class InstructModeController : ControllerBase -{ - private readonly IServiceProvider _services; - private readonly ILogger _logger; - - public InstructModeController(IServiceProvider services, ILogger logger) - { - _services = services; - _logger = logger; - } - - [HttpPost("/instruct/{agentId}")] - public async Task InstructCompletion([FromRoute] string agentId, [FromBody] InstructMessageModel input) - { - var state = _services.GetRequiredService(); - input.States.ForEach(x => state.SetState(x.Key, x.Value, activeRounds: x.ActiveRounds, source: StateSource.External)); - state.SetState("provider", input.Provider, source: StateSource.External) - .SetState("model", input.Model, source: StateSource.External) - .SetState("model_id", input.ModelId, source: StateSource.External) - .SetState("instruction", input.Instruction, source: StateSource.External) - .SetState("input_text", input.Text, source: StateSource.External) - .SetState("template_name", input.Template, source: StateSource.External) - .SetState("channel", input.Channel, source: StateSource.External) - .SetState("code_options", input.CodeOptions, source: StateSource.External) - .SetState("file_options", input.FileOptions, source: StateSource.External); - - var instructor = _services.GetRequiredService(); - var result = await instructor.Execute(agentId, - new RoleDialogModel(AgentRole.User, input.Text), - instruction: input.Instruction, - templateName: input.Template, - files: input.Files, - codeOptions: input.CodeOptions, - fileOptions: input.FileOptions); - - result.States = state.GetStates(); - return result; - } - - [HttpPost("/instruct/text-completion")] - public async Task TextCompletion([FromBody] IncomingInstructRequest input) - { - var state = _services.GetRequiredService(); - input.States.ForEach(x => state.SetState(x.Key, x.Value, activeRounds: x.ActiveRounds, source: StateSource.External)); - state.SetState("provider", input.Provider ?? "azure-openai", source: StateSource.External) - .SetState("model", input.Model, source: StateSource.External) - .SetState("model_id", input.ModelId, source: StateSource.External); - - var agentId = input.AgentId ?? Guid.Empty.ToString(); - var textCompletion = CompletionProvider.GetTextCompletion(_services); - var response = await textCompletion.GetCompletion(input.Text, agentId, Guid.NewGuid().ToString()); - - await HookEmitter.Emit(_services, async hook => - await hook.OnResponseGenerated(new InstructResponseModel - { - AgentId = agentId, - Provider = textCompletion.Provider, - Model = textCompletion.Model, - TemplateName = input.Template, - UserMessage = input.Text, - CompletionText = response - }), agentId); - - return response; - } - - #region Chat - [HttpPost("/instruct/chat-completion")] - public async Task ChatCompletion([FromBody] IncomingInstructRequest input) - { - var state = _services.GetRequiredService(); - input.States.ForEach(x => state.SetState(x.Key, x.Value, activeRounds: x.ActiveRounds, source: StateSource.External)); - state.SetState("provider", input.Provider, source: StateSource.External) - .SetState("model", input.Model, source: StateSource.External) - .SetState("model_id", input.ModelId, source: StateSource.External); - - var agentId = input.AgentId ?? Guid.Empty.ToString(); - var completion = CompletionProvider.GetChatCompletion(_services); - var message = await completion.GetChatCompletions(new Agent() - { - Id = agentId, - Instruction = input.Instruction - }, new List - { - new RoleDialogModel(AgentRole.User, input.Text) - { - Files = input.Files?.Select(x => new BotSharpFile - { - FileUrl = x.FileUrl, - FileData = x.FileData, - ContentType = x.ContentType - }).ToList() ?? [] - } - }); - - await HookEmitter.Emit(_services, async hook => - await hook.OnResponseGenerated(new InstructResponseModel - { - AgentId = agentId, - Provider = completion.Provider, - Model = completion.Model, - TemplateName = input.Template, - UserMessage = input.Text, - SystemInstruction = message.RenderedInstruction, - CompletionText = message.Content - }), agentId); - - return message.Content; - } - #endregion - - #region Read image - [HttpPost("/instruct/multi-modal")] - public async Task MultiModalCompletion([FromBody] MultiModalFileRequest input) - { - var state = _services.GetRequiredService(); - input.States.ForEach(x => state.SetState(x.Key, x.Value, source: StateSource.External)); - - try - { - var fileInstruct = _services.GetRequiredService(); - var content = await fileInstruct.ReadImages(input.Text, input.Files, new InstructOptions - { - Provider = input.Provider, - Model = input.Model, - AgentId = input.AgentId, - TemplateName = input.TemplateName - }); - return content; - } - catch (Exception ex) - { - var error = $"Error in reading multi-modal files. {ex.Message}"; - _logger.LogError(ex, error); - return error; - } - } - - [HttpPost("/instruct/multi-modal/form")] - public async Task MultiModalCompletion([FromForm] IEnumerable files, [FromForm] MultiModalRequest request) - { - var state = _services.GetRequiredService(); - request?.States?.ForEach(x => state.SetState(x.Key, x.Value, source: StateSource.External)); - var viewModel = new MultiModalViewModel(); - - try - { - var fileModels = files.Select(x => new InstructFileModel - { - FileData = FileUtility.BuildFileDataFromFile(x), - ContentType = x.ContentType - }).ToList(); - - var fileInstruct = _services.GetRequiredService(); - var content = await fileInstruct.ReadImages(request?.Text ?? string.Empty, fileModels, new InstructOptions - { - Provider = request?.Provider, - Model = request?.Model, - AgentId = request?.AgentId, - TemplateName = request?.TemplateName - }); - viewModel.Content = content; - return viewModel; - } - catch (Exception ex) - { - var error = $"Error in reading multi-modal files. {ex.Message}"; - _logger.LogError(ex, error); - viewModel.Message = error; - return viewModel; - } - } - #endregion - - #region Pdf - [HttpPost("/instruct/pdf-completion")] - public async Task PdfCompletion([FromBody] PdfReadFileRequest request) - { - var state = _services.GetRequiredService(); - request.States.ForEach(x => state.SetState(x.Key, x.Value, source: StateSource.External)); - var viewModel = new PdfCompletionViewModel(); - - try - { - var fileInstruct = _services.GetRequiredService(); - var content = await fileInstruct.ReadPdf(request.Text, request.Files, new InstructOptions - { - Provider = request.Provider, - Model = request.Model, - AgentId = request.AgentId, - TemplateName = request.TemplateName, - ImageConvertProvider = request.ImageConvertProvider - }); - viewModel.Content = content; - return viewModel; - } - catch (Exception ex) - { - var error = $"Error in pdf completion. {ex.Message}"; - _logger.LogError(ex, error); - viewModel.Message = error; - return viewModel; - } - } - - [HttpPost("/instruct/pdf-completion/form")] - public async Task PdfCompletion([FromForm] IEnumerable files, [FromForm] PdfReadRequest request) - { - var state = _services.GetRequiredService(); - request?.States?.ForEach(x => state.SetState(x.Key, x.Value, source: StateSource.External)); - var viewModel = new PdfCompletionViewModel(); - - try - { - var fileModels = files.Select(x => new InstructFileModel - { - FileData = FileUtility.BuildFileDataFromFile(x), - ContentType = x.ContentType - }).ToList(); - - var fileInstruct = _services.GetRequiredService(); - var content = await fileInstruct.ReadPdf(request?.Text ?? string.Empty, fileModels, new InstructOptions - { - Provider = request?.Provider, - Model = request?.Model, - AgentId = request?.AgentId, - TemplateName = request?.TemplateName, - ImageConvertProvider = request?.ImageConvertProvider - }); - viewModel.Content = content; - return viewModel; - } - catch (Exception ex) - { - var error = $"Error in pdf completion. {ex.Message}"; - _logger.LogError(ex, error); - viewModel.Message = error; - return viewModel; - } - } - #endregion - - #region Audio - [HttpPost("/instruct/speech-to-text")] - public async Task SpeechToText([FromBody] SpeechToTextFileRequest request) - { - var fileInstruct = _services.GetRequiredService(); - var state = _services.GetRequiredService(); - request.States.ForEach(x => state.SetState(x.Key, x.Value, source: StateSource.External)); - var viewModel = new SpeechToTextViewModel(); - - try - { - var audio = request.File; - if (audio == null) - { - return new SpeechToTextViewModel { Message = "Error! Cannot find a valid audio file!" }; - } - var content = await fileInstruct.SpeechToText(audio, request.Text, new InstructOptions - { - Provider = request.Provider, - Model = request.Model, - AgentId = request.AgentId, - TemplateName = request.TemplateName - }); - viewModel.Content = content; - return viewModel; - } - catch (Exception ex) - { - var error = $"Error in speech to text. {ex.Message}"; - _logger.LogError(ex, error); - viewModel.Message = error; - return viewModel; - } - } - - [HttpPost("/instruct/speech-to-text/form")] - public async Task SpeechToText(IFormFile file, [FromForm] SpeechToTextRequest request) - { - var fileInstruct = _services.GetRequiredService(); - var state = _services.GetRequiredService(); - request?.States?.ForEach(x => state.SetState(x.Key, x.Value, source: StateSource.External)); - var viewModel = new SpeechToTextViewModel(); - - try - { - var audioData = FileUtility.BuildFileDataFromFile(file); - var content = await fileInstruct.SpeechToText(new InstructFileModel - { - FileData = audioData, - FileName = Path.GetFileNameWithoutExtension(file.FileName), - FileExtension = Path.GetExtension(file.FileName) - }, - request?.Text ?? string.Empty, - new InstructOptions - { - Provider = request?.Provider, - Model = request?.Model, - AgentId = request?.AgentId, - TemplateName = request?.TemplateName - }); - - viewModel.Content = content; - return viewModel; - } - catch (Exception ex) - { - var error = $"Error in speech-to-text. {ex.Message}"; - _logger.LogError(ex, error); - viewModel.Message = error; - return viewModel; - } - } - - [HttpPost("/instruct/text-to-speech")] - public async Task TextToSpeech([FromBody] TextToSpeechRequest request) - { - var state = _services.GetRequiredService(); - request.States.ForEach(x => state.SetState(x.Key, x.Value, source: StateSource.External)); - - var completion = CompletionProvider.GetAudioSynthesizer(_services, provider: request.Provider, model: request.Model); - var binaryData = await completion.GenerateAudioAsync(request.Text); - var stream = binaryData.ToStream(); - stream.Position = 0; - - return new FileStreamResult(stream, "audio/mpeg") { FileDownloadName = "output.mp3" }; - } - #endregion -} diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBaseController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBase/KnowledgeBaseController.cs similarity index 97% rename from src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBaseController.cs rename to src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBase/KnowledgeBaseController.cs index 27588318d..923470b81 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBaseController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBase/KnowledgeBaseController.cs @@ -68,14 +68,15 @@ public async Task> SearchVectorKnowledge([ { var options = new VectorSearchOptions { - Fields = request.Fields, - FilterGroups = request.FilterGroups, - Limit = request.Limit ?? 5, - Confidence = request.Confidence ?? 0.5f, - WithVector = request.WithVector + Fields = request?.Fields, + FilterGroups = request?.FilterGroups, + Limit = request?.Limit ?? 5, + Confidence = request?.Confidence ?? 0.5f, + WithVector = request?.WithVector ?? false, + SearchParam = request?.SearchParam }; - var results = await _knowledgeService.SearchVectorKnowledge(request.Text, collection, options); + var results = await _knowledgeService.SearchVectorKnowledge(request?.Text ?? string.Empty, collection, options); return results.Select(x => VectorKnowledgeViewModel.From(x)).ToList(); } diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/LoggerController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Logging/LoggerController.cs similarity index 100% rename from src/Infrastructure/BotSharp.OpenAPI/Controllers/LoggerController.cs rename to src/Infrastructure/BotSharp.OpenAPI/Controllers/Logging/LoggerController.cs diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/McpController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Mcp/McpController.cs similarity index 100% rename from src/Infrastructure/BotSharp.OpenAPI/Controllers/McpController.cs rename to src/Infrastructure/BotSharp.OpenAPI/Controllers/Mcp/McpController.cs diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/RealtimeController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Realtime/RealtimeController.cs similarity index 100% rename from src/Infrastructure/BotSharp.OpenAPI/Controllers/RealtimeController.cs rename to src/Infrastructure/BotSharp.OpenAPI/Controllers/Realtime/RealtimeController.cs diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/LlmProviderController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Setting/LlmProviderController.cs similarity index 78% rename from src/Infrastructure/BotSharp.OpenAPI/Controllers/LlmProviderController.cs rename to src/Infrastructure/BotSharp.OpenAPI/Controllers/Setting/LlmProviderController.cs index 6b87bec2f..6c2b335c1 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/LlmProviderController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Setting/LlmProviderController.cs @@ -1,4 +1,5 @@ using BotSharp.Abstraction.MLTasks; +using BotSharp.Abstraction.MLTasks.Filters; using BotSharp.Abstraction.MLTasks.Settings; namespace BotSharp.OpenAPI.Controllers; @@ -9,6 +10,7 @@ public class LlmProviderController : ControllerBase { private readonly IServiceProvider _services; private readonly ILlmProviderService _llmProvider; + public LlmProviderController(IServiceProvider services, ILlmProviderService llmProvider) { _services = services; @@ -22,16 +24,16 @@ public IEnumerable GetLlmProviders() } [HttpGet("/llm-provider/{provider}/models")] - public IEnumerable GetLlmProviderModels([FromRoute] string provider) + public IEnumerable GetLlmProviderModels([FromRoute] string provider, [FromQuery] LlmModelType modelType = LlmModelType.Chat) { var list = _llmProvider.GetProviderModels(provider); - return list.Where(x => x.Type == LlmModelType.Chat); + return list.Where(x => x.Type == modelType); } [HttpGet("/llm-configs")] - public List GetLlmConfigs([FromQuery] LlmConfigOptions options) + public List GetLlmConfigs([FromQuery] LlmConfigFilter filter) { - var configs = _llmProvider.GetLlmConfigs(options); + var configs = _llmProvider.GetLlmConfigs(filter); return configs; } } diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/PluginController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Setting/PluginController.cs similarity index 100% rename from src/Infrastructure/BotSharp.OpenAPI/Controllers/PluginController.cs rename to src/Infrastructure/BotSharp.OpenAPI/Controllers/Setting/PluginController.cs diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/RouterController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Setting/RouterController.cs similarity index 100% rename from src/Infrastructure/BotSharp.OpenAPI/Controllers/RouterController.cs rename to src/Infrastructure/BotSharp.OpenAPI/Controllers/Setting/RouterController.cs diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/SettingController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Setting/SettingController.cs similarity index 100% rename from src/Infrastructure/BotSharp.OpenAPI/Controllers/SettingController.cs rename to src/Infrastructure/BotSharp.OpenAPI/Controllers/Setting/SettingController.cs diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/RoleController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/User/RoleController.cs similarity index 100% rename from src/Infrastructure/BotSharp.OpenAPI/Controllers/RoleController.cs rename to src/Infrastructure/BotSharp.OpenAPI/Controllers/User/RoleController.cs diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/User/UserController.cs similarity index 100% rename from src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs rename to src/Infrastructure/BotSharp.OpenAPI/Controllers/User/UserController.cs diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/Request/AgentCodeScriptGenerationRequest.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/Request/AgentCodeScriptGenerationRequest.cs new file mode 100644 index 000000000..7b3b1607f --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/Request/AgentCodeScriptGenerationRequest.cs @@ -0,0 +1,14 @@ +using BotSharp.Abstraction.Coding.Options; +using System.Text.Json.Serialization; + +namespace BotSharp.OpenAPI.ViewModels.Agents; + +public class AgentCodeScriptGenerationRequest +{ + [JsonPropertyName("text")] + public string Text { get; set; } = string.Empty; + + [JsonPropertyName("options")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public CodeProcessOptions? Options { get; set; } +} diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/Request/AgentCodeScriptUpdateModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/Request/AgentCodeScriptUpdateModel.cs new file mode 100644 index 000000000..0b2120462 --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/Request/AgentCodeScriptUpdateModel.cs @@ -0,0 +1,23 @@ +using BotSharp.Abstraction.Agents.Models; +using BotSharp.Abstraction.Agents.Options; +using System.Text.Json.Serialization; + +namespace BotSharp.OpenAPI.ViewModels.Agents; + +public class AgentCodeScriptUpdateModel +{ + [JsonPropertyName("code_scripts")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public List? CodeScripts { get; set; } + + [JsonPropertyName("options")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public AgentCodeScriptUpdateOptions? Options { get; set; } +} + +public class AgentCodeScriptDeleteModel +{ + [JsonPropertyName("code_scripts")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public List? CodeScripts { get; set; } +} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/Request/AgentDeleteRequest.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/Request/AgentDeleteRequest.cs new file mode 100644 index 000000000..cb962c9a5 --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/Request/AgentDeleteRequest.cs @@ -0,0 +1,11 @@ +using BotSharp.Abstraction.Agents.Options; +using System.Text.Json.Serialization; + +namespace BotSharp.OpenAPI.ViewModels.Agents; + +public class AgentDeleteRequest +{ + [JsonPropertyName("options")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public AgentDeleteOptions? Options { get; set; } +} diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/View/AgentCodeScriptViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/View/AgentCodeScriptViewModel.cs new file mode 100644 index 000000000..dca5b41c4 --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/View/AgentCodeScriptViewModel.cs @@ -0,0 +1,52 @@ +using BotSharp.Abstraction.Agents.Models; +using System.Text.Json.Serialization; + +namespace BotSharp.OpenAPI.ViewModels.Agents; + +public class AgentCodeScriptViewModel +{ + + [JsonPropertyName("name")] + public string Name { get; set; } = null!; + + [JsonPropertyName("content")] + public string Content { get; set; } = null!; + + [JsonPropertyName("script_type")] + public string ScriptType { get; set; } = null!; + + public AgentCodeScriptViewModel() + { + + } + + public static AgentCodeScriptViewModel From(AgentCodeScript model) + { + if (model == null) + { + return null; + } + + return new AgentCodeScriptViewModel + { + Name = model.Name, + Content = model.Content, + ScriptType = model.ScriptType + }; + } + + public static AgentCodeScript To(AgentCodeScriptViewModel model) + { + if (model == null) + { + return null; + } + + return new AgentCodeScript + { + Name = model.Name, + Content = model.Content, + ScriptType = model.ScriptType + }; + } +} diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/Response/ConversationChartDataResponse.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/Response/ConversationChartDataResponse.cs index 197c11746..67ab1a0f3 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/Response/ConversationChartDataResponse.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/Response/ConversationChartDataResponse.cs @@ -16,25 +16,4 @@ public class ConversationChartDataResponse Data = result.Data }; } -} - - -public class ConversationChartCodeResponse -{ - public string Code { get; set; } - public string Language { get; set; } - - public static ConversationChartCodeResponse? From(ChartCodeResult? result) - { - if (result == null) - { - return null; - } - - return new() - { - Code = result.Code, - Language = result.Language - }; - } -} +} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Instructs/View/InstructBaseViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Instructs/View/InstructBaseViewModel.cs index 0b20fca36..140b7be75 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Instructs/View/InstructBaseViewModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Instructs/View/InstructBaseViewModel.cs @@ -2,12 +2,8 @@ namespace BotSharp.OpenAPI.ViewModels.Instructs; -public class InstructBaseViewModel +public class InstructBaseViewModel : ResponseBase { [JsonPropertyName("content")] public string Content { get; set; } = string.Empty; - - [JsonPropertyName("message")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string? Message { get; set; } } diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/SearchVectorKnowledgeRequest.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/SearchVectorKnowledgeRequest.cs index 50a2fc5c2..93b48dc2a 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/SearchVectorKnowledgeRequest.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/SearchVectorKnowledgeRequest.cs @@ -22,4 +22,7 @@ public class SearchVectorKnowledgeRequest [JsonPropertyName("with_vector")] public bool WithVector { get; set; } + + [JsonPropertyName("search_param")] + public VectorSearchParamModel? SearchParam { get; set; } } diff --git a/src/Plugins/BotSharp.Plugin.EmailHandler/Functions/HandleEmailReaderFn.cs b/src/Plugins/BotSharp.Plugin.EmailHandler/Functions/HandleEmailReaderFn.cs index fcd2be3a4..957bce442 100644 --- a/src/Plugins/BotSharp.Plugin.EmailHandler/Functions/HandleEmailReaderFn.cs +++ b/src/Plugins/BotSharp.Plugin.EmailHandler/Functions/HandleEmailReaderFn.cs @@ -68,7 +68,7 @@ public async Task Execute(RoleDialogModel message) var llmProviderService = _services.GetRequiredService(); var provider = llmProviderService.GetProviders().FirstOrDefault(x => x == "openai"); var model = llmProviderService.GetProviderModel(provider: provider ?? "openai", id: "gpt-4o"); - var completion = CompletionProvider.GetChatCompletion(_services, provider: provider, model: model.Name); + var completion = CompletionProvider.GetChatCompletion(_services, provider: provider, model: model?.Name); var convService = _services.GetRequiredService(); var conversationId = convService.ConversationId; var dialogs = convService.GetDialogHistory(fromBreakpoint: false); diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Vector.cs b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Vector.cs index 5d638f731..d0817c802 100644 --- a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Vector.cs +++ b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Vector.cs @@ -2,7 +2,6 @@ using BotSharp.Abstraction.VectorStorage.Enums; using BotSharp.Abstraction.VectorStorage.Filters; using BotSharp.Abstraction.VectorStorage.Options; -using Microsoft.AspNetCore.Http.HttpResults; namespace BotSharp.Plugin.KnowledgeBase.Services; @@ -82,7 +81,10 @@ public async Task> GetVectorCollections(stri }).ToList(); var vectorDb = GetVectorDb(); - if (vectorDb == null) return []; + if (vectorDb == null) + { + return []; + } var dbCollections = await vectorDb.GetCollections(); return configs.Where(x => dbCollections.Contains(x.Name)); } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs index 044a52b13..5e3a0bb35 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs @@ -1,4 +1,5 @@ using BotSharp.Abstraction.Agents.Models; +using BotSharp.Abstraction.Agents.Options; using BotSharp.Abstraction.Functions.Models; using BotSharp.Abstraction.Repositories.Filters; using BotSharp.Abstraction.Routing.Models; @@ -9,7 +10,10 @@ public partial class MongoRepository { public void UpdateAgent(Agent agent, AgentField field) { - if (agent == null || string.IsNullOrWhiteSpace(agent.Id)) return; + if (agent == null || string.IsNullOrWhiteSpace(agent.Id)) + { + return; + } switch (field) { @@ -90,7 +94,10 @@ public void UpdateAgent(Agent agent, AgentField field) #region Update Agent Fields private void UpdateAgentName(string agentId, string name) { - if (string.IsNullOrWhiteSpace(name)) return; + if (string.IsNullOrWhiteSpace(name)) + { + return; + } var filter = Builders.Filter.Eq(x => x.Id, agentId); var update = Builders.Update @@ -102,7 +109,10 @@ private void UpdateAgentName(string agentId, string name) private void UpdateAgentDescription(string agentId, string description) { - if (string.IsNullOrWhiteSpace(description)) return; + if (string.IsNullOrWhiteSpace(description)) + { + return; + } var filter = Builders.Filter.Eq(x => x.Id, agentId); var update = Builders.Update @@ -174,7 +184,10 @@ private void UpdateAgentInheritAgentId(string agentId, string? inheritAgentId) private void UpdateAgentProfiles(string agentId, List profiles) { - if (profiles == null) return; + if (profiles == null) + { + return; + } var filter = Builders.Filter.Eq(x => x.Id, agentId); var update = Builders.Update @@ -186,7 +199,10 @@ private void UpdateAgentProfiles(string agentId, List profiles) public bool UpdateAgentLabels(string agentId, List labels) { - if (labels == null) return false; + if (labels == null) + { + return false; + } var filter = Builders.Filter.Eq(x => x.Id, agentId); var update = Builders.Update @@ -199,7 +215,10 @@ public bool UpdateAgentLabels(string agentId, List labels) private void UpdateAgentRoutingRules(string agentId, List rules) { - if (rules == null) return; + if (rules == null) + { + return; + } var ruleElements = rules.Select(x => RoutingRuleMongoElement.ToMongoElement(x)).ToList(); var filter = Builders.Filter.Eq(x => x.Id, agentId); @@ -226,7 +245,10 @@ private void UpdateAgentInstructions(string agentId, string instruction, List functions) { - if (functions == null) return; + if (functions == null) + { + return; + } var functionsToUpdate = functions.Select(f => FunctionDefMongoElement.ToMongoElement(f)).ToList(); var filter = Builders.Filter.Eq(x => x.Id, agentId); @@ -239,7 +261,10 @@ private void UpdateAgentFunctions(string agentId, List functions) private void UpdateAgentTemplates(string agentId, List templates) { - if (templates == null) return; + if (templates == null) + { + return; + } var templatesToUpdate = templates.Select(t => AgentTemplateMongoElement.ToMongoElement(t)).ToList(); var filter = Builders.Filter.Eq(x => x.Id, agentId); @@ -252,7 +277,10 @@ private void UpdateAgentTemplates(string agentId, List templates) private void UpdateAgentResponses(string agentId, List responses) { - if (responses == null || string.IsNullOrWhiteSpace(agentId)) return; + if (responses == null || string.IsNullOrWhiteSpace(agentId)) + { + return; + } var responsesToUpdate = responses.Select(r => AgentResponseMongoElement.ToMongoElement(r)).ToList(); var filter = Builders.Filter.Eq(x => x.Id, agentId); @@ -265,7 +293,10 @@ private void UpdateAgentResponses(string agentId, List responses) private void UpdateAgentSamples(string agentId, List samples) { - if (samples == null) return; + if (samples == null) + { + return; + } var filter = Builders.Filter.Eq(x => x.Id, agentId); var update = Builders.Update @@ -277,7 +308,10 @@ private void UpdateAgentSamples(string agentId, List samples) private void UpdateAgentUtilities(string agentId, bool mergeUtility, List utilities) { - if (utilities == null) return; + if (utilities == null) + { + return; + } var elements = utilities?.Select(x => AgentUtilityMongoElement.ToMongoElement(x))?.ToList() ?? []; @@ -292,7 +326,10 @@ private void UpdateAgentUtilities(string agentId, bool mergeUtility, List mcps) { - if (mcps == null) return; + if (mcps == null) + { + return; + } var elements = mcps?.Select(x => AgentMcpToolMongoElement.ToMongoElement(x))?.ToList() ?? []; @@ -305,7 +342,10 @@ private void UpdateAgentMcpTools(string agentId, List mcps) } private void UpdateAgentKnowledgeBases(string agentId, List knowledgeBases) { - if (knowledgeBases == null) return; + if (knowledgeBases == null) + { + return; + } var elements = knowledgeBases?.Select(x => AgentKnowledgeBaseMongoElement.ToMongoElement(x))?.ToList() ?? []; @@ -319,7 +359,10 @@ private void UpdateAgentKnowledgeBases(string agentId, List private void UpdateAgentRules(string agentId, List rules) { - if (rules == null) return; + if (rules == null) + { + return; + } var elements = rules?.Select(x => AgentRuleMongoElement.ToMongoElement(x))?.ToList() ?? []; @@ -389,7 +432,10 @@ private void UpdateAgentAllFields(Agent agent) public Agent? GetAgent(string agentId, bool basicsOnly = false) { var agent = _dc.Agents.AsQueryable().FirstOrDefault(x => x.Id == agentId); - if (agent == null) return null; + if (agent == null) + { + return null; + } return TransformAgentDocument(agent); } @@ -451,7 +497,10 @@ join u in _dc.Users.AsQueryable() on ua.UserId equals u.Id where ua.UserId == userId || u.ExternalId == userId select ua).ToList(); - if (found.IsNullOrEmpty()) return []; + if (found.IsNullOrEmpty()) + { + return []; + } var res = found.Select(x => new UserAgent { @@ -468,7 +517,10 @@ join u in _dc.Users.AsQueryable() on ua.UserId equals u.Id foreach (var item in res) { var agent = agents.FirstOrDefault(x => x.Id == item.AgentId); - if (agent == null) continue; + if (agent == null) + { + continue; + } item.Agent = agent; } @@ -480,7 +532,10 @@ public List GetAgentResponses(string agentId, string prefix, string inte { var responses = new List(); var agent = _dc.Agents.AsQueryable().FirstOrDefault(x => x.Id == agentId); - if (agent == null) return responses; + if (agent == null) + { + return responses; + } return agent.Responses.Where(x => x.Prefix == prefix && x.Intent == intent).Select(x => x.Content).ToList(); } @@ -488,21 +543,33 @@ public List GetAgentResponses(string agentId, string prefix, string inte public string GetAgentTemplate(string agentId, string templateName) { var agent = _dc.Agents.AsQueryable().FirstOrDefault(x => x.Id == agentId); - if (agent == null) return string.Empty; + if (agent == null) + { + return string.Empty; + } return agent.Templates?.FirstOrDefault(x => x.Name.IsEqualTo(templateName))?.Content ?? string.Empty; } public bool PatchAgentTemplate(string agentId, AgentTemplate template) { - if (string.IsNullOrEmpty(agentId) || template == null) return false; + if (string.IsNullOrEmpty(agentId) || template == null) + { + return false; + } var filter = Builders.Filter.Eq(x => x.Id, agentId); var agent = _dc.Agents.Find(filter).FirstOrDefault(); - if (agent == null || agent.Templates.IsNullOrEmpty()) return false; + if (agent == null || agent.Templates.IsNullOrEmpty()) + { + return false; + } var foundTemplate = agent.Templates.FirstOrDefault(x => x.Name.IsEqualTo(template.Name)); - if (foundTemplate == null) return false; + if (foundTemplate == null) + { + return false; + } foundTemplate.Content = template.Content; var update = Builders.Update.Set(x => x.Templates, agent.Templates); @@ -512,11 +579,17 @@ public bool PatchAgentTemplate(string agentId, AgentTemplate template) public bool AppendAgentLabels(string agentId, List labels) { - if (labels.IsNullOrEmpty()) return false; + if (labels.IsNullOrEmpty()) + { + return false; + } var filter = Builders.Filter.Eq(x => x.Id, agentId); var agent = _dc.Agents.Find(filter).FirstOrDefault(); - if (agent == null) return false; + if (agent == null) + { + return false; + } var prevLabels = agent.Labels ?? []; var curLabels = prevLabels.Concat(labels).Distinct().ToList(); @@ -530,7 +603,10 @@ public bool AppendAgentLabels(string agentId, List labels) public void BulkInsertAgents(List agents) { - if (agents.IsNullOrEmpty()) return; + if (agents.IsNullOrEmpty()) + { + return; + } var agentDocs = agents.Select(x => new AgentDocument { @@ -569,10 +645,16 @@ public void BulkInsertAgents(List agents) public void BulkInsertUserAgents(List userAgents) { - if (userAgents.IsNullOrEmpty()) return; + if (userAgents.IsNullOrEmpty()) + { + return; + } var filtered = userAgents.Where(x => !string.IsNullOrEmpty(x.UserId) && !string.IsNullOrEmpty(x.AgentId)).ToList(); - if (filtered.IsNullOrEmpty()) return; + if (filtered.IsNullOrEmpty()) + { + return; + } var userAgentDocs = filtered.Select(x => new UserAgentDocument { @@ -604,22 +686,33 @@ public bool DeleteAgents() } } - public bool DeleteAgent(string agentId) + public bool DeleteAgent(string agentId, AgentDeleteOptions? options = null) { try { - if (string.IsNullOrEmpty(agentId)) return false; + if (string.IsNullOrEmpty(agentId)) + { + return false; + } + + if (options == null || options.DeleteUserAgents) + { + var userAgentFilter = Builders.Filter.Eq(x => x.AgentId, agentId); + _dc.UserAgents.DeleteMany(userAgentFilter); + } + + if (options == null || options.DeleteRoleAgents) + { + var roleAgentFilter = Builders.Filter.Eq(x => x.AgentId, agentId); + _dc.RoleAgents.DeleteMany(roleAgentFilter); + } + + DeleteAgentCodeScripts(agentId, options?.ToDeleteCodeScripts); - var agentFilter = Builders.Filter.Eq(x => x.Id, agentId); - var userAgentFilter = Builders.Filter.Eq(x => x.AgentId, agentId); - var roleAgentFilter = Builders.Filter.Eq(x => x.AgentId, agentId); var agentTaskFilter = Builders.Filter.Eq(x => x.AgentId, agentId); - var agentCodeFilter = Builders.Filter.Eq(x => x.AgentId, agentId); - - _dc.UserAgents.DeleteMany(userAgentFilter); - _dc.RoleAgents.DeleteMany(roleAgentFilter); _dc.AgentTasks.DeleteMany(agentTaskFilter); - _dc.AgentCodeScripts.DeleteMany(agentCodeFilter); + + var agentFilter = Builders.Filter.Eq(x => x.Id, agentId); _dc.Agents.DeleteOne(agentFilter); return true; } @@ -631,7 +724,10 @@ public bool DeleteAgent(string agentId) private Agent TransformAgentDocument(AgentDocument? agentDoc) { - if (agentDoc == null) return new Agent(); + if (agentDoc == null) + { + return new Agent(); + } return new Agent { diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentCodeScript.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentCodeScript.cs index fa2830f2a..2f9d461d9 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentCodeScript.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentCodeScript.cs @@ -1,6 +1,5 @@ using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Repositories.Filters; -using BotSharp.Abstraction.Repositories.Models; using BotSharp.Abstraction.Repositories.Options; namespace BotSharp.Plugin.MongoStorage.Repository; @@ -93,7 +92,7 @@ public bool BulkInsertAgentCodeScripts(string agentId, List scr { var script = AgentCodeScriptDocument.ToMongoModel(x); script.AgentId = agentId; - script.Id = x.Id.IfNullOrEmptyAs(Guid.NewGuid().ToString()); + script.Id = x.Id.IfNullOrEmptyAs(Guid.NewGuid().ToString())!; script.CreatedTime = DateTime.UtcNow; script.UpdatedTime = DateTime.UtcNow; return script; @@ -111,6 +110,8 @@ public bool DeleteAgentCodeScripts(string agentId, List? script } DeleteResult deleted; + var builder = Builders.Filter; + if (scripts != null) { var scriptPaths = scripts.Select(x => x.CodePath); @@ -119,13 +120,16 @@ public bool DeleteAgentCodeScripts(string agentId, List? script new BsonDocument("$concat", new BsonArray { "$ScriptType", "/", "$Name" }), new BsonArray(scriptPaths) })); - - var filterDef = new BsonDocumentFilterDefinition(exprFilter); + + var filterDef = builder.And( + builder.Eq(x => x.AgentId, agentId), + new BsonDocumentFilterDefinition(exprFilter) + ); deleted = _dc.AgentCodeScripts.DeleteMany(filterDef); } else { - deleted = _dc.AgentCodeScripts.DeleteMany(Builders.Filter.Empty); + deleted = _dc.AgentCodeScripts.DeleteMany(builder.Eq(x => x.AgentId, agentId)); } return deleted.DeletedCount > 0; diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentTask.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentTask.cs index 060994d44..f4f4fb656 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentTask.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentTask.cs @@ -1,6 +1,5 @@ using BotSharp.Abstraction.Repositories.Filters; using BotSharp.Abstraction.Tasks.Models; -using MongoDB.Driver; namespace BotSharp.Plugin.MongoStorage.Repository; @@ -67,10 +66,16 @@ public async ValueTask> GetAgentTasks(AgentTaskFilter filt public AgentTask? GetAgentTask(string agentId, string taskId) { - if (string.IsNullOrEmpty(taskId)) return null; + if (string.IsNullOrEmpty(taskId)) + { + return null; + } var taskDoc = _dc.AgentTasks.AsQueryable().FirstOrDefault(x => x.Id == taskId); - if (taskDoc == null) return null; + if (taskDoc == null) + { + return null; + } var agentDoc = _dc.Agents.AsQueryable().FirstOrDefault(x => x.Id == taskDoc.AgentId); var agent = TransformAgentDocument(agentDoc); @@ -107,11 +112,17 @@ public void BulkInsertAgentTasks(string agentId, List tasks) public void UpdateAgentTask(AgentTask task, AgentTaskField field) { - if (task == null || string.IsNullOrEmpty(task.Id)) return; + if (task == null || string.IsNullOrEmpty(task.Id)) + { + return; + } var filter = Builders.Filter.Eq(x => x.Id, task.Id); var taskDoc = _dc.AgentTasks.Find(filter).FirstOrDefault(); - if (taskDoc == null) return; + if (taskDoc == null) + { + return; + } switch (field) { diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Image/ImageClientExtensions.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Image/ImageClientExtensions.cs index 7ce780357..9f2555042 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Image/ImageClientExtensions.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Image/ImageClientExtensions.cs @@ -17,6 +17,7 @@ public static class ImageClientExtensions /// Generates image edits with multiple input images for composition /// /// The ImageClient instance + /// The LLM model /// Array of image streams to compose /// Array of corresponding file names for the images /// The prompt describing the desired composition diff --git a/src/Plugins/BotSharp.Plugin.Planner/Sequential/SequentialPlanner.cs b/src/Plugins/BotSharp.Plugin.Planner/Sequential/SequentialPlanner.cs index 94b4a6873..a3c7e9464 100644 --- a/src/Plugins/BotSharp.Plugin.Planner/Sequential/SequentialPlanner.cs +++ b/src/Plugins/BotSharp.Plugin.Planner/Sequential/SequentialPlanner.cs @@ -171,7 +171,7 @@ public async Task GetDecomposedStepAsync(Agent router, string me // chat completion var completion = CompletionProvider.GetChatCompletion(_services, provider: "openai", - model: model.Name); + model: model?.Name); int retryCount = 0; while (retryCount < 2) diff --git a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Models/PackageInstallResult.cs b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Models/PackageInstallResult.cs index b6fdb9941..a7b87cf64 100644 --- a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Models/PackageInstallResult.cs +++ b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Models/PackageInstallResult.cs @@ -1,7 +1,8 @@ +using BotSharp.Abstraction.Models; + namespace BotSharp.Plugin.PythonInterpreter.Models; -internal class PackageInstallResult +internal class PackageInstallResult : ResponseBase { - internal bool Success { get; set; } - internal string ErrorMsg { get; set; } + } diff --git a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyCodeInterpreter.cs b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyCodeInterpreter.cs index 76bd6fe60..8ee5d536a 100644 --- a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyCodeInterpreter.cs +++ b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyCodeInterpreter.cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.Coding.Models; using Microsoft.Extensions.Logging; using Python.Runtime; using System.Threading; @@ -35,6 +36,59 @@ public async Task RunAsync(string codeScript, CodeInterpr return InnerRunCode(codeScript, options); } + public async Task GenerateCodeScriptAsync(string text, CodeGenerationOptions? options = null) + { + Agent? agent = null; + + var agentId = options?.AgentId; + var templateName = options?.TemplateName; + + var agentService = _services.GetRequiredService(); + if (!string.IsNullOrEmpty(agentId)) + { + agent = await agentService.GetAgent(agentId); + } + + var instruction = string.Empty; + if (agent != null && !string.IsNullOrEmpty(templateName)) + { + instruction = agent.Templates?.FirstOrDefault(x => x.Name.IsEqualTo(templateName))?.Content; + } + + var innerAgent = new Agent + { + Id = agent?.Id ?? BuiltInAgentId.AIProgrammer, + Name = agent?.Name ?? "AI Programmer", + Instruction = instruction, + LlmConfig = new AgentLlmConfig + { + Provider = options?.Provider ?? "openai", + Model = options?.Model ?? "gpt-5-mini", + MaxOutputTokens = options?.MaxOutputTokens, + ReasoningEffortLevel = options?.ReasoningEffortLevel + }, + TemplateDict = options?.Data ?? new() + }; + + text = text.IfNullOrEmptyAs("Please follow the instruction to generate code script.")!; + var completion = CompletionProvider.GetChatCompletion(_services, provider: innerAgent.LlmConfig.Provider, model: innerAgent.LlmConfig.Model); + var response = await completion.GetChatCompletions(innerAgent, new List + { + new RoleDialogModel(AgentRole.User, text) + { + CurrentAgentId = innerAgent.Id + } + }); + + return new CodeGenerationResult + { + Success = true, + Content = response.Content, + Language = options?.Language ?? "python" + }; + } + + #region Private methods private CodeInterpretResponse InnerRunCode(string codeScript, CodeInterpretOptions? options = null) { try @@ -125,4 +179,5 @@ private CodeInterpretResponse CoreRun(string codeScript, CodeInterpretOptions? o } } } + #endregion } diff --git a/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs b/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs index 32543f83e..cbca32ae5 100644 --- a/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs +++ b/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs @@ -103,12 +103,18 @@ public async Task> GetCollections() { var exist = await DoesCollectionExist(collectionName); - if (!exist) return null; + if (!exist) + { + return null; + } var client = GetClient(); var details = await client.GetCollectionInfoAsync(collectionName); - if (details == null) return null; + if (details == null) + { + return null; + } var payloadSchema = details.PayloadSchema?.Select(x => new PayloadSchemaDetail { @@ -287,11 +293,13 @@ public async Task> Search(string collectionNam options ??= VectorSearchOptions.Default(); Filter? queryFilter = BuildQueryFilter(options.FilterGroups); WithPayloadSelector? payloadSelector = BuildPayloadSelector(options.Fields); + SearchParams? param = BuildSearchParam(options?.SearchParam); var client = GetClient(); var points = await client.SearchAsync(collectionName, vector, limit: (ulong)options.Limit.GetValueOrDefault(), + searchParams: param, scoreThreshold: options.Confidence, filter: queryFilter, payloadSelector: payloadSelector, @@ -824,6 +832,21 @@ public async Task DeleteCollectionShapshot(string collectionName, string s }; } + private SearchParams? BuildSearchParam(VectorSearchParamModel? param) + { + if (param == null + || param.ExactSearch == null) + { + return null; + } + + var search = new SearchParams + { + Exact = param.ExactSearch.Value + }; + return search; + } + private PayloadSchemaType ConvertPayloadSchemaType(string schemaType) { PayloadSchemaType res; diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Services/SqlChartProcessor.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Services/SqlChartProcessor.cs new file mode 100644 index 000000000..858cf11f0 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Services/SqlChartProcessor.cs @@ -0,0 +1,47 @@ +using BotSharp.Abstraction.Chart.Options; +using BotSharp.Abstraction.Repositories; + +namespace BotSharp.Plugin.SqlDriver.Services; + +public class SqlChartProcessor : IChartProcessor +{ + private readonly IServiceProvider _services; + private readonly ILogger _logger; + + public SqlChartProcessor( + IServiceProvider services, + ILogger logger) + { + _services = services; + _logger = logger; + } + + public string Provider => "sql_driver"; + + public async Task GetConversationChartDataAsync(string conversationId, string messageId, ChartDataOptions? options = null) + { + if (string.IsNullOrWhiteSpace(conversationId)) + { + return null; + } + + if (!string.IsNullOrWhiteSpace(options?.TargetStateName)) + { + var db = _services.GetRequiredService(); + var states = db.GetConversationStates(conversationId); + var value = states?.GetValueOrDefault(options?.TargetStateName)?.Values?.LastOrDefault()?.Data; + + // To do + //return new ChartDataResult(); + } + + // Dummy data for testing + var data = new + { + categories = new string[] { "A", "B", "C", "D", "E" }, + values = new int[] { 42, 67, 29, 85, 53 } + }; + + return new ChartDataResult { Data = data }; + } +} diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Services/SqlChartService.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Services/SqlChartService.cs deleted file mode 100644 index 886ac7c6d..000000000 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/Services/SqlChartService.cs +++ /dev/null @@ -1,156 +0,0 @@ -using BotSharp.Abstraction.Chart.Options; -using BotSharp.Abstraction.Options; -using BotSharp.Abstraction.Repositories; -using BotSharp.Core.Infrastructures; -using BotSharp.Plugin.SqlDriver.LlmContext; - -namespace BotSharp.Plugin.SqlDriver.Services; - -public class SqlChartService : IBotSharpChartService -{ - private readonly IServiceProvider _services; - private readonly ILogger _logger; - private readonly BotSharpOptions _botSharpOptions; - - public SqlChartService( - IServiceProvider services, - ILogger logger, - BotSharpOptions botSharpOptions) - { - _services = services; - _logger = logger; - _botSharpOptions = botSharpOptions; - } - - public string Provider => "sql_driver"; - - public async Task GetConversationChartData(string conversationId, string messageId, ChartDataOptions options) - { - if (string.IsNullOrWhiteSpace(conversationId)) - { - return null; - } - - if (!string.IsNullOrWhiteSpace(options?.TargetStateName)) - { - var db = _services.GetRequiredService(); - var states = db.GetConversationStates(conversationId); - var value = states?.GetValueOrDefault(options?.TargetStateName)?.Values?.LastOrDefault()?.Data; - - // To do - //return new ChartDataResult(); - } - - // Dummy data for testing - var data = new - { - categories = new string[] { "A", "B", "C", "D", "E" }, - values = new int[] { 42, 67, 29, 85, 53 } - }; - - return new ChartDataResult { Data = data }; - } - - public async Task GetConversationChartCode(string conversationId, string messageId, ChartCodeOptions options) - { - if (string.IsNullOrWhiteSpace(conversationId)) - { - return null; - } - - var agentService = _services.GetRequiredService(); - - var agentId = options.AgentId.IfNullOrEmptyAs(BuiltInAgentId.UtilityAssistant); - var templateName = options.TemplateName.IfNullOrEmptyAs("util-chart-plot_instruction"); - var inst = GetChartCodeInstruction(agentId, templateName); - - var agent = await agentService.GetAgent(agentId); - agent = new Agent - { - Id = agent.Id, - Name = agent.Name, - Instruction = inst, - LlmConfig = new AgentLlmConfig - { - MaxOutputTokens = options.Llm?.MaxOutputTokens ?? 8192, - ReasoningEffortLevel = options.Llm?.ReasoningEffortLevel - }, - TemplateDict = new(BuildChartStates(options)) - }; - - var dialogs = new List - { - new RoleDialogModel - { - Role = AgentRole.User, - MessageId = messageId, - Content = options.Text.IfNullOrEmptyAs("Please follow the instruction to generate response.") - } - }; - var response = await GetChatCompletion(agent, dialogs, options); - var obj = response.JsonContent(); - - return new ChartCodeResult - { - Code = obj?.JsCode, - Language = "javascript" - }; - } - - - private Dictionary BuildChartStates(ChartCodeOptions options) - { - var states = new Dictionary(); - - if (!options.States.IsNullOrEmpty()) - { - foreach (var item in options.States) - { - if (item.Value == null) - { - continue; - } - states[item.Key] = item.Value; - } - } - return states; - } - - private string GetChartCodeInstruction(string agentId, string templateName) - { - var db = _services.GetRequiredService(); - var templateContent = db.GetAgentTemplate(agentId, templateName); - return templateContent; - } - - private async Task GetChatCompletion(Agent agent, List dialogs, ChartCodeOptions options) - { - try - { - var (provider, model) = GetLlmProviderModel(options); - var completion = CompletionProvider.GetChatCompletion(_services, provider: provider, model: model); - var response = await completion.GetChatCompletions(agent, dialogs); - return response.Content; - } - catch (Exception ex) - { - var error = $"Error when generating chart code. {ex.Message}"; - _logger.LogWarning(ex, error); - return error; - } - } - - private (string, string) GetLlmProviderModel(ChartCodeOptions options) - { - var provider = "openai"; - var model = "gpt-5"; - - if (options?.Llm != null) - { - provider = options.Llm.Provider.IfNullOrEmptyAs(provider); - model = options.Llm.Model.IfNullOrEmptyAs(model); - } - - return (provider, model); - } -} diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/SqlDriverPlugin.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/SqlDriverPlugin.cs index 1dd27ad79..7d079e57e 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/SqlDriverPlugin.cs +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/SqlDriverPlugin.cs @@ -30,6 +30,6 @@ public void RegisterDI(IServiceCollection services, IConfiguration config) services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); } } diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json index 9283d9be4..00d27465c 100644 --- a/src/WebStarter/appsettings.json +++ b/src/WebStarter/appsettings.json @@ -50,7 +50,11 @@ "Name": "gpt-35-turbo", "Version": "1106", "ApiKey": "", - "Endpoint": "https://gpt-35-turbo-instruct.openai.azure.com/" + "Endpoint": "https://gpt-35-turbo-instruct.openai.azure.com/", + "Type": "chat", + "Capabilities": [ + "Chat" + ] }, { "Name": "gpt-35-turbo-instruct", @@ -58,6 +62,9 @@ "ApiKey": "", "Endpoint": "https://gpt-35-turbo-instruct.openai.azure.com/", "Type": "text", + "Capabilities": [ + "Text" + ], "Cost": { "TextInputCost": 0.0015, "CachedTextInputCost": 0, @@ -74,7 +81,10 @@ "Models": [ { "Name": "llama-2-7b-guanaco-qlora.Q2_K.gguf", - "Type": "chat" + "Type": "chat", + "Capabilities": [ + "Chat" + ] } ] }, @@ -83,11 +93,17 @@ "Models": [ { "Name": "mistralai/Mistral-7B-v0.1", - "Type": "text" + "Type": "text", + "Capabilities": [ + "Text" + ] }, { "Name": "TinyLlama/TinyLlama-1.1B-Chat-v1.0", - "Type": "text" + "Type": "text", + "Capabilities": [ + "Text" + ] } ] }, @@ -97,6 +113,9 @@ { "Name": "gpt-35-turbo", "Type": "chat", + "Capabilities": [ + "Chat" + ], "Cost": { "TextInputCost": 0.0015, "CachedTextInputCost": 0, @@ -114,6 +133,9 @@ { "Name": "chatglm3_6b", "Type": "chat", + "Capabilities": [ + "Chat" + ], "Cost": { "TextInputCost": 0.0015, "CachedTextInputCost": 0, @@ -135,6 +157,10 @@ "ApiKey": "", "Type": "chat", "MultiModal": true, + "Capabilities": [ + "Chat", + "ImageReading" + ], "Cost": { "TextInputCost": 0.00015, "CachedTextInputCost": 0, @@ -151,6 +177,10 @@ "ApiKey": "", "Type": "chat", "MultiModal": true, + "Capabilities": [ + "Chat", + "ImageReading" + ], "Cost": { "TextInputCost": 0.0025, "CachedTextInputCost": 0, @@ -165,9 +195,11 @@ "Name": "gpt-4o-mini-realtime-preview-2024-12-17", "Version": "2024-12-17", "ApiKey": "", - "Type": "chat", + "Type": "realtime", "MultiModal": true, - "RealTime": true, + "Capabilities": [ + "Realtime" + ], "Cost": { "TextInputCost": 0.0025, "CachedTextInputCost": 0, @@ -183,6 +215,9 @@ "Version": "3-small", "ApiKey": "", "Type": "embedding", + "Capabilities": [ + "Embedding" + ], "Embedding": { "Dimension": 1536 } @@ -194,7 +229,11 @@ "ApiKey": "", "Endpoint": "", "Type": "image", - "ImageGeneration": true, + "Capabilities": [ + "ImageGeneration", + "ImageEdit", + "ImageVariation" + ], "Image": { "Generation": { "Size": { @@ -246,7 +285,9 @@ "Version": "dall-e-3", "ApiKey": "", "Type": "image", - "ImageGeneration": true, + "Capabilities": [ + "ImageGeneration" + ], "Image": { "Generation": { "Size": { @@ -282,7 +323,10 @@ "Version": "gpt-image-1", "ApiKey": "", "Type": "image", - "ImageGeneration": true, + "Capabilities": [ + "ImageGeneration", + "ImageEdit" + ], "Image": { "Generation": { "Size": { @@ -328,7 +372,10 @@ "Version": "gpt-image-1-mini", "ApiKey": "", "Type": "image", - "ImageGeneration": true, + "Capabilities": [ + "ImageGeneration", + "ImageEdit" + ], "Image": { "Generation": { "Size": { @@ -378,6 +425,10 @@ "ApiKey": "", "Endpoint": "https://api.deepseek.com/v1/", "Type": "chat", + "Capabilities": [ + "Chat", + "ImageReading" + ], "Cost": { "TextInputCost": 0.0015, "CachedTextInputCost": 0, @@ -397,6 +448,11 @@ "ApiKey": "", "Type": "chat", "MultiModal": true, + "Capabilities": [ + "Chat", + "ImageReading", + "PdfReading" + ], "Cost": { "TextInputCost": 0.0015, "CachedTextInputCost": 0, @@ -411,6 +467,9 @@ "ApiKey": "", "Type": "realtime", "MultiModal": true, + "Capabilities": [ + "Realtime" + ], "Cost": { "TextInputCost": 0.0015, "CachedTextInputCost": 0, diff --git a/tests/BotSharp.LLM.Tests/Core/TestAgentService.cs b/tests/BotSharp.LLM.Tests/Core/TestAgentService.cs index 2ed0557fa..add520c70 100644 --- a/tests/BotSharp.LLM.Tests/Core/TestAgentService.cs +++ b/tests/BotSharp.LLM.Tests/Core/TestAgentService.cs @@ -1,6 +1,7 @@ using BotSharp.Abstraction.Agents; using BotSharp.Abstraction.Agents.Enums; using BotSharp.Abstraction.Agents.Models; +using BotSharp.Abstraction.Agents.Options; using BotSharp.Abstraction.Functions.Models; using BotSharp.Abstraction.Models; using BotSharp.Abstraction.Plugins.Models; @@ -76,7 +77,7 @@ public Task GetAgent(string id) return Task.FromResult(new Agent()); } - public Task DeleteAgent(string id) + public Task DeleteAgent(string id, AgentDeleteOptions? options = null) { return Task.FromResult(true); }