From 4e690c88ce0743eb973c747989b74ed006936eba Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Wed, 29 Oct 2025 20:26:57 -0500 Subject: [PATCH 01/16] add agent rule output args --- .../Rules/IRuleTrigger.cs | 4 +++ .../Controllers/Agent/AgentController.Rule.cs | 7 +++-- .../Agents/View/AgentRuleViewModel.cs | 29 +++++++++++++++++++ 3 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/View/AgentRuleViewModel.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleTrigger.cs b/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleTrigger.cs index 7b6c9bc25..508cd375f 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleTrigger.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleTrigger.cs @@ -1,3 +1,5 @@ +using System.Text.Json; + namespace BotSharp.Abstraction.Rules; public interface IRuleTrigger @@ -9,4 +11,6 @@ public interface IRuleTrigger string EntityType { get; set; } string EntityId { get; set; } + + JsonDocument OutputArgs => JsonDocument.Parse("{}"); } diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/Agent/AgentController.Rule.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Agent/AgentController.Rule.cs index 4daefc717..5feddec47 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/Agent/AgentController.Rule.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Agent/AgentController.Rule.cs @@ -6,12 +6,13 @@ namespace BotSharp.OpenAPI.Controllers; public partial class AgentController { [HttpGet("/rule/triggers")] - public IEnumerable GetRuleTriggers() + public IEnumerable GetRuleTriggers() { var triggers = _services.GetServices(); - return triggers.Select(x => new AgentRule + return triggers.Select(x => new AgentRuleViewModel { - TriggerName = x.GetType().Name + TriggerName = x.GetType().Name, + OutputArgs = x.OutputArgs }).OrderBy(x => x.TriggerName).ToList(); } diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/View/AgentRuleViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/View/AgentRuleViewModel.cs new file mode 100644 index 000000000..6297a6733 --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/View/AgentRuleViewModel.cs @@ -0,0 +1,29 @@ +using System.Text.Json.Serialization; + +namespace BotSharp.OpenAPI.ViewModels.Agents; + +public class AgentRuleViewModel +{ + [JsonPropertyName("trigger_name")] + public string TriggerName { get; set; } = string.Empty; + + [JsonPropertyName("output_args")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public JsonDocument? OutputArgs { get; set; } + + [JsonPropertyName("json_args")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? JsonArgs + { + get + { + if (OutputArgs == null) + { + return null; + } + + var json = JsonSerializer.Serialize(OutputArgs.RootElement, new JsonSerializerOptions { WriteIndented = true }); + return $"```json\r\n{json}\r\n```"; + } + } +} From 9750cc2843963587e41e26f470d9fe026beaf7f5 Mon Sep 17 00:00:00 2001 From: Jicheng Lu Date: Wed, 29 Oct 2025 21:55:25 -0500 Subject: [PATCH 02/16] refine rule engine --- .../BotSharp.Abstraction/Rules/IRuleEngine.cs | 3 +- .../Rules/Options/RuleTriggerOptions.cs | 31 +++++ .../BotSharp.Abstraction/Using.cs | 3 +- .../BotSharp.Core.Rules/Engines/RuleEngine.cs | 127 ++++++++++++++++-- 4 files changed, 148 insertions(+), 16 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Rules/Options/RuleTriggerOptions.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleEngine.cs b/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleEngine.cs index c870f145f..62483a303 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleEngine.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleEngine.cs @@ -2,5 +2,6 @@ namespace BotSharp.Abstraction.Rules; public interface IRuleEngine { - Task> Triggered(IRuleTrigger trigger, string data, List? states = null); + Task> Trigger(IRuleTrigger trigger, string text, RuleTriggerOptions? options = null) + => throw new NotImplementedException(); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Rules/Options/RuleTriggerOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Rules/Options/RuleTriggerOptions.cs new file mode 100644 index 000000000..3c9e66709 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Rules/Options/RuleTriggerOptions.cs @@ -0,0 +1,31 @@ +using System.Text.Json; + +namespace BotSharp.Abstraction.Rules.Options; + +public class RuleTriggerOptions +{ + /// + /// Code processor provider + /// + public string? CodeProcessor { get; set; } = "botsharp-py-interpreter"; + + /// + /// Code script name + /// + public string? CodeScriptName { get; set; } + + /// + /// Json arguments + /// + public JsonDocument? Arguments { get; set; } + + /// + /// Agent where the code script is stored + /// + public string? AgentId { get; set; } + + /// + /// States + /// + public List? States { get; set; } = null; +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Using.cs b/src/Infrastructure/BotSharp.Abstraction/Using.cs index d20775375..41f8faaea 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Using.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Using.cs @@ -21,4 +21,5 @@ global using BotSharp.Abstraction.Knowledges.Models; global using BotSharp.Abstraction.Crontab.Models; global using BotSharp.Abstraction.MCP.Models; -global using BotSharp.Abstraction.Settings; \ No newline at end of file +global using BotSharp.Abstraction.Settings; +global using BotSharp.Abstraction.Rules.Options; \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs index 1b3006663..10e739d1a 100644 --- a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs +++ b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs @@ -1,25 +1,32 @@ +using BotSharp.Abstraction.Coding; using BotSharp.Abstraction.Conversations; using BotSharp.Abstraction.Models; using BotSharp.Abstraction.Repositories.Filters; +using BotSharp.Abstraction.Rules.Options; using BotSharp.Abstraction.Utilities; using Microsoft.Extensions.Logging; using System.Data; +using System.Text.Json; namespace BotSharp.Core.Rules.Engines; public class RuleEngine : IRuleEngine { private readonly IServiceProvider _services; - private readonly ILogger _logger; + private readonly ILogger _logger; - public RuleEngine(IServiceProvider services, ILogger logger) + public RuleEngine( + IServiceProvider services, + ILogger logger) { _services = services; _logger = logger; } - public async Task> Triggered(IRuleTrigger trigger, string data, List? states = null) + public async Task> Trigger(IRuleTrigger trigger, string text, RuleTriggerOptions? options = null) { + var newConversationIds = new List(); + // Pull all user defined rules var agentService = _services.GetRequiredService(); var agents = await agentService.GetAgents(new AgentFilter @@ -30,34 +37,45 @@ public async Task> Triggered(IRuleTrigger trigger, string da } }); - var preFilteredAgents = agents.Items.Where(x => - x.Rules.Exists(r => r.TriggerName == trigger.Name && - !x.Disabled)).ToList(); + var isTriggered = true; - // Trigger the agents - var instructService = _services.GetRequiredService(); - var newConversationIds = new List(); + // Apply code trigger + if (options != null + && !string.IsNullOrWhiteSpace(options.CodeProcessor) + && !string.IsNullOrWhiteSpace(options.AgentId)) + { + var scriptName = options.CodeScriptName ?? $"{trigger.Name}_cron.py"; + isTriggered = await HandleCodeTrigger(options.AgentId, scriptName, options.CodeProcessor, trigger.Name, options.Arguments, options.States); + } - foreach (var agent in preFilteredAgents) + if (!isTriggered) + { + return newConversationIds; + } + + var filteredAgents = agents.Items.Where(x => x.Rules.Exists(r => r.TriggerName == trigger.Name && !x.Disabled)).ToList(); + + // Trigger agents + foreach (var agent in filteredAgents) { var convService = _services.GetRequiredService(); var conv = await convService.NewConversation(new Conversation { Channel = trigger.Channel, - Title = data, + Title = text, AgentId = agent.Id }); - var message = new RoleDialogModel(AgentRole.User, data); + var message = new RoleDialogModel(AgentRole.User, text); var allStates = new List { new("channel", trigger.Channel) }; - if (states != null) + if (options?.States != null) { - allStates.AddRange(states); + allStates.AddRange(options.States); } convService.SetConversationId(conv.Id, allStates); @@ -92,4 +110,85 @@ await convService.SendMessage(agent.Id, return newConversationIds; } + + #region Private methods + private async Task HandleCodeTrigger(string agentId, string scriptName, string codeProcessor, string triggerName, JsonDocument? args = null, IEnumerable? states = null) + { + var processor = _services.GetServices().FirstOrDefault(x => x.Provider.IsEqualTo(codeProcessor)); + if (processor == null) + { + _logger.LogWarning($"Unable to find code processor: {codeProcessor}."); + return false; + } + + var agentService = _services.GetRequiredService(); + var codeScript = await agentService.GetAgentCodeScript(agentId, scriptName, scriptType: AgentCodeScriptType.Src); + + if (string.IsNullOrWhiteSpace(codeScript)) + { + _logger.LogWarning($"Unable to find code script ({scriptName}) in agent ({agentId})."); + return false; + } + + var msg = $"rule trigger ({triggerName}) code script ({scriptName}) in agent ({agentId}) => args: {args?.RootElement.GetRawText()}."; + + try + { + var response = await processor.RunAsync(codeScript, options: new() + { + ScriptName = scriptName, + Arguments = BuildArguments(args, states) + }); + + if (response == null || !response.Success) + { + _logger.LogWarning($"Failed to handle {msg}"); + return false; + } + + bool result; + LogLevel logLevel; + if (response.Result.IsEqualTo("true") || response.Result.IsEqualTo("1")) + { + logLevel = LogLevel.Information; + result = true; + } + else + { + logLevel = LogLevel.Warning; + result = false; + } + + _logger.Log(logLevel, $"Code result: {response.Result}. {msg}"); + return result; + } + catch (Exception ex) + { + _logger.LogError(ex, $"Error when handling {msg}"); + return false; + } + } + + private IEnumerable BuildArguments(JsonDocument? args, IEnumerable? states) + { + var dict = new Dictionary(); + if (!states.IsNullOrEmpty()) + { + foreach (var state in states) + { + if (state.Value != null) + { + dict[state.Key] = state.Value.ConvertToString(); + } + } + } + + if (args != null) + { + dict["trigger_input"] = args.RootElement.GetRawText(); + } + + return dict.Select(x => new KeyValue(x.Key, x.Value)); + } +#endregion } From 920fcc6261476241ade18ef35bb6d7415759d66b Mon Sep 17 00:00:00 2001 From: Jicheng Lu Date: Wed, 29 Oct 2025 21:59:25 -0500 Subject: [PATCH 03/16] rename --- .../BotSharp.Core.Rules/Engines/RuleEngine.cs | 25 +++---------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs index 10e739d1a..e0ed8a72a 100644 --- a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs +++ b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs @@ -45,7 +45,7 @@ public async Task> Trigger(IRuleTrigger trigger, string text && !string.IsNullOrWhiteSpace(options.AgentId)) { var scriptName = options.CodeScriptName ?? $"{trigger.Name}_cron.py"; - isTriggered = await HandleCodeTrigger(options.AgentId, scriptName, options.CodeProcessor, trigger.Name, options.Arguments, options.States); + isTriggered = await TriggerCodeScript(options.AgentId, scriptName, options.CodeProcessor, trigger.Name, options.Arguments, options.States); } if (!isTriggered) @@ -87,32 +87,13 @@ await convService.SendMessage(agent.Id, convService.SaveStates(); newConversationIds.Add(conv.Id); - - /*foreach (var rule in agent.Rules) - { - var userSay = $"===Input data with Before and After values===\r\n{data}\r\n\r\n===Trigger Criteria===\r\n{rule.Criteria}\r\n\r\nJust output 1 or 0 without explanation: "; - - var result = await instructService.Execute(BuiltInAgentId.RulesInterpreter, new RoleDialogModel(AgentRole.User, userSay), "criteria_check", "#TEMPLATE#"); - - // Check if meet the criteria - if (result.Text == "1") - { - // Hit rule - _logger.LogInformation($"Hit rule {rule.TriggerName} {rule.EntityType} {rule.EventName}, {data}"); - - await convService.SendMessage(agent.Id, - new RoleDialogModel(AgentRole.User, $"The conversation was triggered by {rule.Criteria}"), - null, - msg => Task.CompletedTask); - } - }*/ } return newConversationIds; } #region Private methods - private async Task HandleCodeTrigger(string agentId, string scriptName, string codeProcessor, string triggerName, JsonDocument? args = null, IEnumerable? states = null) + private async Task TriggerCodeScript(string agentId, string scriptName, string codeProcessor, string triggerName, JsonDocument? args = null, IEnumerable? states = null) { var processor = _services.GetServices().FirstOrDefault(x => x.Provider.IsEqualTo(codeProcessor)); if (processor == null) @@ -159,7 +140,7 @@ private async Task HandleCodeTrigger(string agentId, string scriptName, st result = false; } - _logger.Log(logLevel, $"Code result: {response.Result}. {msg}"); + _logger.Log(logLevel, $"Code result ({response.Result}) from {msg}"); return result; } catch (Exception ex) From 3ea642dc57f1503f102f6aa039a3e0068ac815ad Mon Sep 17 00:00:00 2001 From: Jicheng Lu Date: Thu, 30 Oct 2025 00:09:57 -0500 Subject: [PATCH 04/16] refine --- .../Coding/Constants/BuiltInCodeProcessor.cs | 6 +++ .../Rules/IRuleTrigger.cs | 3 ++ .../Rules/Options/RuleTriggerOptions.cs | 2 +- .../BotSharp.Core.Rules/Engines/RuleEngine.cs | 39 +++++++++++-------- .../Agents/Services/AgentService.Coding.cs | 3 +- .../Services/InstructService.Execute.cs | 3 +- .../Services/PyCodeInterpreter.cs | 3 +- .../Using.cs | 2 + 8 files changed, 39 insertions(+), 22 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Coding/Constants/BuiltInCodeProcessor.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Coding/Constants/BuiltInCodeProcessor.cs b/src/Infrastructure/BotSharp.Abstraction/Coding/Constants/BuiltInCodeProcessor.cs new file mode 100644 index 000000000..d4802feaa --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Coding/Constants/BuiltInCodeProcessor.cs @@ -0,0 +1,6 @@ +namespace BotSharp.Abstraction.Coding.Constants; + +public static class BuiltInCodeProcessor +{ + public const string PyInterpreter = "botsharp-py-interpreter"; +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleTrigger.cs b/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleTrigger.cs index 508cd375f..4a58027f4 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleTrigger.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleTrigger.cs @@ -12,5 +12,8 @@ public interface IRuleTrigger string EntityId { get; set; } + /// + /// The default arguments as input to code trigger (display purpose) + /// JsonDocument OutputArgs => JsonDocument.Parse("{}"); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Rules/Options/RuleTriggerOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Rules/Options/RuleTriggerOptions.cs index 3c9e66709..2135437c7 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Rules/Options/RuleTriggerOptions.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Rules/Options/RuleTriggerOptions.cs @@ -7,7 +7,7 @@ public class RuleTriggerOptions /// /// Code processor provider /// - public string? CodeProcessor { get; set; } = "botsharp-py-interpreter"; + public string? CodeProcessor { get; set; } /// /// Code script name diff --git a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs index e0ed8a72a..901b7386c 100644 --- a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs +++ b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs @@ -1,4 +1,5 @@ using BotSharp.Abstraction.Coding; +using BotSharp.Abstraction.Coding.Constants; using BotSharp.Abstraction.Conversations; using BotSharp.Abstraction.Models; using BotSharp.Abstraction.Repositories.Filters; @@ -39,13 +40,10 @@ public async Task> Trigger(IRuleTrigger trigger, string text var isTriggered = true; - // Apply code trigger - if (options != null - && !string.IsNullOrWhiteSpace(options.CodeProcessor) - && !string.IsNullOrWhiteSpace(options.AgentId)) + // Code trigger + if (options != null) { - var scriptName = options.CodeScriptName ?? $"{trigger.Name}_cron.py"; - isTriggered = await TriggerCodeScript(options.AgentId, scriptName, options.CodeProcessor, trigger.Name, options.Arguments, options.States); + isTriggered = await TriggerCodeScript(trigger.Name, options); } if (!isTriggered) @@ -53,9 +51,8 @@ public async Task> Trigger(IRuleTrigger trigger, string text return newConversationIds; } - var filteredAgents = agents.Items.Where(x => x.Rules.Exists(r => r.TriggerName == trigger.Name && !x.Disabled)).ToList(); - // Trigger agents + var filteredAgents = agents.Items.Where(x => x.Rules.Exists(r => r.TriggerName == trigger.Name && !x.Disabled)).ToList(); foreach (var agent in filteredAgents) { var convService = _services.GetRequiredService(); @@ -93,32 +90,40 @@ await convService.SendMessage(agent.Id, } #region Private methods - private async Task TriggerCodeScript(string agentId, string scriptName, string codeProcessor, string triggerName, JsonDocument? args = null, IEnumerable? states = null) + private async Task TriggerCodeScript(string triggerName, RuleTriggerOptions options) { - var processor = _services.GetServices().FirstOrDefault(x => x.Provider.IsEqualTo(codeProcessor)); + var agentId = options.AgentId; + if (string.IsNullOrWhiteSpace(agentId)) + { + return false; + } + + var provider = options.CodeProcessor ?? BuiltInCodeProcessor.PyInterpreter; + var processor = _services.GetServices().FirstOrDefault(x => x.Provider.IsEqualTo(provider)); if (processor == null) { - _logger.LogWarning($"Unable to find code processor: {codeProcessor}."); + _logger.LogWarning($"Unable to find code processor: {provider}."); return false; } var agentService = _services.GetRequiredService(); + var scriptName = options.CodeScriptName ?? $"{triggerName}_rule.py"; var codeScript = await agentService.GetAgentCodeScript(agentId, scriptName, scriptType: AgentCodeScriptType.Src); + var msg = $"rule trigger ({triggerName}) code script ({scriptName}) in agent ({agentId}) => args: {options.Arguments?.RootElement.GetRawText()}."; + if (string.IsNullOrWhiteSpace(codeScript)) { - _logger.LogWarning($"Unable to find code script ({scriptName}) in agent ({agentId})."); + _logger.LogWarning($"Unable to find {msg}."); return false; } - var msg = $"rule trigger ({triggerName}) code script ({scriptName}) in agent ({agentId}) => args: {args?.RootElement.GetRawText()}."; - try { var response = await processor.RunAsync(codeScript, options: new() { ScriptName = scriptName, - Arguments = BuildArguments(args, states) + Arguments = BuildArguments(options.Arguments, options.States) }); if (response == null || !response.Success) @@ -140,7 +145,7 @@ private async Task TriggerCodeScript(string agentId, string scriptName, st result = false; } - _logger.Log(logLevel, $"Code result ({response.Result}) from {msg}"); + _logger.Log(logLevel, $"Code script execution result ({response.Result}) from {msg}"); return result; } catch (Exception ex) @@ -166,7 +171,7 @@ private IEnumerable BuildArguments(JsonDocument? args, IEnumerable new KeyValue(x.Key, x.Value)); diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Coding.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Coding.cs index aee641c8c..7f97a0464 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Coding.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Coding.cs @@ -1,5 +1,6 @@ using BotSharp.Abstraction.Agents.Options; using BotSharp.Abstraction.Coding; +using BotSharp.Abstraction.Coding.Constants; using BotSharp.Abstraction.Coding.Options; namespace BotSharp.Core.Agents.Services; @@ -76,7 +77,7 @@ public async Task GenerateCodeScript(string agentId, strin }; } - var processor = options?.Processor ?? "botsharp-py-interpreter"; + var processor = options?.Processor ?? BuiltInCodeProcessor.PyInterpreter; var codeProcessor = _services.GetServices().FirstOrDefault(x => x.Provider.IsEqualTo(processor)); if (codeProcessor == null) { diff --git a/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs b/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs index 8960ecf63..018798b24 100644 --- a/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs +++ b/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs @@ -1,4 +1,5 @@ using BotSharp.Abstraction.Coding; +using BotSharp.Abstraction.Coding.Constants; using BotSharp.Abstraction.Files.Options; using BotSharp.Abstraction.Files.Proccessors; using BotSharp.Abstraction.Instructs; @@ -179,7 +180,7 @@ await hook.OnResponseGenerated(new InstructResponseModel var state = _services.GetRequiredService(); var hooks = _services.GetHooks(agent.Id); - var codeProvider = codeOptions?.Processor ?? "botsharp-py-interpreter"; + var codeProvider = codeOptions?.Processor ?? BuiltInCodeProcessor.PyInterpreter; var codeProcessor = _services.GetServices() .FirstOrDefault(x => x.Provider.IsEqualTo(codeProvider)); diff --git a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyCodeInterpreter.cs b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyCodeInterpreter.cs index 8ee5d536a..dc2a78b70 100644 --- a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyCodeInterpreter.cs +++ b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyCodeInterpreter.cs @@ -1,4 +1,3 @@ -using BotSharp.Abstraction.Coding.Models; using Microsoft.Extensions.Logging; using Python.Runtime; using System.Threading; @@ -22,7 +21,7 @@ public PyCodeInterpreter( _executor = executor; } - public string Provider => "botsharp-py-interpreter"; + public string Provider => BuiltInCodeProcessor.PyInterpreter; public async Task RunAsync(string codeScript, CodeInterpretOptions? options = null) { diff --git a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Using.cs b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Using.cs index bd2c943b7..b8df1173d 100644 --- a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Using.cs +++ b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Using.cs @@ -21,6 +21,8 @@ global using BotSharp.Abstraction.Messaging.Models.RichContent.Template; global using BotSharp.Abstraction.Routing; global using BotSharp.Abstraction.Coding; +global using BotSharp.Abstraction.Coding.Constants; +global using BotSharp.Abstraction.Coding.Models; global using BotSharp.Abstraction.Coding.Options; global using BotSharp.Abstraction.Coding.Responses; global using BotSharp.Core.Coding; From ead4588f1f2e1c4c0734c9f0d378f8284df48478 Mon Sep 17 00:00:00 2001 From: Jicheng Lu Date: Thu, 30 Oct 2025 00:19:29 -0500 Subject: [PATCH 05/16] rename --- .../Coding/{Constants => Enums}/BuiltInCodeProcessor.cs | 2 +- src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs | 2 +- .../BotSharp.Core/Agents/Services/AgentService.Coding.cs | 2 +- .../BotSharp.Core/Instructs/Services/InstructService.Execute.cs | 2 +- src/Plugins/BotSharp.Plugin.PythonInterpreter/Using.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename src/Infrastructure/BotSharp.Abstraction/Coding/{Constants => Enums}/BuiltInCodeProcessor.cs (69%) diff --git a/src/Infrastructure/BotSharp.Abstraction/Coding/Constants/BuiltInCodeProcessor.cs b/src/Infrastructure/BotSharp.Abstraction/Coding/Enums/BuiltInCodeProcessor.cs similarity index 69% rename from src/Infrastructure/BotSharp.Abstraction/Coding/Constants/BuiltInCodeProcessor.cs rename to src/Infrastructure/BotSharp.Abstraction/Coding/Enums/BuiltInCodeProcessor.cs index d4802feaa..cdbf76a99 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Coding/Constants/BuiltInCodeProcessor.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Coding/Enums/BuiltInCodeProcessor.cs @@ -1,4 +1,4 @@ -namespace BotSharp.Abstraction.Coding.Constants; +namespace BotSharp.Abstraction.Coding.Enums; public static class BuiltInCodeProcessor { diff --git a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs index 901b7386c..da60a3854 100644 --- a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs +++ b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs @@ -1,5 +1,5 @@ using BotSharp.Abstraction.Coding; -using BotSharp.Abstraction.Coding.Constants; +using BotSharp.Abstraction.Coding.Enums; using BotSharp.Abstraction.Conversations; using BotSharp.Abstraction.Models; using BotSharp.Abstraction.Repositories.Filters; diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Coding.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Coding.cs index 7f97a0464..9f6777d0f 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Coding.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Coding.cs @@ -1,6 +1,6 @@ using BotSharp.Abstraction.Agents.Options; using BotSharp.Abstraction.Coding; -using BotSharp.Abstraction.Coding.Constants; +using BotSharp.Abstraction.Coding.Enums; using BotSharp.Abstraction.Coding.Options; namespace BotSharp.Core.Agents.Services; diff --git a/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs b/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs index 018798b24..56a006dab 100644 --- a/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs +++ b/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs @@ -1,5 +1,5 @@ using BotSharp.Abstraction.Coding; -using BotSharp.Abstraction.Coding.Constants; +using BotSharp.Abstraction.Coding.Enums; using BotSharp.Abstraction.Files.Options; using BotSharp.Abstraction.Files.Proccessors; using BotSharp.Abstraction.Instructs; diff --git a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Using.cs b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Using.cs index b8df1173d..5b9a32b99 100644 --- a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Using.cs +++ b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Using.cs @@ -21,7 +21,7 @@ global using BotSharp.Abstraction.Messaging.Models.RichContent.Template; global using BotSharp.Abstraction.Routing; global using BotSharp.Abstraction.Coding; -global using BotSharp.Abstraction.Coding.Constants; +global using BotSharp.Abstraction.Coding.Enums; global using BotSharp.Abstraction.Coding.Models; global using BotSharp.Abstraction.Coding.Options; global using BotSharp.Abstraction.Coding.Responses; From 0af83be052a0f2ebce8980bcd0a3c409da8cf432 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Thu, 30 Oct 2025 09:58:29 -0500 Subject: [PATCH 06/16] minor change --- .../Coding/Options/CodeGenerationOptions.cs | 7 ++- .../BotSharp.Abstraction/Rules/IRuleEngine.cs | 8 +++ .../Rules/Options/RuleTriggerOptions.cs | 8 +-- .../BotSharp.Core.Rules/Engines/RuleEngine.cs | 51 +++++++------------ .../Agent/AgentController.Coding.cs | 3 +- .../Services/PyCodeInterpreter.cs | 6 +-- 6 files changed, 39 insertions(+), 44 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Coding/Options/CodeGenerationOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Coding/Options/CodeGenerationOptions.cs index be6437041..de886f5ef 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Coding/Options/CodeGenerationOptions.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Coding/Options/CodeGenerationOptions.cs @@ -17,11 +17,10 @@ public class CodeGenerationOptions : LlmConfigBase public string? TemplateName { get; set; } /// - /// The programming language + /// Programming language /// - [JsonPropertyName("language")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string? Language { get; set; } = "python"; + [JsonPropertyName("programming_language")] + public string? ProgrammingLanguage { get; set; } /// /// Data that can be used to fill in the prompt diff --git a/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleEngine.cs b/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleEngine.cs index 62483a303..51c80d349 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleEngine.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleEngine.cs @@ -2,6 +2,14 @@ namespace BotSharp.Abstraction.Rules; public interface IRuleEngine { + /// + /// Trigger the rule that is subscribed by agents. + /// + /// + /// + /// + /// + /// Task> Trigger(IRuleTrigger trigger, string text, RuleTriggerOptions? options = null) => throw new NotImplementedException(); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Rules/Options/RuleTriggerOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Rules/Options/RuleTriggerOptions.cs index 2135437c7..ef0e3c393 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Rules/Options/RuleTriggerOptions.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Rules/Options/RuleTriggerOptions.cs @@ -15,14 +15,14 @@ public class RuleTriggerOptions public string? CodeScriptName { get; set; } /// - /// Json arguments + /// Argument name as an input key to the code script /// - public JsonDocument? Arguments { get; set; } + public string? ArgsName { get; set; } /// - /// Agent where the code script is stored + /// Json arguments as an input value to the code script /// - public string? AgentId { get; set; } + public JsonDocument? Arguments { get; set; } /// /// States diff --git a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs index da60a3854..64dd8c2b6 100644 --- a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs +++ b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs @@ -38,23 +38,23 @@ public async Task> Trigger(IRuleTrigger trigger, string text } }); - var isTriggered = true; - - // Code trigger - if (options != null) - { - isTriggered = await TriggerCodeScript(trigger.Name, options); - } - - if (!isTriggered) - { - return newConversationIds; - } - // Trigger agents var filteredAgents = agents.Items.Where(x => x.Rules.Exists(r => r.TriggerName == trigger.Name && !x.Disabled)).ToList(); foreach (var agent in filteredAgents) { + var isTriggered = true; + + // Code trigger + if (options != null) + { + isTriggered = await TriggerCodeScript(agent.Id, trigger.Name, options); + } + + if (!isTriggered) + { + continue; + } + var convService = _services.GetRequiredService(); var conv = await convService.NewConversation(new Conversation { @@ -90,9 +90,8 @@ await convService.SendMessage(agent.Id, } #region Private methods - private async Task TriggerCodeScript(string triggerName, RuleTriggerOptions options) + private async Task TriggerCodeScript(string agentId, string triggerName, RuleTriggerOptions options) { - var agentId = options.AgentId; if (string.IsNullOrWhiteSpace(agentId)) { return false; @@ -123,7 +122,7 @@ private async Task TriggerCodeScript(string triggerName, RuleTriggerOption var response = await processor.RunAsync(codeScript, options: new() { ScriptName = scriptName, - Arguments = BuildArguments(options.Arguments, options.States) + Arguments = BuildArguments(options.ArgsName, options.Arguments) }); if (response == null || !response.Success) @@ -155,26 +154,14 @@ private async Task TriggerCodeScript(string triggerName, RuleTriggerOption } } - private IEnumerable BuildArguments(JsonDocument? args, IEnumerable? states) + private IEnumerable BuildArguments(string? argName, JsonDocument? args) { - var dict = new Dictionary(); - if (!states.IsNullOrEmpty()) - { - foreach (var state in states) - { - if (state.Value != null) - { - dict[state.Key] = state.Value.ConvertToString(); - } - } - } - + var keyValues = new List(); if (args != null) { - dict["trigger_args"] = args.RootElement.GetRawText(); + keyValues.Add(new KeyValue(argName ?? "rule_args", args.RootElement.GetRawText())); } - - return dict.Select(x => new KeyValue(x.Key, x.Value)); + return keyValues; } #endregion } diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/Agent/AgentController.Coding.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Agent/AgentController.Coding.cs index 55c3381bb..40800d35c 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/Agent/AgentController.Coding.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Agent/AgentController.Coding.cs @@ -61,7 +61,8 @@ public async Task GenerateAgentCodeScript([FromRoute] stri 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); + state.SetState("code_processor", request.Options?.Processor, source: StateSource.External); + state.SetState("programming_language", request.Options?.ProgrammingLanguage, source: StateSource.External); var result = await _agentService.GenerateCodeScript(agentId, request.Text, request?.Options); return result; diff --git a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyCodeInterpreter.cs b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyCodeInterpreter.cs index dc2a78b70..2e70e3d8c 100644 --- a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyCodeInterpreter.cs +++ b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyCodeInterpreter.cs @@ -41,10 +41,10 @@ public async Task GenerateCodeScriptAsync(string text, Cod var agentId = options?.AgentId; var templateName = options?.TemplateName; - - var agentService = _services.GetRequiredService(); + if (!string.IsNullOrEmpty(agentId)) { + var agentService = _services.GetRequiredService(); agent = await agentService.GetAgent(agentId); } @@ -83,7 +83,7 @@ public async Task GenerateCodeScriptAsync(string text, Cod { Success = true, Content = response.Content, - Language = options?.Language ?? "python" + Language = options?.ProgrammingLanguage ?? "python" }; } From cb90c5775fe654068d2bc2b7c51999d34b80b0ae Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Thu, 30 Oct 2025 10:16:35 -0500 Subject: [PATCH 07/16] add logger inject --- .../{Conversation => Application}/GoogleController.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) rename src/Infrastructure/BotSharp.OpenAPI/Controllers/{Conversation => Application}/GoogleController.cs (93%) diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/GoogleController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Application/GoogleController.cs similarity index 93% rename from src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/GoogleController.cs rename to src/Infrastructure/BotSharp.OpenAPI/Controllers/Application/GoogleController.cs index 13faeb2d1..5497d6c2e 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/GoogleController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Application/GoogleController.cs @@ -11,15 +11,18 @@ public class GoogleController : ControllerBase private readonly IServiceProvider _services; private readonly BotSharpOptions _options; private readonly IHttpClientFactory _httpClientFactory; - private readonly ILogger _logger; + private readonly ILogger _logger; - public GoogleController(IServiceProvider services, + public GoogleController( + IServiceProvider services, + ILogger logger, IHttpClientFactory httpClientFactory, BotSharpOptions options) { _services = services; - _options = options; + _logger = logger; _httpClientFactory = httpClientFactory; + _options = options; } [HttpGet("/address/options")] From 8fce79d6ca5ef55d7aa8c6af73f4b970f44c30c7 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Thu, 30 Oct 2025 11:46:29 -0500 Subject: [PATCH 08/16] add coding settings --- .../Agents/Settings/AgentSettings.cs | 8 ++++-- .../Coding/Settings/CodingSettings.cs | 14 ++++++++++ .../BotSharp.Abstraction/Using.cs | 3 ++- .../BotSharp.Core/Agents/AgentPlugin.cs | 3 --- .../BotSharp.Core/Coding/CodingPlugin.cs | 15 +++++++++++ src/Infrastructure/BotSharp.Core/Using.cs | 1 + .../Services/PyCodeInterpreter.cs | 26 ++++++++++++++++--- .../Using.cs | 1 + src/WebStarter/appsettings.json | 4 +++ 9 files changed, 66 insertions(+), 9 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Coding/Settings/CodingSettings.cs create mode 100644 src/Infrastructure/BotSharp.Core/Coding/CodingPlugin.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Settings/AgentSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Settings/AgentSettings.cs index 887c437ec..69e4752b2 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Settings/AgentSettings.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Settings/AgentSettings.cs @@ -10,6 +10,10 @@ public class AgentSettings /// /// This is the default LLM config for agent /// - public AgentLlmConfig LlmConfig { get; set; } - = new AgentLlmConfig(); + public AgentLlmConfig LlmConfig { get; set; } = new AgentLlmConfig(); + + /// + /// General coding settings + /// + public CodingSettings Coding { get; set; } = new CodingSettings(); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Coding/Settings/CodingSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Coding/Settings/CodingSettings.cs new file mode 100644 index 000000000..5f194bf32 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Coding/Settings/CodingSettings.cs @@ -0,0 +1,14 @@ +namespace BotSharp.Abstraction.Coding.Settings; + +public class CodingSettings +{ + /// + /// Llm provider to generate code script + /// + public string? Provider { get; set; } + + /// + /// Llm model to generate code script + /// + public string? Model { get; set; } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Using.cs b/src/Infrastructure/BotSharp.Abstraction/Using.cs index 41f8faaea..ca0cbe7f7 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Using.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Using.cs @@ -22,4 +22,5 @@ global using BotSharp.Abstraction.Crontab.Models; global using BotSharp.Abstraction.MCP.Models; global using BotSharp.Abstraction.Settings; -global using BotSharp.Abstraction.Rules.Options; \ No newline at end of file +global using BotSharp.Abstraction.Rules.Options; +global using BotSharp.Abstraction.Coding.Settings; \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Core/Agents/AgentPlugin.cs b/src/Infrastructure/BotSharp.Core/Agents/AgentPlugin.cs index 449db38da..575385455 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/AgentPlugin.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/AgentPlugin.cs @@ -5,7 +5,6 @@ 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; @@ -49,8 +48,6 @@ public void RegisterDI(IServiceCollection services, IConfiguration config) render.RegisterType(typeof(AgentSettings)); return settingService.Bind("Agent"); }); - - services.AddSingleton(); } public bool AttachMenu(List menu) diff --git a/src/Infrastructure/BotSharp.Core/Coding/CodingPlugin.cs b/src/Infrastructure/BotSharp.Core/Coding/CodingPlugin.cs new file mode 100644 index 000000000..4f788f996 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Coding/CodingPlugin.cs @@ -0,0 +1,15 @@ +using Microsoft.Extensions.Configuration; + +namespace BotSharp.Core.Coding; + +public class CodingPlugin : IBotSharpPlugin +{ + public string Id => "31bc334b-9462-4191-beac-cb4a139b78c1"; + public string Name => "Coding"; + public string Description => "Handling execution and generation of code scripts"; + + public void RegisterDI(IServiceCollection services, IConfiguration config) + { + services.AddSingleton(); + } +} diff --git a/src/Infrastructure/BotSharp.Core/Using.cs b/src/Infrastructure/BotSharp.Core/Using.cs index de3fa1e78..39ac00cc8 100644 --- a/src/Infrastructure/BotSharp.Core/Using.cs +++ b/src/Infrastructure/BotSharp.Core/Using.cs @@ -6,6 +6,7 @@ global using BotSharp.Abstraction.Conversations.Models; global using BotSharp.Abstraction.Conversations.Settings; global using BotSharp.Abstraction.Coding.Models; +global using BotSharp.Abstraction.Coding.Settings; global using BotSharp.Abstraction.Crontab.Models; global using BotSharp.Abstraction.Files; global using BotSharp.Abstraction.Files.Enums; diff --git a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyCodeInterpreter.cs b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyCodeInterpreter.cs index 2e70e3d8c..9e03b2aae 100644 --- a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyCodeInterpreter.cs +++ b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyCodeInterpreter.cs @@ -10,15 +10,18 @@ public class PyCodeInterpreter : ICodeProcessor private readonly IServiceProvider _services; private readonly ILogger _logger; private readonly CodeScriptExecutor _executor; + private readonly AgentSettings _agentSettings; public PyCodeInterpreter( IServiceProvider services, ILogger logger, - CodeScriptExecutor executor) + CodeScriptExecutor executor, + AgentSettings agentSettings) { _services = services; _logger = logger; _executor = executor; + _agentSettings = agentSettings; } public string Provider => BuiltInCodeProcessor.PyInterpreter; @@ -54,6 +57,7 @@ public async Task GenerateCodeScriptAsync(string text, Cod instruction = agent.Templates?.FirstOrDefault(x => x.Name.IsEqualTo(templateName))?.Content; } + var (provider, model) = GetLlmProviderModel(); var innerAgent = new Agent { Id = agent?.Id ?? BuiltInAgentId.AIProgrammer, @@ -61,8 +65,8 @@ public async Task GenerateCodeScriptAsync(string text, Cod Instruction = instruction, LlmConfig = new AgentLlmConfig { - Provider = options?.Provider ?? "openai", - Model = options?.Model ?? "gpt-5-mini", + Provider = options?.Provider ?? provider, + Model = options?.Model ?? model, MaxOutputTokens = options?.MaxOutputTokens, ReasoningEffortLevel = options?.ReasoningEffortLevel }, @@ -178,5 +182,21 @@ private CodeInterpretResponse CoreRun(string codeScript, CodeInterpretOptions? o } } } + + private (string, string) GetLlmProviderModel() + { + var provider = _agentSettings.Coding?.Provider; + var model = _agentSettings.Coding?.Model; + + if (!string.IsNullOrEmpty(provider) && !string.IsNullOrEmpty(model)) + { + return (provider, model); + } + + provider = "openai"; + model = "gpt-5-mini"; + + return (provider, model); + } #endregion } diff --git a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Using.cs b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Using.cs index 5b9a32b99..9296406a3 100644 --- a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Using.cs +++ b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Using.cs @@ -23,6 +23,7 @@ global using BotSharp.Abstraction.Coding; global using BotSharp.Abstraction.Coding.Enums; global using BotSharp.Abstraction.Coding.Models; +global using BotSharp.Abstraction.Coding.Settings; global using BotSharp.Abstraction.Coding.Options; global using BotSharp.Abstraction.Coding.Responses; global using BotSharp.Core.Coding; diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json index 071b279a5..e97678903 100644 --- a/src/WebStarter/appsettings.json +++ b/src/WebStarter/appsettings.json @@ -468,6 +468,10 @@ "LlmConfig": { "Provider": "openai", "Model": "gpt-4.1-nano" + }, + "Coding": { + "Provider": "openai", + "Model": "gpt-5-mini" } }, From 1733e4f4a8edc38eb8bf7321a912a7c19084a4aa Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Wed, 5 Nov 2025 14:30:43 -0600 Subject: [PATCH 09/16] add rule code generate instruction --- .../BotSharp.Core/BotSharp.Core.csproj | 145 ++++++++++-------- .../rule-code-generate_instruction.liquid | 13 ++ 2 files changed, 93 insertions(+), 65 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Core/data/agents/c2a2faf6-b8b5-47fe-807b-f4714cf25dd4/templates/rule-code-generate_instruction.liquid diff --git a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj index 8d045f856..b9b2fbffc 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj +++ b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj @@ -62,50 +62,59 @@ - - - - - - - - - - + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - + + @@ -118,129 +127,135 @@ PreserveNewest - + + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest PreserveNewest - + + PreserveNewest - + PreserveNewest - + + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + + PreserveNewest - + PreserveNewest - + PreserveNewest - + + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - - - + PreserveNewest - + + PreserveNewest - + PreserveNewest - + + PreserveNewest diff --git a/src/Infrastructure/BotSharp.Core/data/agents/c2a2faf6-b8b5-47fe-807b-f4714cf25dd4/templates/rule-code-generate_instruction.liquid b/src/Infrastructure/BotSharp.Core/data/agents/c2a2faf6-b8b5-47fe-807b-f4714cf25dd4/templates/rule-code-generate_instruction.liquid new file mode 100644 index 000000000..50958f4eb --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/data/agents/c2a2faf6-b8b5-47fe-807b-f4714cf25dd4/templates/rule-code-generate_instruction.liquid @@ -0,0 +1,13 @@ +Based on user's request, help generate a refined python code of function definition, only using base python packages, that can return a boolean value of true or false to determine if based on known states, the function can determine if conditions are met. + +User's request is {{user_request}} + +Couple of notes to address: +1. You need to generate a function named check_trigger_criterion(args), where input is a json object. The example of this json object is {{states_example}}. +2. The input to this function comes from the arguments. You must use "ArgumentParser" to take "states" as an argument, and then use "parse_known_args" to get the raw args. +3. After getting the raw args, you need to use 'clean_args = bytes(raw_args, "utf-8").decode("unicode_escape")' to clean the raw argument, and then use 'args = json.loads(clean_args)' to get the json args. +4. Based on the user's request and input args, the output of this function must be True or False. +5. You must only call check_trigger_criterion with the parsed json args in "if __name__ == '__main__'" block, and print only the function output. If any error occurs, print "Error". +6. Refine the code so it will return for certain errors if detected, for example, when input is not a valid json, return "Error: Input is not a valid JSON string."; If "states" not in data: return "Error: Missing 'states' key in JSON."; or when certain states are missing from input, so I can better track of this. + +Output the code directly in order to directly save it to a py file. Avoid using comments for format issues, use code snip block for the output. \ No newline at end of file From 7729f55d93613033d8d1f671f6741941f390835c Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Wed, 5 Nov 2025 16:56:27 -0600 Subject: [PATCH 10/16] refine rule trigger code prompt --- .../BotSharp.Core/BotSharp.Core.csproj | 4 ++-- ...=> rule-trigger-code-generate_instruction.liquid} | 12 +++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) rename src/Infrastructure/BotSharp.Core/data/agents/c2a2faf6-b8b5-47fe-807b-f4714cf25dd4/templates/{rule-code-generate_instruction.liquid => rule-trigger-code-generate_instruction.liquid} (61%) diff --git a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj index b9b2fbffc..d0d517f4a 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj +++ b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj @@ -102,7 +102,7 @@ - + @@ -225,7 +225,7 @@ PreserveNewest - + PreserveNewest diff --git a/src/Infrastructure/BotSharp.Core/data/agents/c2a2faf6-b8b5-47fe-807b-f4714cf25dd4/templates/rule-code-generate_instruction.liquid b/src/Infrastructure/BotSharp.Core/data/agents/c2a2faf6-b8b5-47fe-807b-f4714cf25dd4/templates/rule-trigger-code-generate_instruction.liquid similarity index 61% rename from src/Infrastructure/BotSharp.Core/data/agents/c2a2faf6-b8b5-47fe-807b-f4714cf25dd4/templates/rule-code-generate_instruction.liquid rename to src/Infrastructure/BotSharp.Core/data/agents/c2a2faf6-b8b5-47fe-807b-f4714cf25dd4/templates/rule-trigger-code-generate_instruction.liquid index 50958f4eb..f7a18b172 100644 --- a/src/Infrastructure/BotSharp.Core/data/agents/c2a2faf6-b8b5-47fe-807b-f4714cf25dd4/templates/rule-code-generate_instruction.liquid +++ b/src/Infrastructure/BotSharp.Core/data/agents/c2a2faf6-b8b5-47fe-807b-f4714cf25dd4/templates/rule-trigger-code-generate_instruction.liquid @@ -3,11 +3,13 @@ Based on user's request, help generate a refined python code of function definit User's request is {{user_request}} Couple of notes to address: -1. You need to generate a function named check_trigger_criterion(args), where input is a json object. The example of this json object is {{states_example}}. -2. The input to this function comes from the arguments. You must use "ArgumentParser" to take "states" as an argument, and then use "parse_known_args" to get the raw args. +1. You need to generate a function named check_trigger_criterion(args), where input is a json object. The example of this json object is {{args_example}}. +2. The input to this function comes from the arguments. You must use "ArgumentParser" to take an argument named "states", and then use "parse_known_args" to get the raw args. 3. After getting the raw args, you need to use 'clean_args = bytes(raw_args, "utf-8").decode("unicode_escape")' to clean the raw argument, and then use 'args = json.loads(clean_args)' to get the json args. -4. Based on the user's request and input args, the output of this function must be True or False. +4. Based on the user's request and input args, generate the function logic and ONLY return a boolean value. 5. You must only call check_trigger_criterion with the parsed json args in "if __name__ == '__main__'" block, and print only the function output. If any error occurs, print "Error". -6. Refine the code so it will return for certain errors if detected, for example, when input is not a valid json, return "Error: Input is not a valid JSON string."; If "states" not in data: return "Error: Missing 'states' key in JSON."; or when certain states are missing from input, so I can better track of this. +6. Refine the code so it will return for certain errors if detected, for example, when input is not a valid json, return "Error: Input is not a valid JSON string."; or when certain attributes are missing from json args, so I can better track of this. +7. You can use try-except blocks to catch any errors, and return "Error" if any error occurs. Do not use sys.exit(0) to exit the program. +8. Use as fewer comments and try-except blocks as possible. -Output the code directly in order to directly save it to a py file. Avoid using comments for format issues, use code snip block for the output. \ No newline at end of file +Output the executable code directly in order to directly save it to a py file. \ No newline at end of file From 511b806199d79a16104dbc6eb585e71d007a69b2 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Wed, 5 Nov 2025 17:23:49 -0600 Subject: [PATCH 11/16] rename --- .../Instructs/Options/InstructOptions.cs | 2 +- .../Instruct/FileInstructService.Image.cs | 8 ++++---- .../Services/Instruct/FileInstructService.Pdf.cs | 2 +- .../Services/Instruct/FileInstructService.cs | 3 --- .../Instruct/InstructModeController.File.cs | 4 ++-- .../Instruct/InstructModeController.Image.cs | 14 +++++++------- .../Instructs/Request/InstructBaseRequest.cs | 16 ++++++++-------- .../Hooks/StreamingLogHook.cs | 1 - 8 files changed, 23 insertions(+), 27 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Instructs/Options/InstructOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Instructs/Options/InstructOptions.cs index cc5a76cd5..b2ff66fba 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Instructs/Options/InstructOptions.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Instructs/Options/InstructOptions.cs @@ -35,5 +35,5 @@ public class InstructOptions /// /// Image convert provider /// - public string? ImageConvertProvider { get; set; } + public string? ImageConverter { get; set; } } diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Image.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Image.cs index dbd9de90f..cd9c42afc 100644 --- a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Image.cs +++ b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Image.cs @@ -85,7 +85,7 @@ public async Task VaryImage(InstructFileModel image, InstructOp var binary = await DownloadFile(image); // Convert image - var converter = GetImageConverter(options?.ImageConvertProvider); + var converter = GetImageConverter(options?.ImageConverter); if (converter != null) { binary = await converter.ConvertImage(binary); @@ -130,7 +130,7 @@ public async Task EditImage(string text, InstructFileModel imag var binary = await DownloadFile(image); // Convert image - var converter = GetImageConverter(options?.ImageConvertProvider); + var converter = GetImageConverter(options?.ImageConverter); if (converter != null) { binary = await converter.ConvertImage(binary); @@ -180,7 +180,7 @@ public async Task EditImage(string text, InstructFileModel imag var maskBinary = await DownloadFile(mask); // Convert image - var converter = GetImageConverter(options?.ImageConvertProvider); + var converter = GetImageConverter(options?.ImageConverter); if (converter != null) { imageBinary = await converter.ConvertImage(imageBinary); @@ -236,7 +236,7 @@ public async Task ComposeImages(string text, InstructFileModel[ var binary = await DownloadFile(image); // Convert image - var converter = GetImageConverter(options?.ImageConvertProvider); + var converter = GetImageConverter(options?.ImageConverter); if (converter != null) { binary = await converter.ConvertImage(binary); diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Pdf.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Pdf.cs index 2e4792222..9defa8cc8 100644 --- a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Pdf.cs +++ b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Pdf.cs @@ -26,7 +26,7 @@ public async Task ReadPdf(string text, List files, In var pdfFiles = await DownloadAndSaveFiles(sessionDir, files); var targetFiles = pdfFiles; - var converter = GetImageConverter(options?.ImageConvertProvider); + var converter = GetImageConverter(options?.ImageConverter); if (converter == null && provider == "openai") { var fileCoreSettings = _services.GetRequiredService(); diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.cs index 170a0fc0c..fb34f24d0 100644 --- a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.cs +++ b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.cs @@ -1,7 +1,4 @@ - using BotSharp.Abstraction.Files.Converters; -using Microsoft.Extensions.Options; -using static System.Net.Mime.MediaTypeNames; namespace BotSharp.Core.Files.Services; diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/Instruct/InstructModeController.File.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Instruct/InstructModeController.File.cs index 031821433..9c348d8b6 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/Instruct/InstructModeController.File.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Instruct/InstructModeController.File.cs @@ -24,7 +24,7 @@ public async Task PdfCompletion([FromBody] PdfReadFileRe Model = request.Model, AgentId = request.AgentId, TemplateName = request.TemplateName, - ImageConvertProvider = request.ImageConvertProvider + ImageConverter = request.ImageConverter }); viewModel.Success = true; @@ -62,7 +62,7 @@ public async Task PdfCompletion([FromForm] IEnumerable ComposeImages([FromBody] ImageCompos Model = request.Model, AgentId = request.AgentId, TemplateName = request.TemplateName, - ImageConvertProvider = request.ImageConvertProvider + ImageConverter = request.ImageConverter }); imageViewModel.Success = true; @@ -103,7 +103,7 @@ public async Task ImageVariation([FromBody] ImageVaria Provider = request.Provider, Model = request.Model, AgentId = request.AgentId, - ImageConvertProvider = request.ImageConvertProvider + ImageConverter = request.ImageConverter }); imageViewModel.Success = true; @@ -142,7 +142,7 @@ public async Task ImageVariation(IFormFile file, [From Provider = request?.Provider, Model = request?.Model, AgentId = request?.AgentId, - ImageConvertProvider = request?.ImageConvertProvider + ImageConverter = request?.ImageConverter }); imageViewModel.Success = true; @@ -182,7 +182,7 @@ public async Task ImageEdit([FromBody] ImageEditFileRe Model = request.Model, AgentId = request.AgentId, TemplateName = request.TemplateName, - ImageConvertProvider = request.ImageConvertProvider + ImageConverter = request.ImageConverter }); imageViewModel.Success = true; @@ -222,7 +222,7 @@ public async Task ImageEdit(IFormFile file, [FromForm] Model = request?.Model, AgentId = request?.AgentId, TemplateName = request?.TemplateName, - ImageConvertProvider = request?.ImageConvertProvider + ImageConverter = request?.ImageConverter }); imageViewModel.Success = true; @@ -264,7 +264,7 @@ public async Task ImageMaskEdit([FromBody] ImageMaskEd Model = request.Model, AgentId = request.AgentId, TemplateName = request.TemplateName, - ImageConvertProvider = request.ImageConvertProvider + ImageConverter = request.ImageConverter }); imageViewModel.Success = true; @@ -312,7 +312,7 @@ public async Task ImageMaskEdit(IFormFile image, IForm Model = request?.Model, AgentId = request?.AgentId, TemplateName = request?.TemplateName, - ImageConvertProvider = request?.ImageConvertProvider + ImageConverter = request?.ImageConverter }); imageViewModel.Success = true; diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Instructs/Request/InstructBaseRequest.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Instructs/Request/InstructBaseRequest.cs index 96c831cb0..d1f44c34d 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Instructs/Request/InstructBaseRequest.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Instructs/Request/InstructBaseRequest.cs @@ -44,8 +44,8 @@ public class ImageGenerationRequest : InstructBaseRequest public class ImageVariationRequest : InstructBaseRequest { - [JsonPropertyName("image_convert_provider")] - public string? ImageConvertProvider { get; set; } + [JsonPropertyName("image_converter")] + public string? ImageConverter { get; set; } } public class ImageVariationFileRequest : ImageVariationRequest @@ -60,8 +60,8 @@ public class ImageEditRequest : InstructBaseRequest [JsonPropertyName("text")] public string Text { get; set; } = string.Empty; - [JsonPropertyName("image_convert_provider")] - public string? ImageConvertProvider { get; set; } + [JsonPropertyName("image_converter")] + public string? ImageConverter { get; set; } } public class ImageEditFileRequest : ImageEditRequest @@ -81,8 +81,8 @@ public class ImageMaskEditRequest : InstructBaseRequest [JsonPropertyName("text")] public string Text { get; set; } = string.Empty; - [JsonPropertyName("image_convert_provider")] - public string? ImageConvertProvider { get; set; } + [JsonPropertyName("image_converter")] + public string? ImageConverter { get; set; } } public class ImageMaskEditFileRequest : ImageMaskEditRequest @@ -100,8 +100,8 @@ public class PdfReadRequest : InstructBaseRequest [JsonPropertyName("text")] public string Text { get; set; } = string.Empty; - [JsonPropertyName("image_convert_provider")] - public string? ImageConvertProvider { get; set; } + [JsonPropertyName("image_converter")] + public string? ImageConverter { get; set; } } public class PdfReadFileRequest : PdfReadRequest diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs index 7156f29f9..321deed56 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs @@ -1,5 +1,4 @@ using BotSharp.Abstraction.Conversations.Enums; -using BotSharp.Abstraction.Routing.Enums; using BotSharp.Abstraction.Routing.Models; using Microsoft.AspNetCore.SignalR; using System.Runtime.CompilerServices; From f5d7adeedf815c078082b55c9ee3ed2beaa3694b Mon Sep 17 00:00:00 2001 From: Jicheng Lu Date: Wed, 5 Nov 2025 22:34:44 -0600 Subject: [PATCH 12/16] refine --- .../BotSharp.Abstraction/Rules/IRuleEngine.cs | 3 ++- .../Rules/Options/RuleTriggerOptions.cs | 9 ++------- .../BotSharp.Core.Rules/Engines/RuleEngine.cs | 18 +++++++++--------- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleEngine.cs b/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleEngine.cs index 51c80d349..adcf87aaf 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleEngine.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleEngine.cs @@ -7,9 +7,10 @@ public interface IRuleEngine /// /// /// + /// /// /// /// - Task> Trigger(IRuleTrigger trigger, string text, RuleTriggerOptions? options = null) + Task> Trigger(IRuleTrigger trigger, string text, IEnumerable? states = null, RuleTriggerOptions? options = null) => throw new NotImplementedException(); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Rules/Options/RuleTriggerOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Rules/Options/RuleTriggerOptions.cs index ef0e3c393..068052b0b 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Rules/Options/RuleTriggerOptions.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Rules/Options/RuleTriggerOptions.cs @@ -17,15 +17,10 @@ public class RuleTriggerOptions /// /// Argument name as an input key to the code script /// - public string? ArgsName { get; set; } + public string? ArgumentName { get; set; } /// /// Json arguments as an input value to the code script /// - public JsonDocument? Arguments { get; set; } - - /// - /// States - /// - public List? States { get; set; } = null; + public JsonDocument? ArgumentContent { get; set; } } diff --git a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs index 64dd8c2b6..61073b6d5 100644 --- a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs +++ b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs @@ -24,7 +24,7 @@ public RuleEngine( _logger = logger; } - public async Task> Trigger(IRuleTrigger trigger, string text, RuleTriggerOptions? options = null) + public async Task> Trigger(IRuleTrigger trigger, string text, IEnumerable? states = null, RuleTriggerOptions? options = null) { var newConversationIds = new List(); @@ -39,7 +39,7 @@ public async Task> Trigger(IRuleTrigger trigger, string text }); // Trigger agents - var filteredAgents = agents.Items.Where(x => x.Rules.Exists(r => r.TriggerName == trigger.Name && !x.Disabled)).ToList(); + var filteredAgents = agents.Items.Where(x => x.Rules.Exists(r => r.TriggerName.IsEqualTo(trigger.Name) && !x.Disabled)).ToList(); foreach (var agent in filteredAgents) { var isTriggered = true; @@ -70,9 +70,9 @@ public async Task> Trigger(IRuleTrigger trigger, string text new("channel", trigger.Channel) }; - if (options?.States != null) + if (states != null) { - allStates.AddRange(options.States); + allStates.AddRange(states); } convService.SetConversationId(conv.Id, allStates); @@ -109,7 +109,7 @@ private async Task TriggerCodeScript(string agentId, string triggerName, R var scriptName = options.CodeScriptName ?? $"{triggerName}_rule.py"; var codeScript = await agentService.GetAgentCodeScript(agentId, scriptName, scriptType: AgentCodeScriptType.Src); - var msg = $"rule trigger ({triggerName}) code script ({scriptName}) in agent ({agentId}) => args: {options.Arguments?.RootElement.GetRawText()}."; + var msg = $"rule trigger ({triggerName}) code script ({scriptName}) in agent ({agentId}) => args: {options.ArgumentContent?.RootElement.GetRawText()}."; if (string.IsNullOrWhiteSpace(codeScript)) { @@ -122,7 +122,7 @@ private async Task TriggerCodeScript(string agentId, string triggerName, R var response = await processor.RunAsync(codeScript, options: new() { ScriptName = scriptName, - Arguments = BuildArguments(options.ArgsName, options.Arguments) + Arguments = BuildArguments(options.ArgumentName, options.ArgumentContent) }); if (response == null || !response.Success) @@ -133,7 +133,7 @@ private async Task TriggerCodeScript(string agentId, string triggerName, R bool result; LogLevel logLevel; - if (response.Result.IsEqualTo("true") || response.Result.IsEqualTo("1")) + if (response.Result.IsEqualTo("true")) { logLevel = LogLevel.Information; result = true; @@ -154,12 +154,12 @@ private async Task TriggerCodeScript(string agentId, string triggerName, R } } - private IEnumerable BuildArguments(string? argName, JsonDocument? args) + private IEnumerable BuildArguments(string? name, JsonDocument? args) { var keyValues = new List(); if (args != null) { - keyValues.Add(new KeyValue(argName ?? "rule_args", args.RootElement.GetRawText())); + keyValues.Add(new KeyValue(name ?? "rule_args", args.RootElement.GetRawText())); } return keyValues; } From d807ec6d6b3354f96f00b6a14436f2566a022908 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Thu, 6 Nov 2025 15:49:09 -0600 Subject: [PATCH 13/16] add rule trigger statement --- .../BotSharp.Abstraction/Rules/IRuleTrigger.cs | 5 +++++ .../BotSharp.Core.Rules/Engines/RuleEngine.cs | 2 +- .../Files/Services/Instruct/FileInstructService.cs | 2 +- .../templates/rule-trigger-code-generate_instruction.liquid | 2 +- .../Controllers/Agent/AgentController.Rule.cs | 6 ++++-- .../ViewModels/Agents/View/AgentRuleViewModel.cs | 6 ++++++ 6 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleTrigger.cs b/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleTrigger.cs index 4a58027f4..c7ad59d9a 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleTrigger.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleTrigger.cs @@ -16,4 +16,9 @@ public interface IRuleTrigger /// The default arguments as input to code trigger (display purpose) /// JsonDocument OutputArgs => JsonDocument.Parse("{}"); + + /// + /// Explain the purpose of rule trigger (display purpose) + /// + string Statement => string.Empty; } diff --git a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs index 61073b6d5..4f9360b96 100644 --- a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs +++ b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs @@ -159,7 +159,7 @@ private IEnumerable BuildArguments(string? name, JsonDocument? args) var keyValues = new List(); if (args != null) { - keyValues.Add(new KeyValue(name ?? "rule_args", args.RootElement.GetRawText())); + keyValues.Add(new KeyValue(name ?? "trigger_args", args.RootElement.GetRawText())); } return keyValues; } diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.cs index fb34f24d0..fd5d29122 100644 --- a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.cs +++ b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.cs @@ -81,7 +81,7 @@ private async Task DownloadFile(InstructFileModel file) private string BuildFileName(string? name, string? extension, string defaultName, string defaultExtension) { var fname = name.IfNullOrEmptyAs(defaultName); - var fextension = extension.IfNullOrEmptyAs(defaultExtension); + var fextension = extension.IfNullOrEmptyAs(defaultExtension)!; fextension = fextension.StartsWith(".") ? fextension.Substring(1) : fextension; return $"{name}.{fextension}"; } diff --git a/src/Infrastructure/BotSharp.Core/data/agents/c2a2faf6-b8b5-47fe-807b-f4714cf25dd4/templates/rule-trigger-code-generate_instruction.liquid b/src/Infrastructure/BotSharp.Core/data/agents/c2a2faf6-b8b5-47fe-807b-f4714cf25dd4/templates/rule-trigger-code-generate_instruction.liquid index f7a18b172..2293ede7b 100644 --- a/src/Infrastructure/BotSharp.Core/data/agents/c2a2faf6-b8b5-47fe-807b-f4714cf25dd4/templates/rule-trigger-code-generate_instruction.liquid +++ b/src/Infrastructure/BotSharp.Core/data/agents/c2a2faf6-b8b5-47fe-807b-f4714cf25dd4/templates/rule-trigger-code-generate_instruction.liquid @@ -4,7 +4,7 @@ User's request is {{user_request}} Couple of notes to address: 1. You need to generate a function named check_trigger_criterion(args), where input is a json object. The example of this json object is {{args_example}}. -2. The input to this function comes from the arguments. You must use "ArgumentParser" to take an argument named "states", and then use "parse_known_args" to get the raw args. +2. The input to this function comes from the arguments. You must use "ArgumentParser" to take an argument named "trigger_args", and then use "parse_known_args" to get the raw args. 3. After getting the raw args, you need to use 'clean_args = bytes(raw_args, "utf-8").decode("unicode_escape")' to clean the raw argument, and then use 'args = json.loads(clean_args)' to get the json args. 4. Based on the user's request and input args, generate the function logic and ONLY return a boolean value. 5. You must only call check_trigger_criterion with the parsed json args in "if __name__ == '__main__'" block, and print only the function output. If any error occurs, print "Error". diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/Agent/AgentController.Rule.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Agent/AgentController.Rule.cs index 5feddec47..52fd719fd 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/Agent/AgentController.Rule.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Agent/AgentController.Rule.cs @@ -11,9 +11,11 @@ public IEnumerable GetRuleTriggers() var triggers = _services.GetServices(); return triggers.Select(x => new AgentRuleViewModel { - TriggerName = x.GetType().Name, + TriggerName = x.Name, + Channel = x.Channel, + Statement = x.Statement, OutputArgs = x.OutputArgs - }).OrderBy(x => x.TriggerName).ToList(); + }).OrderBy(x => x.TriggerName); } [HttpGet("/rule/formalization")] diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/View/AgentRuleViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/View/AgentRuleViewModel.cs index 6297a6733..23386c13d 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/View/AgentRuleViewModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/View/AgentRuleViewModel.cs @@ -7,6 +7,12 @@ public class AgentRuleViewModel [JsonPropertyName("trigger_name")] public string TriggerName { get; set; } = string.Empty; + [JsonPropertyName("channel")] + public string Channel { get; set; } = string.Empty; + + [JsonPropertyName("statement")] + public string Statement { get; set; } = string.Empty; + [JsonPropertyName("output_args")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public JsonDocument? OutputArgs { get; set; } From d4df3172dd6c6a511ee21057f764b9006928d855 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Thu, 6 Nov 2025 16:02:33 -0600 Subject: [PATCH 14/16] add render text --- .../Instruct/FileInstructService.Audio.cs | 8 ++++++-- .../Instruct/FileInstructService.Image.cs | 15 ++++++++++----- .../Instruct/FileInstructService.Pdf.cs | 3 ++- .../Services/Instruct/FileInstructService.cs | 17 +++++++++++++++-- 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Audio.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Audio.cs index 62a1356cc..a1cc821e2 100644 --- a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Audio.cs +++ b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Audio.cs @@ -9,9 +9,13 @@ public async Task SpeechToText(InstructFileModel audio, string? text = n if (string.IsNullOrWhiteSpace(text)) { var innerAgentId = options?.AgentId ?? Guid.Empty.ToString(); - text = await GetAgentTemplate(innerAgentId, options?.TemplateName); + text = await RenderAgentTemplate(innerAgentId, options?.TemplateName, options?.Data); } - + else + { + text = RenderText(text, options?.Data); + } + var completion = CompletionProvider.GetAudioTranscriber(_services, provider: options?.Provider, model: options?.Model); var audioBinary = await DownloadFile(audio); using var stream = audioBinary.ToStream(); diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Image.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Image.cs index cd9c42afc..50e912565 100644 --- a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Image.cs +++ b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Image.cs @@ -10,7 +10,8 @@ public partial class FileInstructService public async Task ReadImages(string text, IEnumerable images, InstructOptions? options = null) { var innerAgentId = options?.AgentId ?? Guid.Empty.ToString(); - var instruction = await GetAgentTemplate(innerAgentId, options?.TemplateName); + var instruction = await RenderAgentTemplate(innerAgentId, options?.TemplateName, options?.Data); + text = RenderText(text, options?.Data); var completion = CompletionProvider.GetChatCompletion(_services, provider: options?.Provider ?? "openai", model: options?.Model ?? "gpt-4o", multiModal: true); var message = await completion.GetChatCompletions(new Agent() @@ -48,7 +49,8 @@ await hook.OnResponseGenerated(new InstructResponseModel public async Task GenerateImage(string text, InstructOptions? options = null) { var innerAgentId = options?.AgentId ?? Guid.Empty.ToString(); - var instruction = await GetAgentTemplate(innerAgentId, options?.TemplateName); + var instruction = await RenderAgentTemplate(innerAgentId, options?.TemplateName, options?.Data); + text = RenderText(text, options?.Data); var textContent = text.IfNullOrEmptyAs(instruction).IfNullOrEmptyAs(string.Empty); var completion = CompletionProvider.GetImageCompletion(_services, provider: options?.Provider ?? "openai", model: options?.Model ?? "gpt-image-1-mini"); @@ -124,7 +126,8 @@ public async Task EditImage(string text, InstructFileModel imag } var innerAgentId = options?.AgentId ?? Guid.Empty.ToString(); - var instruction = await GetAgentTemplate(innerAgentId, options?.TemplateName); + var instruction = await RenderAgentTemplate(innerAgentId, options?.TemplateName, options?.Data); + text = RenderText(text, options?.Data); var completion = CompletionProvider.GetImageCompletion(_services, provider: options?.Provider ?? "openai", model: options?.Model ?? "gpt-image-1-mini"); var binary = await DownloadFile(image); @@ -173,7 +176,8 @@ public async Task EditImage(string text, InstructFileModel imag } var innerAgentId = options?.AgentId ?? Guid.Empty.ToString(); - var instruction = await GetAgentTemplate(innerAgentId, options?.TemplateName); + var instruction = await RenderAgentTemplate(innerAgentId, options?.TemplateName, options?.Data); + text = RenderText(text, options?.Data); var completion = CompletionProvider.GetImageCompletion(_services, provider: options?.Provider ?? "openai", model: options?.Model ?? "gpt-image-1-mini"); var imageBinary = await DownloadFile(image); @@ -225,7 +229,8 @@ await hook.OnResponseGenerated(new InstructResponseModel public async Task ComposeImages(string text, InstructFileModel[] images, InstructOptions? options = null) { var innerAgentId = options?.AgentId ?? Guid.Empty.ToString(); - var instruction = await GetAgentTemplate(innerAgentId, options?.TemplateName); + var instruction = await RenderAgentTemplate(innerAgentId, options?.TemplateName, options?.Data); + text = RenderText(text, options?.Data); var completion = CompletionProvider.GetImageCompletion(_services, provider: options?.Provider ?? "openai", model: options?.Model ?? "gpt-image-1-mini"); diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Pdf.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Pdf.cs index 9defa8cc8..bee60f1af 100644 --- a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Pdf.cs +++ b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Pdf.cs @@ -40,7 +40,8 @@ public async Task ReadPdf(string text, List files, In } var innerAgentId = options?.AgentId ?? Guid.Empty.ToString(); - var instruction = await GetAgentTemplate(innerAgentId, options?.TemplateName); + var instruction = await RenderAgentTemplate(innerAgentId, options?.TemplateName, options?.Data); + text = RenderText(text, options?.Data); var completion = CompletionProvider.GetChatCompletion(_services, provider: provider, model: options?.Model ?? "gpt-5-mini", multiModal: true); diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.cs index fd5d29122..bcb8b786c 100644 --- a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.cs +++ b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.cs @@ -1,4 +1,6 @@ +using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Files.Converters; +using BotSharp.Abstraction.Templating; namespace BotSharp.Core.Files.Services; @@ -60,7 +62,7 @@ private async Task DownloadFile(InstructFileModel file) } } - private async Task GetAgentTemplate(string agentId, string? templateName) + private async Task RenderAgentTemplate(string agentId, string? templateName, IDictionary? data = null) { if (string.IsNullOrWhiteSpace(agentId) || string.IsNullOrWhiteSpace(templateName)) { @@ -74,10 +76,21 @@ private async Task DownloadFile(InstructFileModel file) return null; } - var instruction = agentService.RenderTemplate(agent, templateName); + var instruction = agentService.RenderTemplate(agent, templateName, data); return instruction; } + private string RenderText(string text, IDictionary? data = null) + { + var agentService = _services.GetRequiredService(); + var render = _services.GetRequiredService(); + + var renderData = data != null + ? new Dictionary(data) + : agentService.CollectRenderData(new Agent()); + return render.Render(text, renderData); + } + private string BuildFileName(string? name, string? extension, string defaultName, string defaultExtension) { var fname = name.IfNullOrEmptyAs(defaultName); From fb36f2e25c4eac8db752c1f86eacf5c3453c2bd7 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Thu, 6 Nov 2025 16:42:41 -0600 Subject: [PATCH 15/16] return agent code script time stamp --- .../Agents/IAgentService.cs | 4 ++-- .../Agents/Models/AgentCodeScript.cs | 3 +++ .../Instructs/Contexts/CodeInstructContext.cs | 3 ++- .../Repositories/IBotSharpRepository.cs | 2 +- .../BotSharp.Core.Rules/Engines/RuleEngine.cs | 4 ++-- .../Agents/Services/AgentService.Coding.cs | 2 +- .../Services/Instruct/FileInstructService.cs | 2 +- .../Services/InstructService.Execute.cs | 9 +++++---- .../FileRepository.AgentCodeScript.cs | 17 +++++++++++++---- .../Collections/AgentCodeScriptDocument.cs | 4 +++- .../MongoRepository.AgentCodeScript.cs | 4 ++-- 11 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs index b45ff557a..6075b4543 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs @@ -72,8 +72,8 @@ public interface IAgentService Task> GetAgentCodeScripts(string agentId, AgentCodeScriptFilter? filter = null) => Task.FromResult(new List()); - Task GetAgentCodeScript(string agentId, string scriptName, string scriptType = AgentCodeScriptType.Src) - => Task.FromResult(string.Empty); + Task GetAgentCodeScript(string agentId, string scriptName, string scriptType = AgentCodeScriptType.Src) + => Task.FromResult((AgentCodeScript?)null); Task UpdateAgentCodeScripts(string agentId, List codeScripts, AgentCodeScriptUpdateOptions? options = null) => Task.FromResult(false); diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentCodeScript.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentCodeScript.cs index 840d67210..909f1d002 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentCodeScript.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentCodeScript.cs @@ -5,6 +5,9 @@ public class AgentCodeScript : AgentCodeScriptBase public string Id { get; set; } public string AgentId { get; set; } = null!; + public DateTime CreatedTime { get; set; } = DateTime.UtcNow; + public DateTime UpdatedTime { get; set; } = DateTime.UtcNow; + public AgentCodeScript() : base() { } diff --git a/src/Infrastructure/BotSharp.Abstraction/Instructs/Contexts/CodeInstructContext.cs b/src/Infrastructure/BotSharp.Abstraction/Instructs/Contexts/CodeInstructContext.cs index 77e37efaa..63b1c2875 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Instructs/Contexts/CodeInstructContext.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Instructs/Contexts/CodeInstructContext.cs @@ -2,7 +2,8 @@ namespace BotSharp.Abstraction.Instructs.Contexts; public class CodeInstructContext { - public string CodeScript { get; set; } + public string ScriptName { get; set; } + public string ScriptContent { get; set; } public string ScriptType { get; set; } public List Arguments { get; set; } = []; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs index 52f43fb6b..374271b9f 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs @@ -114,7 +114,7 @@ bool DeleteAgentTasks(string agentId, List? taskIds = null) #region Agent Code List GetAgentCodeScripts(string agentId, AgentCodeScriptFilter? filter = null) => throw new NotImplementedException(); - string? GetAgentCodeScript(string agentId, string scriptName, string scriptType = AgentCodeScriptType.Src) + AgentCodeScript? GetAgentCodeScript(string agentId, string scriptName, string scriptType = AgentCodeScriptType.Src) => throw new NotImplementedException(); bool UpdateAgentCodeScripts(string agentId, List scripts, AgentCodeScriptDbUpdateOptions? options = null) => throw new NotImplementedException(); diff --git a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs index 4f9360b96..deec9a4ad 100644 --- a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs +++ b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs @@ -111,7 +111,7 @@ private async Task TriggerCodeScript(string agentId, string triggerName, R var msg = $"rule trigger ({triggerName}) code script ({scriptName}) in agent ({agentId}) => args: {options.ArgumentContent?.RootElement.GetRawText()}."; - if (string.IsNullOrWhiteSpace(codeScript)) + if (string.IsNullOrWhiteSpace(codeScript?.Content)) { _logger.LogWarning($"Unable to find {msg}."); return false; @@ -119,7 +119,7 @@ private async Task TriggerCodeScript(string agentId, string triggerName, R try { - var response = await processor.RunAsync(codeScript, options: new() + var response = await processor.RunAsync(codeScript.Content, options: new() { ScriptName = scriptName, Arguments = BuildArguments(options.ArgumentName, options.ArgumentContent) diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Coding.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Coding.cs index 9f6777d0f..936bdaf40 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Coding.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Coding.cs @@ -14,7 +14,7 @@ public async Task> GetAgentCodeScripts(string agentId, Age return await Task.FromResult(scripts); } - public async Task GetAgentCodeScript(string agentId, string scriptName, string scriptType = AgentCodeScriptType.Src) + public async Task GetAgentCodeScript(string agentId, string scriptName, string scriptType = AgentCodeScriptType.Src) { var db = _services.GetRequiredService(); var script = db.GetAgentCodeScript(agentId, scriptName, scriptType); diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.cs index bcb8b786c..48cf5e83c 100644 --- a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.cs +++ b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.cs @@ -101,7 +101,7 @@ private string BuildFileName(string? name, string? extension, string defaultName private IImageConverter? GetImageConverter(string? provider) { - var converter = _services.GetServices().FirstOrDefault(x => x.Provider == (provider ?? "file-handler")); + var converter = _services.GetServices().FirstOrDefault(x => x.Provider == (provider ?? "image-handler")); return converter; } #endregion diff --git a/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs b/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs index 56a006dab..8ba6e499b 100644 --- a/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs +++ b/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs @@ -214,7 +214,7 @@ await hook.OnResponseGenerated(new InstructResponseModel // Get code script var scriptType = codeOptions?.ScriptType ?? AgentCodeScriptType.Src; var codeScript = await agentService.GetAgentCodeScript(agent.Id, scriptName, scriptType); - if (string.IsNullOrWhiteSpace(codeScript)) + if (string.IsNullOrWhiteSpace(codeScript?.Content)) { #if DEBUG _logger.LogWarning($"Empty code script. (Agent: {agent.Id}, {scriptName})"); @@ -231,7 +231,8 @@ await hook.OnResponseGenerated(new InstructResponseModel var context = new CodeInstructContext { - CodeScript = codeScript, + ScriptName = codeScript.Name, + ScriptContent = codeScript.Content, ScriptType = scriptType, Arguments = arguments }; @@ -254,7 +255,7 @@ await hook.OnResponseGenerated(new InstructResponseModel } // Run code script - var codeResponse = await codeProcessor.RunAsync(context.CodeScript, options: new() + var codeResponse = await codeProcessor.RunAsync(context.ScriptContent, options: new() { ScriptName = scriptName, Arguments = context.Arguments @@ -284,7 +285,7 @@ await hook.OnResponseGenerated(new InstructResponseModel Model = string.Empty, TemplateName = scriptName, UserMessage = message.Content, - SystemInstruction = context?.CodeScript, + SystemInstruction = $"Code script name: {codeScript.Name}, Version: {codeScript.UpdatedTime.ToString("o")}", CompletionText = response.Text }); } diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentCodeScript.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentCodeScript.cs index 7ebfdc84f..fa4d11a9f 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentCodeScript.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentCodeScript.cs @@ -50,7 +50,7 @@ public List GetAgentCodeScripts(string agentId, AgentCodeScript return results; } - public string? GetAgentCodeScript(string agentId, string scriptName, string scriptType = AgentCodeScriptType.Src) + public AgentCodeScript? GetAgentCodeScript(string agentId, string scriptName, string scriptType = AgentCodeScriptType.Src) { if (string.IsNullOrWhiteSpace(agentId) || string.IsNullOrWhiteSpace(scriptName) @@ -66,11 +66,20 @@ public List GetAgentCodeScripts(string agentId, AgentCodeScript } var foundFile = Directory.EnumerateFiles(dir).FirstOrDefault(file => scriptName.IsEqualTo(Path.GetFileName(file))); - if (!string.IsNullOrEmpty(foundFile)) + if (!File.Exists(foundFile)) { - return File.ReadAllText(foundFile); + return null; } - return string.Empty; + + return new AgentCodeScript + { + AgentId = agentId, + Name = scriptName, + ScriptType = scriptType, + Content = File.ReadAllText(foundFile), + CreatedTime = File.GetCreationTimeUtc(foundFile), + UpdatedTime = File.GetLastWriteTimeUtc(foundFile) + }; } public bool UpdateAgentCodeScripts(string agentId, List scripts, AgentCodeScriptDbUpdateOptions? options = null) diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/AgentCodeScriptDocument.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/AgentCodeScriptDocument.cs index 9a39fb58c..52f4570a1 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/AgentCodeScriptDocument.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/AgentCodeScriptDocument.cs @@ -31,7 +31,9 @@ public static AgentCodeScript ToDomainModel(AgentCodeScriptDocument script) AgentId = script.AgentId, Name = script.Name, Content = script.Content, - ScriptType = script.ScriptType + ScriptType = script.ScriptType, + CreatedTime = script.CreatedTime, + UpdatedTime = script.UpdatedTime }; } } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentCodeScript.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentCodeScript.cs index 86285583d..b6e0dc148 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentCodeScript.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentCodeScript.cs @@ -35,7 +35,7 @@ public List GetAgentCodeScripts(string agentId, AgentCodeScript return found.Select(x => AgentCodeScriptDocument.ToDomainModel(x)).ToList(); } - public string? GetAgentCodeScript(string agentId, string scriptName, string scriptType = AgentCodeScriptType.Src) + public AgentCodeScript? GetAgentCodeScript(string agentId, string scriptName, string scriptType = AgentCodeScriptType.Src) { if (string.IsNullOrWhiteSpace(agentId) || string.IsNullOrWhiteSpace(scriptName) @@ -53,7 +53,7 @@ public List GetAgentCodeScripts(string agentId, AgentCodeScript }; var found = _dc.AgentCodeScripts.Find(builder.And(filters)).FirstOrDefault(); - return found?.Content; + return AgentCodeScriptDocument.ToDomainModel(found); } public bool UpdateAgentCodeScripts(string agentId, List scripts, AgentCodeScriptDbUpdateOptions? options = null) From 7ed92118703e44d5161f461d8ea2585bda504b95 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Fri, 7 Nov 2025 15:07:48 -0600 Subject: [PATCH 16/16] refine --- .../Files/Proccessors/IFileProcessor.cs | 5 + .../Knowledges/IKnowledgeService.cs | 3 +- .../Knowledges/Models/FileKnowledgeModel.cs | 18 ++ .../Models/KnowledgeFileModel.cs | 2 +- .../Options/FileKnowledgeProcessOptions.cs | 5 + .../Knowledges/Options/KnowledgeDocOptions.cs | 6 + .../Responses/FileKnowledgeResponse.cs | 6 + .../BotSharp.Abstraction/Rules/IRuleEngine.cs | 2 +- .../BotSharp.Core.Rules/Engines/RuleEngine.cs | 2 +- .../KnowledgeBase/KnowledgeBaseController.cs | 12 +- .../Request/VectorKnowledgeUploadRequest.cs | 4 +- .../Helpers/KnowledgeSettingHelper.cs | 1 - .../Services/KnowledgeService.Document.cs | 238 +++++++++++------- 13 files changed, 207 insertions(+), 97 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/FileKnowledgeModel.cs rename src/Infrastructure/BotSharp.Abstraction/{Files => Knowledges}/Models/KnowledgeFileModel.cs (87%) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Knowledges/Options/FileKnowledgeProcessOptions.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/Knowledges/Options/KnowledgeDocOptions.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/Knowledges/Responses/FileKnowledgeResponse.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Files/Proccessors/IFileProcessor.cs b/src/Infrastructure/BotSharp.Abstraction/Files/Proccessors/IFileProcessor.cs index 19ab437c9..cebc03e3b 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Files/Proccessors/IFileProcessor.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Files/Proccessors/IFileProcessor.cs @@ -1,5 +1,7 @@ using BotSharp.Abstraction.Files.Options; using BotSharp.Abstraction.Files.Responses; +using BotSharp.Abstraction.Knowledges.Options; +using BotSharp.Abstraction.Knowledges.Responses; namespace BotSharp.Abstraction.Files.Proccessors; @@ -9,4 +11,7 @@ public interface IFileProcessor Task HandleFilesAsync(Agent agent, string text, IEnumerable files, FileHandleOptions? options = null) => throw new NotImplementedException(); + + Task GetFileKnowledgeAsync(FileBinaryDataModel file, FileKnowledgeProcessOptions? options = null) + => throw new NotImplementedException(); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs b/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs index 767615fe5..393898b5f 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs @@ -36,8 +36,9 @@ public interface IKnowledgeService /// /// /// + /// /// - Task UploadDocumentsToKnowledge(string collectionName, IEnumerable files, ChunkOption? option = null); + Task UploadDocumentsToKnowledge(string collectionName, IEnumerable files, KnowledgeDocOptions? options = null); /// /// Save document content to knowledgebase without saving the document /// diff --git a/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/FileKnowledgeModel.cs b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/FileKnowledgeModel.cs new file mode 100644 index 000000000..0b79001e5 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/FileKnowledgeModel.cs @@ -0,0 +1,18 @@ +using BotSharp.Abstraction.VectorStorage.Models; + +namespace BotSharp.Abstraction.Knowledges.Models; + +public class FileKnowledgeModel +{ + public IEnumerable Contents { get; set; } = []; + public IDictionary? Payload { get; set; } +} + + +public class FileKnowledgeWrapper +{ + public Guid FileId { get; set; } + public string? FileSource { get; set; } + public FileBinaryDataModel FileData { get; set; } + public IEnumerable FileKnowledges { get; set; } = []; +} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Abstraction/Files/Models/KnowledgeFileModel.cs b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/KnowledgeFileModel.cs similarity index 87% rename from src/Infrastructure/BotSharp.Abstraction/Files/Models/KnowledgeFileModel.cs rename to src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/KnowledgeFileModel.cs index c3ca76fc7..49f9f1615 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Files/Models/KnowledgeFileModel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/KnowledgeFileModel.cs @@ -1,4 +1,4 @@ -namespace BotSharp.Abstraction.Files.Models; +namespace BotSharp.Abstraction.Knowledges.Models; public class KnowledgeFileModel { diff --git a/src/Infrastructure/BotSharp.Abstraction/Knowledges/Options/FileKnowledgeProcessOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Options/FileKnowledgeProcessOptions.cs new file mode 100644 index 000000000..f093ddedc --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Options/FileKnowledgeProcessOptions.cs @@ -0,0 +1,5 @@ +namespace BotSharp.Abstraction.Knowledges.Options; + +public class FileKnowledgeProcessOptions +{ +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Knowledges/Options/KnowledgeDocOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Options/KnowledgeDocOptions.cs new file mode 100644 index 000000000..826b47e68 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Options/KnowledgeDocOptions.cs @@ -0,0 +1,6 @@ +namespace BotSharp.Abstraction.Knowledges.Options; + +public class KnowledgeDocOptions +{ + public string? Processor { get; set; } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Knowledges/Responses/FileKnowledgeResponse.cs b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Responses/FileKnowledgeResponse.cs new file mode 100644 index 000000000..e0fdea716 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Responses/FileKnowledgeResponse.cs @@ -0,0 +1,6 @@ +namespace BotSharp.Abstraction.Knowledges.Responses; + +public class FileKnowledgeResponse +{ + public IEnumerable Knowledges { get; set; } = []; +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleEngine.cs b/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleEngine.cs index adcf87aaf..c7a6d847b 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleEngine.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleEngine.cs @@ -11,6 +11,6 @@ public interface IRuleEngine /// /// /// - Task> Trigger(IRuleTrigger trigger, string text, IEnumerable? states = null, RuleTriggerOptions? options = null) + Task> Triggered(IRuleTrigger trigger, string text, IEnumerable? states = null, RuleTriggerOptions? options = null) => throw new NotImplementedException(); } diff --git a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs index deec9a4ad..a8c1502b5 100644 --- a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs +++ b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs @@ -24,7 +24,7 @@ public RuleEngine( _logger = logger; } - public async Task> Trigger(IRuleTrigger trigger, string text, IEnumerable? states = null, RuleTriggerOptions? options = null) + public async Task> Triggered(IRuleTrigger trigger, string text, IEnumerable? states = null, RuleTriggerOptions? options = null) { var newConversationIds = new List(); diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBase/KnowledgeBaseController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBase/KnowledgeBaseController.cs index 923470b81..3a9a9e732 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBase/KnowledgeBaseController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBase/KnowledgeBaseController.cs @@ -207,13 +207,15 @@ public async Task DeleteVectorCollectionSnapshots([FromRoute] string colle [HttpPost("/knowledge/document/{collection}/upload")] public async Task UploadKnowledgeDocuments([FromRoute] string collection, [FromBody] VectorKnowledgeUploadRequest request) { - var response = await _knowledgeService.UploadDocumentsToKnowledge(collection, request.Files, request.ChunkOption); + var response = await _knowledgeService.UploadDocumentsToKnowledge(collection, request.Files, request.Options); return response; } - [HttpPost("/knowledge/document/{collection}/form-upload")] - public async Task UploadKnowledgeDocuments([FromRoute] string collection, - [FromForm] IEnumerable files, [FromForm] ChunkOption? option = null) + [HttpPost("/knowledge/document/{collection}/form")] + public async Task UploadKnowledgeDocuments( + [FromRoute] string collection, + [FromForm] IEnumerable files, + [FromForm] KnowledgeDocOptions? options = null) { if (files.IsNullOrEmpty()) { @@ -231,7 +233,7 @@ public async Task UploadKnowledgeDocuments([FromRoute] }); } - var response = await _knowledgeService.UploadDocumentsToKnowledge(collection, docs, option); + var response = await _knowledgeService.UploadDocumentsToKnowledge(collection, docs, options); return response; } diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/VectorKnowledgeUploadRequest.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/VectorKnowledgeUploadRequest.cs index 760519668..2a97733e4 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/VectorKnowledgeUploadRequest.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/VectorKnowledgeUploadRequest.cs @@ -8,6 +8,6 @@ public class VectorKnowledgeUploadRequest [JsonPropertyName("files")] public IEnumerable Files { get; set; } = new List(); - [JsonPropertyName("chunk_option")] - public ChunkOption? ChunkOption { get; set; } + [JsonPropertyName("options")] + public KnowledgeDocOptions? Options { get; set; } } diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Helpers/KnowledgeSettingHelper.cs b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Helpers/KnowledgeSettingHelper.cs index e1bebc582..2384d9ebb 100644 --- a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Helpers/KnowledgeSettingHelper.cs +++ b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Helpers/KnowledgeSettingHelper.cs @@ -30,7 +30,6 @@ public static ITextEmbedding GetTextEmbeddingSetting(IServiceProvider services, // Set up text embedding var embedding = services.GetServices().FirstOrDefault(x => x.Provider == provider); - if (dimension <= 0) { dimension = GetLlmTextEmbeddingDimension(services, provider, model); diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Document.cs b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Document.cs index 207879311..fd07c9f99 100644 --- a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Document.cs +++ b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Document.cs @@ -1,5 +1,6 @@ using BotSharp.Abstraction.Files; using BotSharp.Abstraction.Files.Models; +using BotSharp.Abstraction.Files.Proccessors; using BotSharp.Abstraction.Files.Utilities; using BotSharp.Abstraction.Knowledges.Filters; using BotSharp.Abstraction.Knowledges.Helpers; @@ -7,19 +8,20 @@ using BotSharp.Abstraction.Knowledges.Responses; using BotSharp.Abstraction.VectorStorage.Enums; using System.Net.Http; -using System.Net.Mime; namespace BotSharp.Plugin.KnowledgeBase.Services; public partial class KnowledgeService { - public async Task UploadDocumentsToKnowledge(string collectionName, - IEnumerable files, ChunkOption? option = null) + public async Task UploadDocumentsToKnowledge( + string collectionName, + IEnumerable files, + KnowledgeDocOptions? options = null) { var res = new UploadKnowledgeResponse { Success = [], - Failed = files?.Select(x => x.FileName) ?? new List() + Failed = files?.Select(x => x.FileName) ?? [] }; if (string.IsNullOrWhiteSpace(collectionName) || files.IsNullOrEmpty()) @@ -33,14 +35,13 @@ public async Task UploadDocumentsToKnowledge(string col return res; } - var db = _services.GetRequiredService(); var fileStoreage = _services.GetRequiredService(); - var userId = await GetUserId(); var vectorStoreProvider = _settings.VectorDb.Provider; + var knowledgeFiles = new List(); var successFiles = new List(); var failedFiles = new List(); - foreach (var file in files) + foreach (var file in files!) { if (string.IsNullOrWhiteSpace(file.FileData) && string.IsNullOrWhiteSpace(file.FileUrl)) @@ -52,52 +53,50 @@ public async Task UploadDocumentsToKnowledge(string col { // Get document info var (contentType, binary) = await GetFileInfo(file); - var contents = await GetFileContent(contentType, binary, option ?? ChunkOption.Default()); - - // Save document - var fileId = Guid.NewGuid(); - var saved = SaveDocument(collectionName, vectorStoreProvider, fileId, file.FileName, binary); - if (!saved) + var fileData = new FileBinaryDataModel + { + FileName = file.FileName, + ContentType = contentType, + FileBinaryData = binary + }; + var knowledges = await GetFileKnowledge(fileData, options); + if (knowledges.IsNullOrEmpty()) { failedFiles.Add(file.FileName); continue; } - // Save to vector db + var fileId = Guid.NewGuid(); var payload = new Dictionary() { - { KnowledgePayloadName.DataSource, VectorPayloadValue.BuildStringValue(VectorDataSource.File) }, - { KnowledgePayloadName.FileId, VectorPayloadValue.BuildStringValue(fileId.ToString()) }, - { KnowledgePayloadName.FileName, VectorPayloadValue.BuildStringValue(file.FileName) }, - { KnowledgePayloadName.FileSource, VectorPayloadValue.BuildStringValue(file.FileSource) } + { KnowledgePayloadName.DataSource, (VectorPayloadValue)VectorDataSource.File }, + { KnowledgePayloadName.FileId, (VectorPayloadValue)fileId.ToString() }, + { KnowledgePayloadName.FileName, (VectorPayloadValue)file.FileName }, + { KnowledgePayloadName.FileSource, (VectorPayloadValue)file.FileSource } }; if (!string.IsNullOrWhiteSpace(file.FileUrl)) { - payload[KnowledgePayloadName.FileUrl] = VectorPayloadValue.BuildStringValue(file.FileUrl); + payload[KnowledgePayloadName.FileUrl] = (VectorPayloadValue)file.FileUrl; } - var dataIds = await SaveToVectorDb(collectionName, contents, payload); - if (!dataIds.IsNullOrEmpty()) + foreach (var kg in knowledges) { - db.SaveKnolwedgeBaseFileMeta(new KnowledgeDocMetaData + var kgPayload = new Dictionary(kg.Payload ?? new Dictionary()); + foreach (var pair in payload) { - Collection = collectionName, - FileId = fileId, - FileName = file.FileName, - FileSource = file.FileSource, - ContentType = contentType, - VectorStoreProvider = vectorStoreProvider, - VectorDataIds = dataIds, - CreateDate = DateTime.UtcNow, - CreateUserId = userId - }); - successFiles.Add(file.FileName); + kgPayload[pair.Key] = pair.Value; + } + kg.Payload = kgPayload; } - else + + knowledgeFiles.Add(new() { - failedFiles.Add(file.FileName); - } + FileId = fileId, + FileData = fileData, + FileSource = VectorDataSource.File, + FileKnowledges = knowledges + }); } catch (Exception ex) { @@ -107,10 +106,11 @@ public async Task UploadDocumentsToKnowledge(string col } } + var response = await HandleKnowledgeFiles(collectionName, vectorStoreProvider, knowledgeFiles, saveFile: true); return new UploadKnowledgeResponse { - Success = successFiles, - Failed = failedFiles + Success = successFiles.Concat(response.Success).Distinct(), + Failed = failedFiles.Concat(response.Failed).Distinct() }; } @@ -136,39 +136,37 @@ public async Task ImportDocumentContentToKnowledge(string collectionName, var fileId = Guid.NewGuid(); var contentType = FileUtility.GetFileContentType(fileName); - var innerPayload = new Dictionary(); - if (payload != null) - { - foreach (var item in payload) - { - innerPayload[item.Key] = item.Value; - } - } - - innerPayload[KnowledgePayloadName.DataSource] = VectorPayloadValue.BuildStringValue(VectorDataSource.File); - innerPayload[KnowledgePayloadName.FileId] = VectorPayloadValue.BuildStringValue(fileId.ToString()); - innerPayload[KnowledgePayloadName.FileName] = VectorPayloadValue.BuildStringValue(fileName); - innerPayload[KnowledgePayloadName.FileSource] = VectorPayloadValue.BuildStringValue(fileSource); + var innerPayload = new Dictionary(payload ?? []); + innerPayload[KnowledgePayloadName.DataSource] = (VectorPayloadValue)VectorDataSource.File; + innerPayload[KnowledgePayloadName.FileId] = (VectorPayloadValue)fileId.ToString(); + innerPayload[KnowledgePayloadName.FileName] = (VectorPayloadValue)fileName; + innerPayload[KnowledgePayloadName.FileSource] = (VectorPayloadValue)fileSource; if (!string.IsNullOrWhiteSpace(refData?.Url)) { - innerPayload[KnowledgePayloadName.FileUrl] = VectorPayloadValue.BuildStringValue(refData.Url); + innerPayload[KnowledgePayloadName.FileUrl] = (VectorPayloadValue)refData.Url; } - var dataIds = await SaveToVectorDb(collectionName, contents, innerPayload); - db.SaveKnolwedgeBaseFileMeta(new KnowledgeDocMetaData + var kgFile = new FileKnowledgeWrapper { - Collection = collectionName, FileId = fileId, - FileName = fileName, FileSource = fileSource, - ContentType = contentType, - VectorStoreProvider = vectorStoreProvider, - VectorDataIds = dataIds, - RefData = refData, - CreateDate = DateTime.UtcNow, - CreateUserId = userId - }); + FileData = new() + { + FileName = fileName, + ContentType = contentType, + FileBinaryData = BinaryData.Empty + }, + FileKnowledges = new List + { + new() + { + Contents = contents, + Payload = innerPayload + } + } + }; + await HandleKnowledgeFiles(collectionName, vectorStoreProvider, [kgFile], saveFile: false); return true; } catch (Exception ex) @@ -338,7 +336,6 @@ public async Task GetKnowledgeDocumentBinaryData(string col } - #region Private methods /// /// Get file content type and file bytes @@ -370,20 +367,16 @@ public async Task GetKnowledgeDocumentBinaryData(string col } #region Read doc content - private async Task> GetFileContent(string contentType, BinaryData binary, ChunkOption option) + private async Task> GetFileKnowledge(FileBinaryDataModel file, KnowledgeDocOptions? options) { - IEnumerable results = new List(); - - if (contentType.IsEqualTo(MediaTypeNames.Text.Plain)) + var processor = _services.GetServices().FirstOrDefault(x => x.Provider.IsEqualTo(options?.Processor)); + if (processor == null) { - results = await ReadTxt(binary, option); + return Enumerable.Empty(); } - else if (contentType.IsEqualTo(MediaTypeNames.Application.Pdf)) - { - results = await ReadPdf(binary); - } - - return results; + + var response = await processor.GetFileKnowledgeAsync(file, options: new() { }); + return response?.Knowledges ?? []; } private async Task> ReadTxt(BinaryData binary, ChunkOption option) @@ -398,11 +391,6 @@ private async Task> ReadTxt(BinaryData binary, ChunkOption o var lines = TextChopper.Chop(content, option); return lines; } - - private async Task> ReadPdf(BinaryData binary) - { - return Enumerable.Empty(); - } #endregion @@ -427,16 +415,96 @@ private async Task> SaveToVectorDb(string collectionName, IE for (int i = 0; i < contents.Count(); i++) { var content = contents.ElementAt(i); - var vector = await textEmbedding.GetVectorAsync(content); - var dataId = Guid.NewGuid(); - var saved = await vectorDb.Upsert(collectionName, dataId, vector, content, payload ?? []); - if (!saved) continue; + try + { + var vector = await textEmbedding.GetVectorAsync(content); + var dataId = Guid.NewGuid(); + var saved = await vectorDb.Upsert(collectionName, dataId, vector, content, payload ?? []); + + if (!saved) + { + continue; + } - dataIds.Add(dataId.ToString()); + dataIds.Add(dataId.ToString()); + } + catch (Exception ex) + { + _logger.LogError(ex, $"Error when saving file knowledge to vector db collection {collectionName}. (Content: {content.SubstringMax(20)})"); + } } return dataIds; } + + private async Task HandleKnowledgeFiles( + string collectionName, + string vectorStore, + IEnumerable knowledgeFiles, + bool saveFile = false) + { + if (knowledgeFiles.IsNullOrEmpty()) + { + return new(); + } + + var successFiles = new List(); + var failedFiles = new List(); + var db = _services.GetRequiredService(); + + var userId = await GetUserId(); + foreach (var item in knowledgeFiles) + { + var file = item.FileData; + + // Save document + if (saveFile) + { + var saved = SaveDocument(collectionName, vectorStore, item.FileId, file.FileName, file.FileBinaryData); + if (!saved) + { + _logger.LogWarning($"Failed to save knowledge file: {file.FileName} to collection {collectionName}."); + failedFiles.Add(file.FileName); + continue; + } + } + + // Save to vector db + var dataIds = new List(); + foreach (var kg in item.FileKnowledges) + { + var ids = await SaveToVectorDb(collectionName, kg.Contents, kg.Payload?.ToDictionary()); + dataIds.AddRange(ids); + } + + if (!dataIds.IsNullOrEmpty()) + { + db.SaveKnolwedgeBaseFileMeta(new KnowledgeDocMetaData + { + Collection = collectionName, + FileId = item.FileId, + FileName = file.FileName, + FileSource = item.FileSource ?? VectorDataSource.File, + ContentType = file.ContentType, + VectorStoreProvider = vectorStore, + VectorDataIds = dataIds, + CreateDate = DateTime.UtcNow, + CreateUserId = userId + }); + successFiles.Add(file.FileName); + } + else + { + failedFiles.Add(file.FileName); + } + } + + return new UploadKnowledgeResponse + { + Success = successFiles, + Failed = failedFiles + }; + } #endregion }