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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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 } From 097d8fb3ecf303d47edc6de079de8edbd4a10c23 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Mon, 10 Nov 2025 15:17:27 -0600 Subject: [PATCH 17/27] refine mongo collection index --- .../Agents/IAgentService.cs | 2 +- ...cessOptions.cs => CodeGenHandleOptions.cs} | 2 +- .../Coding/Settings/CodingSettings.cs | 1 + .../Agents/Services/AgentService.Coding.cs | 5 +- .../Coding/CodeScriptExecutor.cs | 6 ++- .../AgentCodeScriptGenerationRequest.cs | 2 +- .../MongoDbContext.cs | 52 ++++++++++++++----- .../MongoRepository.AgentCodeScript.cs | 5 ++ 8 files changed, 54 insertions(+), 21 deletions(-) rename src/Infrastructure/BotSharp.Abstraction/Coding/Options/{CodeProcessOptions.cs => CodeGenHandleOptions.cs} (92%) diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs index 6075b4543..d0284b224 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs @@ -81,6 +81,6 @@ Task UpdateAgentCodeScripts(string agentId, List codeScri Task DeleteAgentCodeScripts(string agentId, List? codeScripts = null) => Task.FromResult(false); - Task GenerateCodeScript(string agentId, string text, CodeProcessOptions? options = null) + Task GenerateCodeScript(string agentId, string text, CodeGenHandleOptions? options = null) => Task.FromResult(new CodeGenerationResult()); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Coding/Options/CodeProcessOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Coding/Options/CodeGenHandleOptions.cs similarity index 92% rename from src/Infrastructure/BotSharp.Abstraction/Coding/Options/CodeProcessOptions.cs rename to src/Infrastructure/BotSharp.Abstraction/Coding/Options/CodeGenHandleOptions.cs index 343447b6b..963cb8a15 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Coding/Options/CodeProcessOptions.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Coding/Options/CodeGenHandleOptions.cs @@ -1,6 +1,6 @@ namespace BotSharp.Abstraction.Coding.Options; -public class CodeProcessOptions : CodeGenerationOptions +public class CodeGenHandleOptions : CodeGenerationOptions { /// /// Code processor provider diff --git a/src/Infrastructure/BotSharp.Abstraction/Coding/Settings/CodingSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Coding/Settings/CodingSettings.cs index 319581b0b..5ab53a598 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Coding/Settings/CodingSettings.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Coding/Settings/CodingSettings.cs @@ -11,6 +11,7 @@ public class CodingSettings public class CodeScriptGenerationSettings : LlmConfigBase { + public string? Processor { get; set; } = BuiltInCodeProcessor.PyInterpreter; public int? MessageLimit { get; set; } } diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Coding.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Coding.cs index 936bdaf40..6377be27d 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Coding.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Coding.cs @@ -67,7 +67,7 @@ public async Task DeleteAgentCodeScripts(string agentId, List GenerateCodeScript(string agentId, string text, CodeProcessOptions? options = null) + public async Task GenerateCodeScript(string agentId, string text, CodeGenHandleOptions? options = null) { if (string.IsNullOrWhiteSpace(agentId)) { @@ -77,7 +77,8 @@ public async Task GenerateCodeScript(string agentId, strin }; } - var processor = options?.Processor ?? BuiltInCodeProcessor.PyInterpreter; + var settings = _services.GetRequiredService(); + var processor = options?.Processor ?? settings?.CodeGeneration?.Processor ?? BuiltInCodeProcessor.PyInterpreter; var codeProcessor = _services.GetServices().FirstOrDefault(x => x.Provider.IsEqualTo(processor)); if (codeProcessor == null) { diff --git a/src/Infrastructure/BotSharp.Core/Coding/CodeScriptExecutor.cs b/src/Infrastructure/BotSharp.Core/Coding/CodeScriptExecutor.cs index a7aae26e9..ef540593e 100644 --- a/src/Infrastructure/BotSharp.Core/Coding/CodeScriptExecutor.cs +++ b/src/Infrastructure/BotSharp.Core/Coding/CodeScriptExecutor.cs @@ -2,9 +2,11 @@ namespace BotSharp.Core.Coding; public class CodeScriptExecutor { + private const int DEFAULT_MAX_CONCURRENCY = 1; + private readonly CodingSettings _settings; private readonly ILogger _logger; - private readonly SemaphoreSlim _semLock = new(initialCount: 1, maxCount: 1); + private readonly SemaphoreSlim _semLock = new(initialCount: DEFAULT_MAX_CONCURRENCY, maxCount: DEFAULT_MAX_CONCURRENCY); public CodeScriptExecutor( CodingSettings settings, @@ -13,7 +15,7 @@ public CodeScriptExecutor( _settings = settings; _logger = logger; - var maxConcurrency = settings.CodeExecution?.MaxConcurrency > 0 ? settings.CodeExecution.MaxConcurrency : 1; + var maxConcurrency = settings.CodeExecution?.MaxConcurrency > 0 ? settings.CodeExecution.MaxConcurrency : DEFAULT_MAX_CONCURRENCY; _semLock = new(initialCount: maxConcurrency, maxCount: maxConcurrency); } diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/Request/AgentCodeScriptGenerationRequest.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/Request/AgentCodeScriptGenerationRequest.cs index 7b3b1607f..3c68ac897 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/Request/AgentCodeScriptGenerationRequest.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/Request/AgentCodeScriptGenerationRequest.cs @@ -10,5 +10,5 @@ public class AgentCodeScriptGenerationRequest [JsonPropertyName("options")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public CodeProcessOptions? Options { get; set; } + public CodeGenHandleOptions? Options { get; set; } } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/MongoDbContext.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/MongoDbContext.cs index a0d0ccff6..05fed34d7 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/MongoDbContext.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/MongoDbContext.cs @@ -15,7 +15,7 @@ public MongoDbContext(BotSharpDatabaseSettings dbSettings) var mongoDbConnectionString = dbSettings.BotSharpMongoDb; _mongoClient = new MongoClient(mongoDbConnectionString); _mongoDbDatabaseName = GetDatabaseName(mongoDbConnectionString); - _collectionPrefix = dbSettings.TablePrefix.IfNullOrEmptyAs("BotSharp"); + _collectionPrefix = dbSettings.TablePrefix.IfNullOrEmptyAs("BotSharp")!; } private string GetDatabaseName(string mongoDbConnectionString) @@ -61,7 +61,9 @@ private bool CollectionExists(IMongoDatabase database, string collectionName) private IMongoCollection GetCollectionOrCreate(string name) { if (string.IsNullOrWhiteSpace(name)) + { throw new ArgumentException($"The collection {name} cannot be empty."); + } var collectionName = $"{_collectionPrefix}_{name}"; if (!CollectionExists(Database, collectionName)) @@ -74,6 +76,29 @@ private IMongoCollection GetCollectionOrCreate(string name } #region Indexes + private IMongoCollection CreateAgentCodeScriptIndex() + { + var collection = GetCollectionOrCreate("AgentCodeScripts"); + var curIndexes = collection.Indexes.List().ToList().Where(x => x.Contains("name")).Select(x => x["name"].AsString); + + if (!curIndexes.Any(x => x.StartsWith("AgentId"))) + { + CreateIndex(collection, Builders.IndexKeys.Ascending(x => x.AgentId)); + } + + if (!curIndexes.Any(x => x.StartsWith("Name"))) + { + CreateIndex(collection, Builders.IndexKeys.Ascending(x => x.Name)); + } + + if (!curIndexes.Any(x => x.StartsWith("ScriptType"))) + { + CreateIndex(collection, Builders.IndexKeys.Ascending(x => x.ScriptType)); + } + + return collection; + } + private IMongoCollection CreateConversationIndex() { var collection = GetCollectionOrCreate("Conversations"); @@ -81,8 +106,7 @@ private IMongoCollection CreateConversationIndex() var createTimeIndex = indexes.FirstOrDefault(x => x.GetElement("name").ToString().StartsWith("CreatedTime")); if (createTimeIndex == null) { - var indexDef = Builders.IndexKeys.Descending(x => x.CreatedTime); - collection.Indexes.CreateOne(new CreateIndexModel(indexDef)); + CreateIndex(collection, Builders.IndexKeys.Descending(x => x.CreatedTime)); } return collection; } @@ -94,8 +118,7 @@ private IMongoCollection CreateConversationStateIndex var stateIndex = indexes.FirstOrDefault(x => x.GetElement("name").ToString().StartsWith("States.Key")); if (stateIndex == null) { - var indexDef = Builders.IndexKeys.Ascending("States.Key"); - collection.Indexes.CreateOne(new CreateIndexModel(indexDef)); + CreateIndex(collection, Builders.IndexKeys.Ascending("States.Key")); } return collection; } @@ -107,8 +130,7 @@ private IMongoCollection CreateAgentTaskIndex() var createTimeIndex = indexes.FirstOrDefault(x => x.GetElement("name").ToString().StartsWith("CreatedTime")); if (createTimeIndex == null) { - var indexDef = Builders.IndexKeys.Descending(x => x.CreatedTime); - collection.Indexes.CreateOne(new CreateIndexModel(indexDef)); + CreateIndex(collection, Builders.IndexKeys.Descending(x => x.CreatedTime)); } return collection; } @@ -120,8 +142,7 @@ private IMongoCollection CreateContentLogIndex() var createTimeIndex = indexes.FirstOrDefault(x => x.GetElement("name").ToString().StartsWith("CreatedTime")); if (createTimeIndex == null) { - var indexDef = Builders.IndexKeys.Ascending(x => x.CreatedTime); - collection.Indexes.CreateOne(new CreateIndexModel(indexDef)); + CreateIndex(collection, Builders.IndexKeys.Ascending(x => x.CreatedTime)); } return collection; } @@ -133,8 +154,7 @@ private IMongoCollection CreateStateLogIndex() var createTimeIndex = indexes.FirstOrDefault(x => x.GetElement("name").ToString().StartsWith("CreatedTime")); if (createTimeIndex == null) { - var indexDef = Builders.IndexKeys.Ascending(x => x.CreatedTime); - collection.Indexes.CreateOne(new CreateIndexModel(indexDef)); + CreateIndex(collection, Builders.IndexKeys.Ascending(x => x.CreatedTime)); } return collection; } @@ -146,11 +166,15 @@ private IMongoCollection CreateInstructionLogIndex() var createTimeIndex = indexes.FirstOrDefault(x => x.GetElement("name").ToString().StartsWith("CreatedTime")); if (createTimeIndex == null) { - var indexDef = Builders.IndexKeys.Descending(x => x.CreatedTime); - collection.Indexes.CreateOne(new CreateIndexModel(indexDef)); + CreateIndex(collection, Builders.IndexKeys.Descending(x => x.CreatedTime)); } return collection; } + + private void CreateIndex(IMongoCollection collection, IndexKeysDefinition indexKeyDef, CreateIndexOptions? options = null) where T : MongoBase + { + collection.Indexes.CreateOne(new CreateIndexModel(indexKeyDef, options)); + } #endregion #endregion @@ -161,7 +185,7 @@ public IMongoCollection AgentTasks => CreateAgentTaskIndex(); public IMongoCollection AgentCodeScripts - => GetCollectionOrCreate("AgentCodeScripts"); + => CreateAgentCodeScriptIndex(); public IMongoCollection Conversations => CreateConversationIndex(); diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentCodeScript.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentCodeScript.cs index b6e0dc148..07f9b36f3 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentCodeScript.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentCodeScript.cs @@ -53,6 +53,11 @@ public List GetAgentCodeScripts(string agentId, AgentCodeScript }; var found = _dc.AgentCodeScripts.Find(builder.And(filters)).FirstOrDefault(); + if (found == null) + { + return null; + } + return AgentCodeScriptDocument.ToDomainModel(found); } From c39d8ae660d2ef4d6612ee5966bb77ec2249b1e7 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Mon, 10 Nov 2025 17:27:38 -0600 Subject: [PATCH 18/27] clean code --- .../BotSharp.Core/Instructs/Services/InstructService.Execute.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs b/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs index 10d9fed57..6c1b88783 100644 --- a/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs +++ b/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs @@ -8,8 +8,6 @@ using BotSharp.Abstraction.Instructs.Options; using BotSharp.Abstraction.MLTasks; using BotSharp.Abstraction.Models; -using Microsoft.Extensions.Options; -using static Dapper.SqlMapper; namespace BotSharp.Core.Instructs; From 72b24d3045d8a3dbaaeab42027f66877a370af26 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Mon, 10 Nov 2025 17:31:27 -0600 Subject: [PATCH 19/27] minor change --- .../Repository/MongoRepository.AgentCodeScript.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentCodeScript.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentCodeScript.cs index 07f9b36f3..cc876e465 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentCodeScript.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentCodeScript.cs @@ -53,12 +53,7 @@ public List GetAgentCodeScripts(string agentId, AgentCodeScript }; var found = _dc.AgentCodeScripts.Find(builder.And(filters)).FirstOrDefault(); - if (found == null) - { - return null; - } - - return AgentCodeScriptDocument.ToDomainModel(found); + return found != null ? AgentCodeScriptDocument.ToDomainModel(found) : null; } public bool UpdateAgentCodeScripts(string agentId, List scripts, AgentCodeScriptDbUpdateOptions? options = null) From 906432d65a1aa48bbbed1424ea867c0ecf115372 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Tue, 11 Nov 2025 10:39:47 -0600 Subject: [PATCH 20/27] temp save --- .../Models/CodeExecutionResponseModel.cs | 10 ++++ .../Instructs/Contexts/CodeInstructContext.cs | 4 +- .../Instructs/IInstructHook.cs | 5 +- .../Instructs/InstructHookBase.cs | 12 +++++ .../BotSharp.Core.Rules/Engines/RuleEngine.cs | 41 +++++++++++++--- .../Services/InstructService.Execute.cs | 33 ++++++------- .../Hooks/InstructionLogHook.cs | 47 +++++++++++++++++-- 7 files changed, 117 insertions(+), 35 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Coding/Models/CodeExecutionResponseModel.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Coding/Models/CodeExecutionResponseModel.cs b/src/Infrastructure/BotSharp.Abstraction/Coding/Models/CodeExecutionResponseModel.cs new file mode 100644 index 000000000..df85fb02b --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Coding/Models/CodeExecutionResponseModel.cs @@ -0,0 +1,10 @@ +namespace BotSharp.Abstraction.Coding.Models; + +public class CodeExecutionResponseModel +{ + public string CodeProcessor { get; set; } = default!; + public AgentCodeScript CodeScript { get; set; } + public IDictionary? Arguments { get; set; } + public string Text { get; set; } = default!; + public string ExecutionResult { get; set; } = default!; +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Instructs/Contexts/CodeInstructContext.cs b/src/Infrastructure/BotSharp.Abstraction/Instructs/Contexts/CodeInstructContext.cs index 63b1c2875..5d2573d5a 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Instructs/Contexts/CodeInstructContext.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Instructs/Contexts/CodeInstructContext.cs @@ -2,8 +2,6 @@ namespace BotSharp.Abstraction.Instructs.Contexts; public class CodeInstructContext { - public string ScriptName { get; set; } - public string ScriptContent { get; set; } - public string ScriptType { get; set; } + public AgentCodeScript CodeScript { get; set; } public List Arguments { get; set; } = []; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructHook.cs b/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructHook.cs index c275691b4..46fad6585 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructHook.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructHook.cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.Coding.Models; using BotSharp.Abstraction.Hooks; using BotSharp.Abstraction.Instructs.Contexts; using BotSharp.Abstraction.Instructs.Models; @@ -10,6 +11,6 @@ public interface IInstructHook : IHookBase Task AfterCompletion(Agent agent, InstructResult result) => Task.CompletedTask; Task OnResponseGenerated(InstructResponseModel response) => Task.CompletedTask; - Task BeforeCodeExecution(Agent agent, RoleDialogModel message, CodeInstructContext context) => Task.CompletedTask; - Task AfterCodeExecution(Agent agent, InstructResult result) => Task.CompletedTask; + Task BeforeCodeExecution(Agent agent, CodeInstructContext context) => Task.CompletedTask; + Task AfterCodeExecution(Agent agent, CodeExecutionResponseModel response) => Task.CompletedTask; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Instructs/InstructHookBase.cs b/src/Infrastructure/BotSharp.Abstraction/Instructs/InstructHookBase.cs index 9209684cf..4ce35b1f9 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Instructs/InstructHookBase.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Instructs/InstructHookBase.cs @@ -1,3 +1,5 @@ +using BotSharp.Abstraction.Coding.Models; +using BotSharp.Abstraction.Instructs.Contexts; using BotSharp.Abstraction.Instructs.Models; namespace BotSharp.Abstraction.Instructs; @@ -20,4 +22,14 @@ public virtual async Task OnResponseGenerated(InstructResponseModel response) { await Task.CompletedTask; } + + public virtual async Task BeforeCodeExecution(Agent agent, CodeInstructContext context) + { + await Task.CompletedTask; + } + + public virtual async Task AfterCodeExecution(Agent agent, CodeExecutionResponseModel response) + { + await Task.CompletedTask; + } } diff --git a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs index d204daa01..de6bbc024 100644 --- a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs +++ b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs @@ -1,6 +1,9 @@ +using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Coding; using BotSharp.Abstraction.Coding.Enums; +using BotSharp.Abstraction.Coding.Settings; using BotSharp.Abstraction.Conversations; +using BotSharp.Abstraction.Hooks; using BotSharp.Abstraction.Models; using BotSharp.Abstraction.Repositories.Filters; using BotSharp.Abstraction.Rules.Options; @@ -15,13 +18,16 @@ public class RuleEngine : IRuleEngine { private readonly IServiceProvider _services; private readonly ILogger _logger; + private readonly CodingSettings _codingSettings; public RuleEngine( IServiceProvider services, - ILogger logger) + ILogger logger, + CodingSettings codingSettings) { _services = services; _logger = logger; + _codingSettings = codingSettings; } public async Task> Triggered(IRuleTrigger trigger, string text, IEnumerable? states = null, RuleTriggerOptions? options = null) @@ -42,7 +48,7 @@ public async Task> Triggered(IRuleTrigger trigger, string te 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; + bool? isTriggered = true; // Code trigger if (options != null) @@ -50,7 +56,7 @@ public async Task> Triggered(IRuleTrigger trigger, string te isTriggered = await TriggerCodeScript(agent.Id, trigger.Name, options); } - if (!isTriggered) + if (isTriggered != null && !isTriggered.Value) { continue; } @@ -90,7 +96,7 @@ await convService.SendMessage(agent.Id, } #region Private methods - private async Task TriggerCodeScript(string agentId, string triggerName, RuleTriggerOptions options) + private async Task TriggerCodeScript(string agentId, string triggerName, RuleTriggerOptions options) { if (string.IsNullOrWhiteSpace(agentId)) { @@ -114,18 +120,26 @@ private async Task TriggerCodeScript(string agentId, string triggerName, R if (string.IsNullOrWhiteSpace(codeScript?.Content)) { _logger.LogWarning($"Unable to find {msg}."); - return false; + return null; } try { - using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3)); + var hooks = _services.GetHooks(agentId); + + var (useLock, useProcess, timeoutSeconds) = GetCodeExecutionConfig(); + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds)); var response = await processor.RunAsync(codeScript.Content, options: new() { ScriptName = scriptName, Arguments = BuildArguments(options.ArgumentName, options.ArgumentContent), + UseLock = useLock, + UseProcess = useProcess }, cancellationToken: cts.Token); + + + if (response == null || !response.Success) { _logger.LogWarning($"Failed to handle {msg}"); @@ -164,5 +178,18 @@ private IEnumerable BuildArguments(string? name, JsonDocument? args) } return keyValues; } -#endregion + + private (bool, bool, int) GetCodeExecutionConfig() + { + var useLock = false; + var useProcess = false; + var timeoutSeconds = 3; + + useLock = _codingSettings.CodeExecution?.UseLock ?? useLock; + useProcess = _codingSettings.CodeExecution?.UseProcess ?? useProcess; + timeoutSeconds = _codingSettings.CodeExecution?.TimeoutSeconds > 0 ? _codingSettings.CodeExecution.TimeoutSeconds.Value : timeoutSeconds; + + return (useLock, useProcess, timeoutSeconds); + } + #endregion } diff --git a/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs b/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs index 6c1b88783..a21420d18 100644 --- a/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs +++ b/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs @@ -234,9 +234,7 @@ await hook.OnResponseGenerated(new InstructResponseModel var context = new CodeInstructContext { - ScriptName = codeScript.Name, - ScriptContent = codeScript.Content, - ScriptType = scriptType, + CodeScript = codeScript, Arguments = arguments }; @@ -244,7 +242,7 @@ await hook.OnResponseGenerated(new InstructResponseModel foreach (var hook in hooks) { await hook.BeforeCompletion(agent, message); - await hook.BeforeCodeExecution(agent, message, context); + await hook.BeforeCodeExecution(agent, context); // Interrupted by hook if (message.StopCompletion) @@ -260,9 +258,9 @@ await hook.OnResponseGenerated(new InstructResponseModel // Run code script var (useLock, useProcess, timeoutSeconds) = GetCodeExecutionConfig(codingSettings); using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds)); - var codeResponse = await codeProcessor.RunAsync(codeScript.Content, options: new() + var codeResponse = await codeProcessor.RunAsync(context.CodeScript?.Content ?? string.Empty, options: new() { - ScriptName = codeScript.Name, + ScriptName = context.CodeScript?.Name, Arguments = context.Arguments, UseLock = useLock, UseProcess = useProcess @@ -276,7 +274,7 @@ await hook.OnResponseGenerated(new InstructResponseModel response = new InstructResult { MessageId = message.MessageId, - Template = codeScript.Name, + Template = context.CodeScript?.Name, Text = codeResponse.Result }; @@ -285,21 +283,20 @@ await hook.OnResponseGenerated(new InstructResponseModel context.Arguments.ForEach(x => state.SetState(x.Key, x.Value, source: StateSource.External)); } + var codeExeResponse = new CodeExecutionResponseModel + { + CodeProcessor = codeProcessor.Provider, + CodeScript = context.CodeScript, + ExecutionResult = response.Text, + Text = message.Content, + Arguments = state.GetStates() + }; + // After code execution foreach (var hook in hooks) { await hook.AfterCompletion(agent, response); - await hook.AfterCodeExecution(agent, response); - await hook.OnResponseGenerated(new InstructResponseModel - { - AgentId = agent.Id, - Provider = codeProcessor.Provider, - Model = string.Empty, - TemplateName = codeScript.Name, - UserMessage = message.Content, - SystemInstruction = $"Code script name: {codeScript}, Version: {codeScript.UpdatedTime.ToString("o")}", - CompletionText = response.Text - }); + await hook.AfterCodeExecution(agent, codeExeResponse); } return response; diff --git a/src/Infrastructure/BotSharp.Logger/Hooks/InstructionLogHook.cs b/src/Infrastructure/BotSharp.Logger/Hooks/InstructionLogHook.cs index 484e82a86..17032223e 100644 --- a/src/Infrastructure/BotSharp.Logger/Hooks/InstructionLogHook.cs +++ b/src/Infrastructure/BotSharp.Logger/Hooks/InstructionLogHook.cs @@ -1,3 +1,5 @@ +using BotSharp.Abstraction.Coding.Models; +using BotSharp.Abstraction.Hooks; using BotSharp.Abstraction.Instructs.Models; using BotSharp.Abstraction.Instructs.Settings; using BotSharp.Abstraction.Loggers.Models; @@ -27,11 +29,7 @@ public InstructionLogHook( public override async Task OnResponseGenerated(InstructResponseModel response) { var settings = _services.GetRequiredService(); - if (response == null - || string.IsNullOrWhiteSpace(response.AgentId) - || settings == null - || !settings.Logging.Enabled - || settings.Logging.ExcludedAgentIds.Contains(response.AgentId)) + if (response == null || !IsLoggingEnabled(response.AgentId)) { return; } @@ -63,4 +61,43 @@ public override async Task OnResponseGenerated(InstructResponseModel response) await base.OnResponseGenerated(response); } + + public override async Task AfterCodeExecution(Agent agent, CodeExecutionResponseModel response) + { + if (response == null || IsLoggingEnabled(agent?.Id)) + { + return; + } + + var db = _services.GetRequiredService(); + var codeScriptVersion = response.CodeScript?.UpdatedTime ?? DateTime.UtcNow; + var user = db.GetUserById(_user.Id); + + db.SaveInstructionLogs(new List + { + new InstructionLogModel + { + AgentId = agent?.Id, + Provider = response.CodeProcessor, + Model = string.Empty, + TemplateName = response.CodeScript?.Name, + UserMessage = response.Text, + SystemInstruction = $"Code script name: {response.CodeScript}, Version: {codeScriptVersion.ToString("o")}", + CompletionText = response.ExecutionResult, + States = response.Arguments?.ToDictionary() ?? [], + UserId = user?.Id + } + }); + + await base.AfterCodeExecution(agent, response); + } + + private bool IsLoggingEnabled(string? agentId) + { + var settings = _services.GetRequiredService(); + return !string.IsNullOrWhiteSpace(agentId) + && settings != null + && settings.Logging.Enabled + && !settings.Logging.ExcludedAgentIds.Contains(agentId); + } } From f306616132c9a34931355930296092f3607bc240 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Tue, 11 Nov 2025 12:38:06 -0600 Subject: [PATCH 21/27] temp save --- .../Coding/Contexts/CodeExecutionContext.cs | 7 +++ .../Instructs/Contexts/CodeInstructContext.cs | 7 --- .../Instructs/IInstructHook.cs | 4 +- .../Instructs/InstructHookBase.cs | 4 +- .../BotSharp.Core.Rules/Engines/RuleEngine.cs | 49 ++++++++++++++----- .../Services/InstructService.Execute.cs | 11 +++-- .../Hooks/InstructionLogHook.cs | 3 +- 7 files changed, 55 insertions(+), 30 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Coding/Contexts/CodeExecutionContext.cs delete mode 100644 src/Infrastructure/BotSharp.Abstraction/Instructs/Contexts/CodeInstructContext.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Coding/Contexts/CodeExecutionContext.cs b/src/Infrastructure/BotSharp.Abstraction/Coding/Contexts/CodeExecutionContext.cs new file mode 100644 index 000000000..84f04455f --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Coding/Contexts/CodeExecutionContext.cs @@ -0,0 +1,7 @@ +namespace BotSharp.Abstraction.Coding.Contexts; + +public class CodeExecutionContext +{ + public AgentCodeScript CodeScript { get; set; } + public IEnumerable Arguments { get; set; } = []; +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Instructs/Contexts/CodeInstructContext.cs b/src/Infrastructure/BotSharp.Abstraction/Instructs/Contexts/CodeInstructContext.cs deleted file mode 100644 index 5d2573d5a..000000000 --- a/src/Infrastructure/BotSharp.Abstraction/Instructs/Contexts/CodeInstructContext.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace BotSharp.Abstraction.Instructs.Contexts; - -public class CodeInstructContext -{ - public AgentCodeScript CodeScript { get; set; } - public List Arguments { get; set; } = []; -} diff --git a/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructHook.cs b/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructHook.cs index 46fad6585..54fc80a90 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructHook.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructHook.cs @@ -1,6 +1,6 @@ +using BotSharp.Abstraction.Coding.Contexts; using BotSharp.Abstraction.Coding.Models; using BotSharp.Abstraction.Hooks; -using BotSharp.Abstraction.Instructs.Contexts; using BotSharp.Abstraction.Instructs.Models; namespace BotSharp.Abstraction.Instructs; @@ -11,6 +11,6 @@ public interface IInstructHook : IHookBase Task AfterCompletion(Agent agent, InstructResult result) => Task.CompletedTask; Task OnResponseGenerated(InstructResponseModel response) => Task.CompletedTask; - Task BeforeCodeExecution(Agent agent, CodeInstructContext context) => Task.CompletedTask; + Task BeforeCodeExecution(Agent agent, CodeExecutionContext context) => Task.CompletedTask; Task AfterCodeExecution(Agent agent, CodeExecutionResponseModel response) => Task.CompletedTask; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Instructs/InstructHookBase.cs b/src/Infrastructure/BotSharp.Abstraction/Instructs/InstructHookBase.cs index 4ce35b1f9..f5758434c 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Instructs/InstructHookBase.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Instructs/InstructHookBase.cs @@ -1,5 +1,5 @@ +using BotSharp.Abstraction.Coding.Contexts; using BotSharp.Abstraction.Coding.Models; -using BotSharp.Abstraction.Instructs.Contexts; using BotSharp.Abstraction.Instructs.Models; namespace BotSharp.Abstraction.Instructs; @@ -23,7 +23,7 @@ public virtual async Task OnResponseGenerated(InstructResponseModel response) await Task.CompletedTask; } - public virtual async Task BeforeCodeExecution(Agent agent, CodeInstructContext context) + public virtual async Task BeforeCodeExecution(Agent agent, CodeExecutionContext context) { await Task.CompletedTask; } diff --git a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs index de6bbc024..be6e1d02c 100644 --- a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs +++ b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs @@ -1,6 +1,8 @@ using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Coding; +using BotSharp.Abstraction.Coding.Contexts; using BotSharp.Abstraction.Coding.Enums; +using BotSharp.Abstraction.Coding.Models; using BotSharp.Abstraction.Coding.Settings; using BotSharp.Abstraction.Conversations; using BotSharp.Abstraction.Hooks; @@ -53,7 +55,7 @@ public async Task> Triggered(IRuleTrigger trigger, string te // Code trigger if (options != null) { - isTriggered = await TriggerCodeScript(agent.Id, trigger.Name, options); + isTriggered = await TriggerCodeScript(agent, trigger.Name, options); } if (isTriggered != null && !isTriggered.Value) @@ -96,9 +98,9 @@ await convService.SendMessage(agent.Id, } #region Private methods - private async Task TriggerCodeScript(string agentId, string triggerName, RuleTriggerOptions options) + private async Task TriggerCodeScript(Agent agent, string triggerName, RuleTriggerOptions options) { - if (string.IsNullOrWhiteSpace(agentId)) + if (string.IsNullOrWhiteSpace(agent?.Id)) { return false; } @@ -113,9 +115,9 @@ await convService.SendMessage(agent.Id, var agentService = _services.GetRequiredService(); var scriptName = options.CodeScriptName ?? $"{triggerName}_rule.py"; - var codeScript = await agentService.GetAgentCodeScript(agentId, scriptName, scriptType: AgentCodeScriptType.Src); + var codeScript = await agentService.GetAgentCodeScript(agent.Id, scriptName, scriptType: AgentCodeScriptType.Src); - var msg = $"rule trigger ({triggerName}) code script ({scriptName}) in agent ({agentId}) => args: {options.ArgumentContent?.RootElement.GetRawText()}."; + var msg = $"rule trigger ({triggerName}) code script ({scriptName}) in agent ({agent.Name}) => args: {options.ArgumentContent?.RootElement.GetRawText()}."; if (string.IsNullOrWhiteSpace(codeScript?.Content)) { @@ -125,7 +127,19 @@ await convService.SendMessage(agent.Id, try { - var hooks = _services.GetHooks(agentId); + var arguments = BuildArguments(options.ArgumentName, options.ArgumentContent); + + var hooks = _services.GetHooks(agent.Id); + var context = new CodeExecutionContext + { + CodeScript = codeScript, + Arguments = arguments + }; + + foreach (var hook in hooks) + { + await hook.BeforeCodeExecution(agent, context); + } var (useLock, useProcess, timeoutSeconds) = GetCodeExecutionConfig(); using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds)); @@ -137,8 +151,18 @@ await convService.SendMessage(agent.Id, UseProcess = useProcess }, cancellationToken: cts.Token); - + var codeResponse = new CodeExecutionResponseModel + { + CodeProcessor = processor.Provider, + CodeScript = codeScript, + Arguments = arguments.DistinctBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value ?? string.Empty), + ExecutionResult = response.Result.IfNullOrEmptyAs(response.ErrorMsg ?? string.Empty)! + }; + foreach (var hook in hooks) + { + await hook.AfterCodeExecution(agent, codeResponse); + } if (response == null || !response.Success) { @@ -181,13 +205,12 @@ private IEnumerable BuildArguments(string? name, JsonDocument? args) private (bool, bool, int) GetCodeExecutionConfig() { - var useLock = false; - var useProcess = false; - var timeoutSeconds = 3; + var codeExecution = _codingSettings.CodeExecution; + var defaultTimeoutSeconds = 3; - useLock = _codingSettings.CodeExecution?.UseLock ?? useLock; - useProcess = _codingSettings.CodeExecution?.UseProcess ?? useProcess; - timeoutSeconds = _codingSettings.CodeExecution?.TimeoutSeconds > 0 ? _codingSettings.CodeExecution.TimeoutSeconds.Value : timeoutSeconds; + var useLock = codeExecution?.UseLock ?? false; + var useProcess = codeExecution?.UseProcess ?? false; + var timeoutSeconds = codeExecution?.TimeoutSeconds > 0 ? codeExecution.TimeoutSeconds : defaultTimeoutSeconds; return (useLock, useProcess, timeoutSeconds); } diff --git a/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs b/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs index a22fae26f..8ed9cb9bb 100644 --- a/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs +++ b/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs @@ -1,9 +1,9 @@ using BotSharp.Abstraction.Coding; using BotSharp.Abstraction.Coding.Enums; +using BotSharp.Abstraction.Coding.Contexts; using BotSharp.Abstraction.Files.Options; using BotSharp.Abstraction.Files.Proccessors; using BotSharp.Abstraction.Instructs; -using BotSharp.Abstraction.Instructs.Contexts; using BotSharp.Abstraction.Instructs.Models; using BotSharp.Abstraction.Instructs.Options; using BotSharp.Abstraction.MLTasks; @@ -232,7 +232,7 @@ await hook.OnResponseGenerated(new InstructResponseModel arguments = state.GetStates().Select(x => new KeyValue(x.Key, x.Value)).ToList(); } - var context = new CodeInstructContext + var context = new CodeExecutionContext { CodeScript = codeScript, Arguments = arguments @@ -280,14 +280,17 @@ await hook.OnResponseGenerated(new InstructResponseModel if (context?.Arguments != null) { - context.Arguments.ForEach(x => state.SetState(x.Key, x.Value, source: StateSource.External)); + foreach (var arg in context.Arguments) + { + state.SetState(arg.Key, arg.Value, source: StateSource.External); + } } var codeExeResponse = new CodeExecutionResponseModel { CodeProcessor = codeProcessor.Provider, CodeScript = context.CodeScript, - ExecutionResult = response.Text, + ExecutionResult = codeResponse.Result.IfNullOrEmptyAs(codeResponse.ErrorMsg ?? string.Empty)!, Text = message.Content, Arguments = state.GetStates() }; diff --git a/src/Infrastructure/BotSharp.Logger/Hooks/InstructionLogHook.cs b/src/Infrastructure/BotSharp.Logger/Hooks/InstructionLogHook.cs index 17032223e..1b2b43748 100644 --- a/src/Infrastructure/BotSharp.Logger/Hooks/InstructionLogHook.cs +++ b/src/Infrastructure/BotSharp.Logger/Hooks/InstructionLogHook.cs @@ -1,5 +1,4 @@ using BotSharp.Abstraction.Coding.Models; -using BotSharp.Abstraction.Hooks; using BotSharp.Abstraction.Instructs.Models; using BotSharp.Abstraction.Instructs.Settings; using BotSharp.Abstraction.Loggers.Models; @@ -64,7 +63,7 @@ public override async Task OnResponseGenerated(InstructResponseModel response) public override async Task AfterCodeExecution(Agent agent, CodeExecutionResponseModel response) { - if (response == null || IsLoggingEnabled(agent?.Id)) + if (response == null || !IsLoggingEnabled(agent?.Id)) { return; } From 84d1c77c9f8abc4472a22182e5811c3b07c0627f Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Tue, 11 Nov 2025 13:47:18 -0600 Subject: [PATCH 22/27] refine file knowledge options --- .../Coding/Responses/CodeInterpretResponse.cs | 2 +- .../Files/Options/FileHandleOptions.cs | 22 +----------- .../Files/Proccessors/IFileProcessor.cs | 2 +- .../Options/FileKnowledgeHandleOptions.cs | 34 +++++++++++++++++++ .../Options/FileKnowledgeProcessOptions.cs | 5 --- .../Knowledges/Options/KnowledgeDocOptions.cs | 2 +- .../Responses/FileKnowledgeResponse.cs | 2 +- .../Services/InstructService.Execute.cs | 20 +++++------ .../Services/KnowledgeService.Document.cs | 2 +- 9 files changed, 49 insertions(+), 42 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Knowledges/Options/FileKnowledgeHandleOptions.cs delete mode 100644 src/Infrastructure/BotSharp.Abstraction/Knowledges/Options/FileKnowledgeProcessOptions.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Coding/Responses/CodeInterpretResponse.cs b/src/Infrastructure/BotSharp.Abstraction/Coding/Responses/CodeInterpretResponse.cs index 405028699..8d41f9853 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Coding/Responses/CodeInterpretResponse.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Coding/Responses/CodeInterpretResponse.cs @@ -6,6 +6,6 @@ public class CodeInterpretResponse : ResponseBase public override string ToString() { - return Result.IfNullOrEmptyAs(ErrorMsg ?? $"Success: {Success}")!; + return Result.IfNullOrEmptyAs(ErrorMsg.IfNullOrEmptyAs($"Success: {Success}"))!; } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Files/Options/FileHandleOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Files/Options/FileHandleOptions.cs index 31a2ab46c..60b3ffc34 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Files/Options/FileHandleOptions.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Files/Options/FileHandleOptions.cs @@ -1,27 +1,7 @@ namespace BotSharp.Abstraction.Files.Options; -public class FileHandleOptions +public class FileHandleOptions : LlmConfigBase { - /// - /// Llm provider - /// - public string? Provider { get; set; } - - /// - /// llm model - /// - public string? Model { get; set; } - - /// - /// Llm maximum output tokens - /// - public int? MaxOutputTokens { get; set; } - - /// - /// Reasoning effort level - /// - public string? ReasoningEfforLevel { get; set; } - /// /// Instruction /// diff --git a/src/Infrastructure/BotSharp.Abstraction/Files/Proccessors/IFileProcessor.cs b/src/Infrastructure/BotSharp.Abstraction/Files/Proccessors/IFileProcessor.cs index cebc03e3b..fbcced77a 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Files/Proccessors/IFileProcessor.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Files/Proccessors/IFileProcessor.cs @@ -12,6 +12,6 @@ public interface IFileProcessor Task HandleFilesAsync(Agent agent, string text, IEnumerable files, FileHandleOptions? options = null) => throw new NotImplementedException(); - Task GetFileKnowledgeAsync(FileBinaryDataModel file, FileKnowledgeProcessOptions? options = null) + Task GetFileKnowledgeAsync(FileBinaryDataModel file, FileKnowledgeHandleOptions? options = null) => throw new NotImplementedException(); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Knowledges/Options/FileKnowledgeHandleOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Options/FileKnowledgeHandleOptions.cs new file mode 100644 index 000000000..9b891ab28 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Options/FileKnowledgeHandleOptions.cs @@ -0,0 +1,34 @@ +namespace BotSharp.Abstraction.Knowledges.Options; + +public class FileKnowledgeHandleOptions : LlmConfigBase +{ + /// + /// Agent id + /// + public string? AgentId { get; set; } + + /// + /// Instruction + /// + public string? Instruction { get; set; } + + /// + /// Message from user + /// + public string? UserMessage { get; set; } + + /// + /// Template name in Agent + /// + public string? TemplateName { get; set; } + + /// + /// The upstream where the file llm is invoked + /// + public string? InvokeFrom { get; set; } + + /// + /// Data that is used to render instruction + /// + public Dictionary? Data { get; set; } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Knowledges/Options/FileKnowledgeProcessOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Options/FileKnowledgeProcessOptions.cs deleted file mode 100644 index f093ddedc..000000000 --- a/src/Infrastructure/BotSharp.Abstraction/Knowledges/Options/FileKnowledgeProcessOptions.cs +++ /dev/null @@ -1,5 +0,0 @@ -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 index 826b47e68..52c123426 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Knowledges/Options/KnowledgeDocOptions.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Options/KnowledgeDocOptions.cs @@ -1,6 +1,6 @@ namespace BotSharp.Abstraction.Knowledges.Options; -public class KnowledgeDocOptions +public class KnowledgeDocOptions : FileKnowledgeHandleOptions { 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 index e0fdea716..27b5d6967 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Knowledges/Responses/FileKnowledgeResponse.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Responses/FileKnowledgeResponse.cs @@ -1,6 +1,6 @@ namespace BotSharp.Abstraction.Knowledges.Responses; -public class FileKnowledgeResponse +public class FileKnowledgeResponse : ResponseBase { public IEnumerable Knowledges { get; set; } = []; } diff --git a/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs b/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs index 8ed9cb9bb..14388a655 100644 --- a/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs +++ b/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs @@ -266,18 +266,16 @@ await hook.OnResponseGenerated(new InstructResponseModel UseProcess = useProcess }, cancellationToken: cts.Token); - if (codeResponse == null || !codeResponse.Success) + if (codeResponse?.Success == true) { - return response; + response = new InstructResult + { + MessageId = message.MessageId, + Template = context.CodeScript?.Name, + Text = codeResponse.Result + }; } - response = new InstructResult - { - MessageId = message.MessageId, - Template = context.CodeScript?.Name, - Text = codeResponse.Result - }; - if (context?.Arguments != null) { foreach (var arg in context.Arguments) @@ -290,7 +288,7 @@ await hook.OnResponseGenerated(new InstructResponseModel { CodeProcessor = codeProcessor.Provider, CodeScript = context.CodeScript, - ExecutionResult = codeResponse.Result.IfNullOrEmptyAs(codeResponse.ErrorMsg ?? string.Empty)!, + ExecutionResult = codeResponse?.ToString() ?? string.Empty, Text = message.Content, Arguments = state.GetStates() }; @@ -298,7 +296,7 @@ await hook.OnResponseGenerated(new InstructResponseModel // After code execution foreach (var hook in hooks) { - await hook.AfterCompletion(agent, response); + await hook.AfterCompletion(agent, response ?? new()); await hook.AfterCodeExecution(agent, codeExeResponse); } diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Document.cs b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Document.cs index fd07c9f99..24fc5546b 100644 --- a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Document.cs +++ b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Document.cs @@ -375,7 +375,7 @@ private async Task> GetFileKnowledge(FileBinaryD return Enumerable.Empty(); } - var response = await processor.GetFileKnowledgeAsync(file, options: new() { }); + var response = await processor.GetFileKnowledgeAsync(file, options: options); return response?.Knowledges ?? []; } From 6c71ddf2e1e6c343a9fe241a3c5615464fe5ee6d Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Tue, 11 Nov 2025 13:49:37 -0600 Subject: [PATCH 23/27] minor change --- src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs index be6e1d02c..2f68fd108 100644 --- a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs +++ b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs @@ -156,7 +156,7 @@ await convService.SendMessage(agent.Id, CodeProcessor = processor.Provider, CodeScript = codeScript, Arguments = arguments.DistinctBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value ?? string.Empty), - ExecutionResult = response.Result.IfNullOrEmptyAs(response.ErrorMsg ?? string.Empty)! + ExecutionResult = response?.ToString() ?? string.Empty }; foreach (var hook in hooks) @@ -183,7 +183,7 @@ await convService.SendMessage(agent.Id, result = false; } - _logger.Log(logLevel, $"Code script execution result ({response.Result}) from {msg}"); + _logger.Log(logLevel, $"Code script execution result ({response}) from {msg}"); return result; } catch (Exception ex) From 5ce5aedaa1f48e2654ef232e90e198da834daea8 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Tue, 11 Nov 2025 16:50:29 -0600 Subject: [PATCH 24/27] add renew token --- .../Users/IAuthenticationHook.cs | 8 + .../Users/IUserService.cs | 2 +- .../Users/Services/UserService.Token.cs | 388 ++++++++++++++++++ .../Users/Services/UserService.cs | 297 +------------- .../Controllers/User/UserController.cs | 17 + 5 files changed, 415 insertions(+), 297 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Core/Users/Services/UserService.Token.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/IAuthenticationHook.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IAuthenticationHook.cs index a8403dfa1..c0eb77a15 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/IAuthenticationHook.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Users/IAuthenticationHook.cs @@ -15,6 +15,14 @@ public interface IAuthenticationHook Task Authenticate(string id, string password) => Task.FromResult(new User()); + /// + /// Renew token for authentication + /// + /// + /// + Task RenewAuthentication(string token) + => Task.FromResult((User?)null); + /// /// Add extra claims to user /// diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs index d03687ffa..20a4bf638 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs @@ -19,8 +19,8 @@ public interface IUserService Task GetAffiliateToken(string authorization); Task GetAdminToken(string authorization); Task GetToken(string authorization); + Task RenewToken(string token); Task CreateTokenByUser(User user); - Task RenewToken(); Task GetMyProfile(); Task VerifyUserNameExisting(string userName); Task VerifyEmailExisting(string email); diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.Token.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.Token.cs new file mode 100644 index 000000000..d75dc7d13 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.Token.cs @@ -0,0 +1,388 @@ +using BotSharp.Abstraction.Infrastructures; +using BotSharp.Abstraction.Users.Enums; +using BotSharp.Abstraction.Users.Models; +using BotSharp.OpenAPI.ViewModels.Users; +using Microsoft.Extensions.Configuration; +using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; + +namespace BotSharp.Core.Users.Services; + +public partial class UserService +{ + public async Task GetToken(string authorization) + { + var base64 = Encoding.UTF8.GetString(Convert.FromBase64String(authorization)); + var (id, password, regionCode) = base64.SplitAsTuple(":"); + + var db = _services.GetRequiredService(); + var record = id.Contains("@") ? db.GetUserByEmail(id) : db.GetUserByUserName(id); + if (record == null) + { + record = db.GetUserByPhone(id, regionCode: regionCode); + } + + if (record != null && record.Type == UserType.Affiliate) + { + return default; + } + + var hooks = _services.GetServices(); + //verify password is correct or not. + if (record != null && !hooks.Any()) + { + var hashPassword = Utilities.HashTextMd5($"{password}{record.Salt}"); + if (hashPassword != record.Password) + { + return default; + } + } + + User? user = record; + var isAuthenticatedByHook = false; + if (record == null || record.Source != UserSource.Internal) + { + // check 3rd party user + foreach (var hook in hooks) + { + user = await hook.Authenticate(id, password); + if (user == null) + { + continue; + } + + if (string.IsNullOrEmpty(user.Source) || user.Source == UserSource.Internal) + { + _logger.LogError($"Please set source name in the Authenticate hook."); + return null; + } + + if (record == null) + { + // create a local user record + record = new User + { + UserName = user.UserName, + Email = user.Email, + FirstName = user.FirstName, + LastName = user.LastName, + Source = user.Source, + ExternalId = user.ExternalId, + Password = user.Password, + Type = user.Type, + Role = user.Role, + RegionCode = user.RegionCode + }; + await CreateUser(record); + } + + isAuthenticatedByHook = true; + break; + } + } + + if ((hooks.Any() && user == null) || record == null) + { + return default; + } + + if (!isAuthenticatedByHook && _setting.NewUserVerification && !record.Verified) + { + return default; + } + + if (!isAuthenticatedByHook && Utilities.HashTextMd5($"{password}{record.Salt}") != record.Password) + { + return default; + } + + var (token, jwt) = BuildToken(record); + foreach (var hook in hooks) + { + hook.UserAuthenticated(record, token); + } + + return token; + } + + public async Task RenewToken(string token) + { + if (string.IsNullOrWhiteSpace(token)) + { + var (newToken, _) = BuildToken(await GetMyProfile()); + return newToken; + } + + // Allow "Bearer {token}" input + if (token.Contains(' ')) + { + token = token.Split(' ', StringSplitOptions.RemoveEmptyEntries).Last(); + } + + try + { + User? user = null; + var tokenHandler = new JwtSecurityTokenHandler(); + + var hooks = _services.GetServices(); + foreach (var hook in hooks) + { + user = await hook.RenewAuthentication(token); + if (user != null) + { + break; + } + } + + if (user == null) + { + // Validate the incoming JWT (signature, issuer, audience, lifetime) + var config = _services.GetRequiredService(); + var validationParameters = new TokenValidationParameters + { + IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(config["Jwt:Key"])), + ValidateIssuerSigningKey = true, + ValidateIssuer = false, + ValidateAudience = false, + ValidateLifetime = false, + ClockSkew = TimeSpan.Zero + }; + + var principal = tokenHandler.ValidateToken(token, validationParameters, out var validatedToken); + var userId = principal?.Claims? + .FirstOrDefault(x => x.Type.IsEqualTo(JwtRegisteredClaimNames.NameId) + || x.Type.IsEqualTo(ClaimTypes.NameIdentifier) + || x.Type.IsEqualTo("uid") + || x.Type.IsEqualTo("user_id") + || x.Type.IsEqualTo("userId"))?.Value; + + if (string.IsNullOrEmpty(userId)) + { + return null; + } + + user = await GetUser(userId); + if (user == null + || user.IsDisabled + || (user.Id != userId && user.ExternalId != userId)) + { + return null; + } + } + + // Issue a new access token + var (newToken, _) = BuildToken(user); + + // Notify hooks for token issuance + foreach (var hook in hooks) + { + hook.UserAuthenticated(user, newToken); + } + + return newToken; + } + catch (SecurityTokenException ex) + { + _logger.LogWarning(ex, "Invalid token presented for refresh."); + return null; + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to refresh token."); + return null; + } + } + + public async Task ActiveUser(UserActivationModel model) + { + var id = model.UserName; + var db = _services.GetRequiredService(); + var record = id.Contains("@") ? db.GetUserByEmail(id) : db.GetUserByUserName(id); + + if (record == null) + { + record = db.GetUserByPhone(id, regionCode: (string.IsNullOrWhiteSpace(model.RegionCode) ? "CN" : model.RegionCode)); + } + + //if (record == null) + //{ + // record = db.GetUserByPhoneV2(id, regionCode: (string.IsNullOrWhiteSpace(model.RegionCode) ? "CN" : model.RegionCode)); + //} + + if (record == null) + { + return default; + } + + if (record.VerificationCode != model.VerificationCode || (record.VerificationCodeExpireAt != null && DateTime.UtcNow > record.VerificationCodeExpireAt)) + { + return default; + } + + if (record.Verified) + { + return default; + } + + db.UpdateUserVerified(record.Id); + + var accessToken = GenerateJwtToken(record); + var jwt = new JwtSecurityTokenHandler().ReadJwtToken(accessToken); + var token = new Token + { + AccessToken = accessToken, + ExpireTime = jwt.Payload.Exp.Value, + TokenType = "Bearer", + Scope = "api" + }; + return token; + } + + public async Task CreateTokenByUser(User user) + { + var accessToken = GenerateJwtToken(user); + var jwt = new JwtSecurityTokenHandler().ReadJwtToken(accessToken); + var token = new Token + { + AccessToken = accessToken, + ExpireTime = jwt.Payload.Exp.Value, + TokenType = "Bearer", + Scope = "api" + }; + return token; + } + + public async Task GetAffiliateToken(string authorization) + { + var base64 = Encoding.UTF8.GetString(Convert.FromBase64String(authorization)); + var (id, password, regionCode) = base64.SplitAsTuple(":"); + var db = _services.GetRequiredService(); + var record = db.GetAffiliateUserByPhone(id); + var isCanLogin = record != null && !record.IsDisabled && record.Type == UserType.Affiliate; + if (!isCanLogin) + { + return default; + } + + if (Utilities.HashTextMd5($"{password}{record.Salt}") != record.Password) + { + return default; + } + + var (token, jwt) = BuildToken(record); + + return await Task.FromResult(token); + } + + public async Task GetAdminToken(string authorization) + { + var base64 = Encoding.UTF8.GetString(Convert.FromBase64String(authorization)); + var (id, password, regionCode) = base64.SplitAsTuple(":"); + var db = _services.GetRequiredService(); + var record = db.GetUserByPhone(id, type: UserType.Internal); + var isCanLogin = record != null && !record.IsDisabled + && record.Type == UserType.Internal && new List + { + UserRole.Root,UserRole.Admin + }.Contains(record.Role); + if (!isCanLogin) + { + return default; + } + + if (Utilities.HashTextMd5($"{password}{record.Salt}") != record.Password) + { + return default; + } + + var (token, jwt) = BuildToken(record); + + return await Task.FromResult(token); + } + + public async Task GetUserTokenExpires() + { + var _cacheService = _services.GetRequiredService(); + return await _cacheService.GetAsync(GetUserTokenExpiresCacheKey(_user.Id)); + } + + #region Private methods + private (Token, JwtSecurityToken) BuildToken(User record) + { + var accessToken = GenerateJwtToken(record); + var jwt = new JwtSecurityTokenHandler().ReadJwtToken(accessToken); + var token = new Token + { + AccessToken = accessToken, + ExpireTime = jwt.Payload.Exp.Value, + TokenType = "Bearer", + Scope = "api" + }; + return (token, jwt); + } + + private string GenerateJwtToken(User user) + { + var claims = new List + { + new Claim(JwtRegisteredClaimNames.NameId, user.Id), + new Claim(JwtRegisteredClaimNames.UniqueName, user.UserName), + new Claim(JwtRegisteredClaimNames.Email, user?.Email ?? string.Empty), + new Claim(JwtRegisteredClaimNames.GivenName, user?.FirstName ?? string.Empty), + new Claim(JwtRegisteredClaimNames.FamilyName, user?.LastName ?? string.Empty), + new Claim("source", user.Source), + new Claim("external_id", user.ExternalId ?? string.Empty), + new Claim("type", user.Type ?? UserType.Client), + new Claim("role", user.Role ?? UserRole.User), + new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), + new Claim("phone", user.Phone ?? string.Empty), + new Claim("affiliate_id", user.AffiliateId ?? string.Empty), + new Claim("employee_id", user.EmployeeId ?? string.Empty), + new Claim("regionCode", user.RegionCode ?? "CN") + }; + + var validators = _services.GetServices(); + foreach (var validator in validators) + { + validator.AddClaims(claims); + } + + var config = _services.GetRequiredService(); + var issuer = config["Jwt:Issuer"]; + var audience = config["Jwt:Audience"]; + var expireInMinutes = int.Parse(config["Jwt:ExpireInMinutes"] ?? "120"); + var key = Encoding.ASCII.GetBytes(config["Jwt:Key"]); + var expires = DateTime.UtcNow.AddMinutes(expireInMinutes); + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims), + Expires = expires, + Issuer = issuer, + Audience = audience, + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), + SecurityAlgorithms.HmacSha256Signature) + }; + var tokenHandler = new JwtSecurityTokenHandler(); + var token = tokenHandler.CreateToken(tokenDescriptor); + SaveUserTokenExpiresCache(user.Id, expires, expireInMinutes).GetAwaiter().GetResult(); + return tokenHandler.WriteToken(token); + } + + private async Task SaveUserTokenExpiresCache(string userId, DateTime expires, int expireInMinutes) + { + var config = _services.GetService(); + var enableSingleLogin = bool.Parse(config["Jwt:EnableSingleLogin"] ?? "false"); + if (enableSingleLogin) + { + var _cacheService = _services.GetRequiredService(); + await _cacheService.SetAsync(GetUserTokenExpiresCacheKey(userId), expires, TimeSpan.FromMinutes(expireInMinutes)); + } + } + + private string GetUserTokenExpiresCacheKey(string userId) + { + return $"user:{userId}_token_expires"; + } + #endregion +} diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs index f7dd61c63..51b720b89 100644 --- a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs +++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs @@ -12,7 +12,7 @@ namespace BotSharp.Core.Users.Services; -public class UserService : IUserService +public partial class UserService : IUserService { private readonly IServiceProvider _services; private readonly IUserIdentity _user; @@ -156,232 +156,6 @@ public async Task UpdatePassword(string password, string verificationCode) return true; } - public async Task GetAffiliateToken(string authorization) - { - var base64 = Encoding.UTF8.GetString(Convert.FromBase64String(authorization)); - var (id, password, regionCode) = base64.SplitAsTuple(":"); - var db = _services.GetRequiredService(); - var record = db.GetAffiliateUserByPhone(id); - var isCanLogin = record != null && !record.IsDisabled && record.Type == UserType.Affiliate; - if (!isCanLogin) - { - return default; - } - - if (Utilities.HashTextMd5($"{password}{record.Salt}") != record.Password) - { - return default; - } - - var (token, jwt) = BuildToken(record); - - return await Task.FromResult(token); - } - - public async Task GetAdminToken(string authorization) - { - var base64 = Encoding.UTF8.GetString(Convert.FromBase64String(authorization)); - var (id, password, regionCode) = base64.SplitAsTuple(":"); - var db = _services.GetRequiredService(); - var record = db.GetUserByPhone(id, type: UserType.Internal); - var isCanLogin = record != null && !record.IsDisabled - && record.Type == UserType.Internal && new List - { - UserRole.Root,UserRole.Admin - }.Contains(record.Role); - if (!isCanLogin) - { - return default; - } - - if (Utilities.HashTextMd5($"{password}{record.Salt}") != record.Password) - { - return default; - } - - var (token, jwt) = BuildToken(record); - - return await Task.FromResult(token); - } - - private (Token, JwtSecurityToken) BuildToken(User record) - { - var accessToken = GenerateJwtToken(record); - var jwt = new JwtSecurityTokenHandler().ReadJwtToken(accessToken); - var token = new Token - { - AccessToken = accessToken, - ExpireTime = jwt.Payload.Exp.Value, - TokenType = "Bearer", - Scope = "api" - }; - return (token, jwt); - } - - public async Task GetToken(string authorization) - { - var base64 = Encoding.UTF8.GetString(Convert.FromBase64String(authorization)); - var (id, password, regionCode) = base64.SplitAsTuple(":"); - - var db = _services.GetRequiredService(); - var record = id.Contains("@") ? db.GetUserByEmail(id) : db.GetUserByUserName(id); - if (record == null) - { - record = db.GetUserByPhone(id, regionCode: regionCode); - } - - if (record != null && record.Type == UserType.Affiliate) - { - return default; - } - - var hooks = _services.GetServices(); - //verify password is correct or not. - if (record != null && !hooks.Any()) - { - var hashPassword = Utilities.HashTextMd5($"{password}{record.Salt}"); - if (hashPassword != record.Password) - { - return default; - } - } - - User? user = record; - var isAuthenticatedByHook = false; - if (record == null || record.Source != UserSource.Internal) - { - // check 3rd party user - foreach (var hook in hooks) - { - user = await hook.Authenticate(id, password); - if (user == null) - { - continue; - } - - if (string.IsNullOrEmpty(user.Source) || user.Source == UserSource.Internal) - { - _logger.LogError($"Please set source name in the Authenticate hook."); - return null; - } - - if (record == null) - { - // create a local user record - record = new User - { - UserName = user.UserName, - Email = user.Email, - FirstName = user.FirstName, - LastName = user.LastName, - Source = user.Source, - ExternalId = user.ExternalId, - Password = user.Password, - Type = user.Type, - Role = user.Role, - RegionCode = user.RegionCode - }; - await CreateUser(record); - } - - isAuthenticatedByHook = true; - break; - } - } - - if ((hooks.Any() && user == null) || record == null) - { - return default; - } - - if (!isAuthenticatedByHook && _setting.NewUserVerification && !record.Verified) - { - return default; - } - - if (!isAuthenticatedByHook && Utilities.HashTextMd5($"{password}{record.Salt}") != record.Password) - { - return default; - } - - var (token, jwt) = BuildToken(record); - foreach (var hook in hooks) - { - hook.UserAuthenticated(record, token); - } - - return token; - } - - private string GenerateJwtToken(User user) - { - var claims = new List - { - new Claim(JwtRegisteredClaimNames.NameId, user.Id), - new Claim(JwtRegisteredClaimNames.UniqueName, user.UserName), - new Claim(JwtRegisteredClaimNames.Email, user?.Email ?? string.Empty), - new Claim(JwtRegisteredClaimNames.GivenName, user?.FirstName ?? string.Empty), - new Claim(JwtRegisteredClaimNames.FamilyName, user?.LastName ?? string.Empty), - new Claim("source", user.Source), - new Claim("external_id", user.ExternalId ?? string.Empty), - new Claim("type", user.Type ?? UserType.Client), - new Claim("role", user.Role ?? UserRole.User), - new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), - new Claim("phone", user.Phone ?? string.Empty), - new Claim("affiliate_id", user.AffiliateId ?? string.Empty), - new Claim("employee_id", user.EmployeeId ?? string.Empty), - new Claim("regionCode", user.RegionCode ?? "CN") - }; - - var validators = _services.GetServices(); - foreach (var validator in validators) - { - validator.AddClaims(claims); - } - - var config = _services.GetRequiredService(); - var issuer = config["Jwt:Issuer"]; - var audience = config["Jwt:Audience"]; - var expireInMinutes = int.Parse(config["Jwt:ExpireInMinutes"] ?? "120"); - var key = Encoding.ASCII.GetBytes(config["Jwt:Key"]); - var expires = DateTime.UtcNow.AddMinutes(expireInMinutes); - var tokenDescriptor = new SecurityTokenDescriptor - { - Subject = new ClaimsIdentity(claims), - Expires = expires, - Issuer = issuer, - Audience = audience, - SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), - SecurityAlgorithms.HmacSha256Signature) - }; - var tokenHandler = new JwtSecurityTokenHandler(); - var token = tokenHandler.CreateToken(tokenDescriptor); - SaveUserTokenExpiresCache(user.Id, expires, expireInMinutes).GetAwaiter().GetResult(); - return tokenHandler.WriteToken(token); - } - - private async Task SaveUserTokenExpiresCache(string userId, DateTime expires, int expireInMinutes) - { - var config = _services.GetService(); - var enableSingleLogin = bool.Parse(config["Jwt:EnableSingleLogin"] ?? "false"); - if (enableSingleLogin) - { - var _cacheService = _services.GetRequiredService(); - await _cacheService.SetAsync(GetUserTokenExpiresCacheKey(userId), expires, TimeSpan.FromMinutes(expireInMinutes)); - } - } - - private string GetUserTokenExpiresCacheKey(string userId) - { - return $"user:{userId}_token_expires"; - } - - public async Task GetUserTokenExpires() - { - var _cacheService = _services.GetRequiredService(); - return await _cacheService.GetAsync(GetUserTokenExpiresCacheKey(_user.Id)); - } - [SharpCache(10, perInstanceCache: true)] public async Task GetMyProfile() { @@ -486,75 +260,6 @@ public async Task UpdateUser(User user, bool isUpdateUserAgents = false) return db.UpdateUser(user, isUpdateUserAgents); } - public async Task ActiveUser(UserActivationModel model) - { - var id = model.UserName; - var db = _services.GetRequiredService(); - var record = id.Contains("@") ? db.GetUserByEmail(id) : db.GetUserByUserName(id); - - if (record == null) - { - record = db.GetUserByPhone(id, regionCode: (string.IsNullOrWhiteSpace(model.RegionCode) ? "CN" : model.RegionCode)); - } - - //if (record == null) - //{ - // record = db.GetUserByPhoneV2(id, regionCode: (string.IsNullOrWhiteSpace(model.RegionCode) ? "CN" : model.RegionCode)); - //} - - if (record == null) - { - return default; - } - - if (record.VerificationCode != model.VerificationCode || (record.VerificationCodeExpireAt != null && DateTime.UtcNow > record.VerificationCodeExpireAt)) - { - return default; - } - - if (record.Verified) - { - return default; - } - - db.UpdateUserVerified(record.Id); - - var accessToken = GenerateJwtToken(record); - var jwt = new JwtSecurityTokenHandler().ReadJwtToken(accessToken); - var token = new Token - { - AccessToken = accessToken, - ExpireTime = jwt.Payload.Exp.Value, - TokenType = "Bearer", - Scope = "api" - }; - return token; - } - - public async Task CreateTokenByUser(User user) - { - var accessToken = GenerateJwtToken(user); - var jwt = new JwtSecurityTokenHandler().ReadJwtToken(accessToken); - var token = new Token - { - AccessToken = accessToken, - ExpireTime = jwt.Payload.Exp.Value, - TokenType = "Bearer", - Scope = "api" - }; - return token; - } - - public async Task RenewToken() - { - var newToken = GenerateJwtToken(await GetMyProfile()); - var newJwt = new JwtSecurityTokenHandler().ReadJwtToken(newToken); - Token token = new Token(); - token.AccessToken = newToken; - token.ExpireTime = newJwt.Payload.Exp.Value; - return token; - } - public async Task VerifyUserNameExisting(string userName) { if (string.IsNullOrEmpty(userName)) diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/User/UserController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/User/UserController.cs index 24b34cbf2..e8b3ed6c3 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/User/UserController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/User/UserController.cs @@ -46,6 +46,23 @@ public async Task> GetToken([FromHeader(Name = "Authorizatio return Ok(token); } + [AllowAnonymous] + [HttpPost("/renew-token")] + public async Task> RenewToken([FromHeader(Name = "Authorization")][Required] string authcode) + { + if (authcode.Contains(' ')) + { + authcode = authcode.Split(' ')[1]; + } + + var token = await _userService.RenewToken(authcode); + if (token == null) + { + return Unauthorized(); + } + return Ok(token); + } + [AllowAnonymous] [HttpGet("/sso/{provider}")] public async Task Authorize([FromRoute] string provider, string redirectUrl) From 12ef4cdb3e131df7a0977875ccf5e9bbcc477599 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Tue, 11 Nov 2025 17:02:55 -0600 Subject: [PATCH 25/27] minor change --- .../BotSharp.Core/Users/Services/UserService.Token.cs | 2 +- .../Controllers/User/UserController.cs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.Token.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.Token.cs index d75dc7d13..0d3631269 100644 --- a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.Token.cs +++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.Token.cs @@ -123,7 +123,6 @@ record = db.GetUserByPhone(id, regionCode: regionCode); try { User? user = null; - var tokenHandler = new JwtSecurityTokenHandler(); var hooks = _services.GetServices(); foreach (var hook in hooks) @@ -149,6 +148,7 @@ record = db.GetUserByPhone(id, regionCode: regionCode); ClockSkew = TimeSpan.Zero }; + var tokenHandler = new JwtSecurityTokenHandler(); var principal = tokenHandler.ValidateToken(token, validationParameters, out var validatedToken); var userId = principal?.Claims? .FirstOrDefault(x => x.Type.IsEqualTo(JwtRegisteredClaimNames.NameId) diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/User/UserController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/User/UserController.cs index e8b3ed6c3..f1a843d2f 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/User/UserController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/User/UserController.cs @@ -34,7 +34,7 @@ public async Task> GetToken([FromHeader(Name = "Authorizatio { if (authcode.Contains(' ')) { - authcode = authcode.Split(' ')[1]; + authcode = authcode.Split(' ', StringSplitOptions.RemoveEmptyEntries).Last(); } var token = await _userService.GetToken(authcode); @@ -48,14 +48,14 @@ public async Task> GetToken([FromHeader(Name = "Authorizatio [AllowAnonymous] [HttpPost("/renew-token")] - public async Task> RenewToken([FromHeader(Name = "Authorization")][Required] string authcode) + public async Task> RenewToken([FromHeader(Name = "Authorization")][Required] string accessToken) { - if (authcode.Contains(' ')) + if (accessToken.Contains(' ')) { - authcode = authcode.Split(' ')[1]; + accessToken = accessToken.Split(' ', StringSplitOptions.RemoveEmptyEntries).Last(); } - var token = await _userService.RenewToken(authcode); + var token = await _userService.RenewToken(accessToken); if (token == null) { return Unauthorized(); From 31ac06920426a12c80f2210973fcbda09de6c260 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Tue, 11 Nov 2025 17:04:24 -0600 Subject: [PATCH 26/27] rename --- .../Controllers/User/UserController.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/User/UserController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/User/UserController.cs index f1a843d2f..ebc18d53d 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/User/UserController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/User/UserController.cs @@ -48,19 +48,19 @@ public async Task> GetToken([FromHeader(Name = "Authorizatio [AllowAnonymous] [HttpPost("/renew-token")] - public async Task> RenewToken([FromHeader(Name = "Authorization")][Required] string accessToken) + public async Task> RenewToken([FromHeader(Name = "Authorization")][Required] string token) { - if (accessToken.Contains(' ')) + if (token.Contains(' ')) { - accessToken = accessToken.Split(' ', StringSplitOptions.RemoveEmptyEntries).Last(); + token = token.Split(' ', StringSplitOptions.RemoveEmptyEntries).Last(); } - var token = await _userService.RenewToken(accessToken); - if (token == null) + var newToken = await _userService.RenewToken(token); + if (newToken == null) { return Unauthorized(); } - return Ok(token); + return Ok(newToken); } [AllowAnonymous] From 45b9002dae96125e6bbe750845904ef3aef54597 Mon Sep 17 00:00:00 2001 From: Jicheng Lu Date: Tue, 11 Nov 2025 18:59:20 -0600 Subject: [PATCH 27/27] minor change --- .../Coding/Contexts/CodeExecutionContext.cs | 2 +- .../BotSharp.Core.Rules/Engines/RuleEngine.cs | 10 +++++----- .../Instructs/Services/InstructService.Execute.cs | 10 +--------- 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Coding/Contexts/CodeExecutionContext.cs b/src/Infrastructure/BotSharp.Abstraction/Coding/Contexts/CodeExecutionContext.cs index 84f04455f..e2ec38a5a 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Coding/Contexts/CodeExecutionContext.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Coding/Contexts/CodeExecutionContext.cs @@ -3,5 +3,5 @@ namespace BotSharp.Abstraction.Coding.Contexts; public class CodeExecutionContext { public AgentCodeScript CodeScript { get; set; } - public IEnumerable Arguments { get; set; } = []; + public List Arguments { get; set; } = []; } diff --git a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs index 2f68fd108..1b8d537be 100644 --- a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs +++ b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs @@ -119,7 +119,7 @@ await convService.SendMessage(agent.Id, var msg = $"rule trigger ({triggerName}) code script ({scriptName}) in agent ({agent.Name}) => args: {options.ArgumentContent?.RootElement.GetRawText()}."; - if (string.IsNullOrWhiteSpace(codeScript?.Content)) + if (codeScript == null || string.IsNullOrWhiteSpace(codeScript.Content)) { _logger.LogWarning($"Unable to find {msg}."); return null; @@ -127,9 +127,9 @@ await convService.SendMessage(agent.Id, try { - var arguments = BuildArguments(options.ArgumentName, options.ArgumentContent); - var hooks = _services.GetHooks(agent.Id); + + var arguments = BuildArguments(options.ArgumentName, options.ArgumentContent); var context = new CodeExecutionContext { CodeScript = codeScript, @@ -146,7 +146,7 @@ await convService.SendMessage(agent.Id, var response = await processor.RunAsync(codeScript.Content, options: new() { ScriptName = scriptName, - Arguments = BuildArguments(options.ArgumentName, options.ArgumentContent), + Arguments = arguments, UseLock = useLock, UseProcess = useProcess }, cancellationToken: cts.Token); @@ -193,7 +193,7 @@ await convService.SendMessage(agent.Id, } } - private IEnumerable BuildArguments(string? name, JsonDocument? args) + private List BuildArguments(string? name, JsonDocument? args) { var keyValues = new List(); if (args != null) diff --git a/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs b/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs index 14388a655..8e4d3ec6d 100644 --- a/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs +++ b/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs @@ -276,21 +276,13 @@ await hook.OnResponseGenerated(new InstructResponseModel }; } - if (context?.Arguments != null) - { - foreach (var arg in context.Arguments) - { - state.SetState(arg.Key, arg.Value, source: StateSource.External); - } - } - var codeExeResponse = new CodeExecutionResponseModel { CodeProcessor = codeProcessor.Provider, CodeScript = context.CodeScript, ExecutionResult = codeResponse?.ToString() ?? string.Empty, Text = message.Content, - Arguments = state.GetStates() + Arguments = context.Arguments?.DistinctBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value ?? string.Empty) }; // After code execution