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 3c71b3fbf..aa673e32b 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/Coding/Enums/BuiltInCodeProcessor.cs b/src/Infrastructure/BotSharp.Abstraction/Coding/Enums/BuiltInCodeProcessor.cs new file mode 100644 index 000000000..cdbf76a99 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Coding/Enums/BuiltInCodeProcessor.cs @@ -0,0 +1,6 @@ +namespace BotSharp.Abstraction.Coding.Enums; + +public static class BuiltInCodeProcessor +{ + public const string PyInterpreter = "botsharp-py-interpreter"; +} 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/Coding/Settings/CodingSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Coding/Settings/CodingSettings.cs index 6ed52a41a..319581b0b 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Coding/Settings/CodingSettings.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Coding/Settings/CodingSettings.cs @@ -1,3 +1,5 @@ +using BotSharp.Abstraction.Coding.Enums; + namespace BotSharp.Abstraction.Coding.Settings; public class CodingSettings @@ -7,20 +9,13 @@ public class CodingSettings public CodeScriptExecutionSettings CodeExecution { get; set; } = new(); } -public class CodeScriptGenerationSettings +public class CodeScriptGenerationSettings : LlmConfigBase { - /// - /// Llm provider to generate code script - /// - public string? Provider { get; set; } - - /// - /// Llm model to generate code script - /// - public string? Model { get; set; } + public int? MessageLimit { get; set; } } public class CodeScriptExecutionSettings { + public string? Processor { get; set; } = BuiltInCodeProcessor.PyInterpreter; public int MaxConcurrency { get; set; } = 1; } \ No newline at end of file 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/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/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.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/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.Abstraction/Rules/IRuleEngine.cs b/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleEngine.cs index c870f145f..c7a6d847b 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleEngine.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleEngine.cs @@ -2,5 +2,15 @@ namespace BotSharp.Abstraction.Rules; public interface IRuleEngine { - Task> Triggered(IRuleTrigger trigger, string data, List? states = null); + /// + /// Trigger the rule that is subscribed by agents. + /// + /// + /// + /// + /// + /// + /// + Task> Triggered(IRuleTrigger trigger, string text, IEnumerable? states = null, RuleTriggerOptions? options = null) + => throw new NotImplementedException(); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleTrigger.cs b/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleTrigger.cs index 7b6c9bc25..c7ad59d9a 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,14 @@ public interface IRuleTrigger string EntityType { get; set; } string EntityId { get; set; } + + /// + /// 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.Abstraction/Rules/Options/RuleTriggerOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Rules/Options/RuleTriggerOptions.cs new file mode 100644 index 000000000..068052b0b --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Rules/Options/RuleTriggerOptions.cs @@ -0,0 +1,26 @@ +using System.Text.Json; + +namespace BotSharp.Abstraction.Rules.Options; + +public class RuleTriggerOptions +{ + /// + /// Code processor provider + /// + public string? CodeProcessor { get; set; } + + /// + /// Code script name + /// + public string? CodeScriptName { get; set; } + + /// + /// Argument name as an input key to the code script + /// + public string? ArgumentName { get; set; } + + /// + /// Json arguments as an input value to the code script + /// + public JsonDocument? ArgumentContent { get; set; } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Using.cs b/src/Infrastructure/BotSharp.Abstraction/Using.cs index ac26508c5..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; global using BotSharp.Abstraction.Coding.Settings; \ 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..d204daa01 100644 --- a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs +++ b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs @@ -1,25 +1,33 @@ +using BotSharp.Abstraction.Coding; +using BotSharp.Abstraction.Coding.Enums; 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> Triggered(IRuleTrigger trigger, string text, IEnumerable? states = null, RuleTriggerOptions? options = null) { + var newConversationIds = new List(); + // Pull all user defined rules var agentService = _services.GetRequiredService(); var agents = await agentService.GetAgents(new AgentFilter @@ -30,25 +38,32 @@ 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(); + // Trigger agents + 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; - // Trigger the agents - var instructService = _services.GetRequiredService(); - var newConversationIds = new List(); + // Code trigger + if (options != null) + { + isTriggered = await TriggerCodeScript(agent.Id, trigger.Name, options); + } + + if (!isTriggered) + { + continue; + } - foreach (var agent in preFilteredAgents) - { 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 { @@ -69,27 +84,85 @@ await convService.SendMessage(agent.Id, convService.SaveStates(); newConversationIds.Add(conv.Id); + } + + return newConversationIds; + } + + #region Private methods + private async Task TriggerCodeScript(string agentId, string triggerName, RuleTriggerOptions options) + { + 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: {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.ArgumentContent?.RootElement.GetRawText()}."; + + if (string.IsNullOrWhiteSpace(codeScript?.Content)) + { + _logger.LogWarning($"Unable to find {msg}."); + return false; + } + + try + { + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3)); + var response = await processor.RunAsync(codeScript.Content, options: new() + { + ScriptName = scriptName, + Arguments = BuildArguments(options.ArgumentName, options.ArgumentContent), + }, cancellationToken: cts.Token); + + if (response == null || !response.Success) + { + _logger.LogWarning($"Failed to handle {msg}"); + return false; + } - /*foreach (var rule in agent.Rules) + bool result; + LogLevel logLevel; + if (response.Result.IsEqualTo("true")) { - 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); - } - }*/ + logLevel = LogLevel.Information; + result = true; + } + else + { + logLevel = LogLevel.Warning; + result = false; + } + + _logger.Log(logLevel, $"Code script execution result ({response.Result}) from {msg}"); + return result; + } + catch (Exception ex) + { + _logger.LogError(ex, $"Error when handling {msg}"); + return false; } + } - return newConversationIds; + private IEnumerable BuildArguments(string? name, JsonDocument? args) + { + var keyValues = new List(); + if (args != null) + { + keyValues.Add(new KeyValue(name ?? "trigger_args", args.RootElement.GetRawText())); + } + return keyValues; } +#endregion } diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Coding.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Coding.cs index aee641c8c..936bdaf40 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.Enums; using BotSharp.Abstraction.Coding.Options; namespace BotSharp.Core.Agents.Services; @@ -13,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); @@ -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/BotSharp.Core.csproj b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj index 8d045f856..d0d517f4a 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/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 dbd9de90f..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"); @@ -85,7 +87,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); @@ -124,13 +126,14 @@ 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); // Convert image - var converter = GetImageConverter(options?.ImageConvertProvider); + var converter = GetImageConverter(options?.ImageConverter); if (converter != null) { binary = await converter.ConvertImage(binary); @@ -173,14 +176,15 @@ 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); 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); @@ -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"); @@ -236,7 +241,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..bee60f1af 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(); @@ -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 170a0fc0c..48cf5e83c 100644 --- a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.cs +++ b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.cs @@ -1,7 +1,6 @@ - +using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Files.Converters; -using Microsoft.Extensions.Options; -using static System.Net.Mime.MediaTypeNames; +using BotSharp.Abstraction.Templating; namespace BotSharp.Core.Files.Services; @@ -63,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)) { @@ -77,21 +76,32 @@ 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); - var fextension = extension.IfNullOrEmptyAs(defaultExtension); + var fextension = extension.IfNullOrEmptyAs(defaultExtension)!; fextension = fextension.StartsWith(".") ? fextension.Substring(1) : fextension; return $"{name}.{fextension}"; } 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 535547005..cacb6913b 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.Responses; +using BotSharp.Abstraction.Coding.Enums; using BotSharp.Abstraction.Files.Options; using BotSharp.Abstraction.Files.Proccessors; using BotSharp.Abstraction.Instructs; @@ -180,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)); @@ -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 }; @@ -256,9 +257,9 @@ await hook.OnResponseGenerated(new InstructResponseModel // Run code script var seconds = codeOptions?.TimeoutSeconds > 0 ? codeOptions.TimeoutSeconds.Value : 3; using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(seconds)); - var codeResponse = await codeProcessor.RunAsync(context.CodeScript, options: new() + var codeResponse = await codeProcessor.RunAsync(codeScript.Content, options: new() { - ScriptName = scriptName, + ScriptName = codeScript.Name, Arguments = context.Arguments }, cancellationToken: cts.Token); @@ -270,7 +271,7 @@ await hook.OnResponseGenerated(new InstructResponseModel response = new InstructResult { MessageId = message.MessageId, - Template = scriptName, + Template = codeScript.Name, Text = codeResponse.Result }; @@ -289,9 +290,9 @@ await hook.OnResponseGenerated(new InstructResponseModel AgentId = agent.Id, Provider = codeProcessor.Provider, Model = string.Empty, - TemplateName = scriptName, + TemplateName = codeScript.Name, UserMessage = message.Content, - SystemInstruction = context?.CodeScript, + SystemInstruction = $"Code script name: {codeScript}, 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/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 new file mode 100644 index 000000000..2293ede7b --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/data/agents/c2a2faf6-b8b5-47fe-807b-f4714cf25dd4/templates/rule-trigger-code-generate_instruction.liquid @@ -0,0 +1,15 @@ +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 {{args_example}}. +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". +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 executable code directly in order to directly save it to a py file. \ No newline at end of file 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/Infrastructure/BotSharp.OpenAPI/Controllers/Agent/AgentController.Rule.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Agent/AgentController.Rule.cs index 4daefc717..52fd719fd 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/Agent/AgentController.Rule.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Agent/AgentController.Rule.cs @@ -6,13 +6,16 @@ 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 - }).OrderBy(x => x.TriggerName).ToList(); + TriggerName = x.Name, + Channel = x.Channel, + Statement = x.Statement, + OutputArgs = x.OutputArgs + }).OrderBy(x => x.TriggerName); } [HttpGet("/rule/formalization")] 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")] 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/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/Agents/View/AgentRuleViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/View/AgentRuleViewModel.cs new file mode 100644 index 000000000..23386c13d --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/View/AgentRuleViewModel.cs @@ -0,0 +1,35 @@ +using System.Text.Json.Serialization; + +namespace BotSharp.OpenAPI.ViewModels.Agents; + +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; } + + [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```"; + } + } +} 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/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.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; 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 } 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) diff --git a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Functions/PyProgrammerFn.cs b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Functions/PyProgrammerFn.cs index a2b41ec33..d3ea58b02 100644 --- a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Functions/PyProgrammerFn.cs +++ b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Functions/PyProgrammerFn.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.Logging; -using Python.Runtime; using System.Text.Json; +using System.Threading; using System.Threading.Tasks; namespace BotSharp.Plugin.PythonInterpreter.Functions; @@ -12,16 +12,19 @@ public class PyProgrammerFn : IFunctionCallback private readonly IServiceProvider _services; private readonly ILogger _logger; - private readonly PythonInterpreterSettings _settings; + private readonly CodingSettings _codingSettings; + private readonly PythonInterpreterSettings _pySettings; public PyProgrammerFn( IServiceProvider services, ILogger logger, - PythonInterpreterSettings settings) + CodingSettings codingSettings, + PythonInterpreterSettings pySettings) { _services = services; _logger = logger; - _settings = settings; + _codingSettings = codingSettings; + _pySettings = pySettings; } public async Task Execute(RoleDialogModel message) @@ -42,7 +45,7 @@ public async Task Execute(RoleDialogModel message) LlmConfig = GetLlmConfig(), TemplateDict = new() { - ["python_version"] = _settings.PythonVersion ?? "3.11", + ["python_version"] = _pySettings.PythonVersion ?? "3.11", ["user_requirement"] = args?.UserRquirement ?? string.Empty } }; @@ -53,7 +56,7 @@ public async Task Execute(RoleDialogModel message) dialogs = convService.GetDialogHistory(); } - var messageLimit = _settings.CodeGeneration?.MessageLimit > 0 ? _settings.CodeGeneration.MessageLimit.Value : 50; + var messageLimit = _codingSettings.CodeGeneration?.MessageLimit > 0 ? _codingSettings.CodeGeneration.MessageLimit.Value : 50; dialogs = dialogs.TakeLast(messageLimit).ToList(); dialogs.Add(new RoleDialogModel(AgentRole.User, "Please follow the instruction and chat context to generate valid python code.") { @@ -66,7 +69,7 @@ public async Task Execute(RoleDialogModel message) try { - var (isSuccess, result) = InnerRunCode(ret.PythonCode); + var (isSuccess, result) = await InnerRunCode(ret.PythonCode); if (isSuccess) { message.Content = result; @@ -102,50 +105,25 @@ public async Task Execute(RoleDialogModel message) /// /// /// - private (bool, string) InnerRunCode(string codeScript) + private async Task<(bool, string)> InnerRunCode(string codeScript) { - using (Py.GIL()) - { - // Import necessary Python modules - dynamic sys = Py.Import("sys"); - dynamic io = Py.Import("io"); + var processor = _services.GetServices() + .FirstOrDefault(x => x.Provider.IsEqualTo(_codingSettings.CodeExecution?.Processor ?? BuiltInCodeProcessor.PyInterpreter)); - try - { - // Redirect standard output/error to capture it - dynamic stringIO = io.StringIO(); - sys.stdout = stringIO; - sys.stderr = stringIO; - sys.argv = new PyList(); - - // Set global items - using var globals = new PyDict(); - if (codeScript?.Contains("__main__") == true) - { - globals.SetItem("__name__", new PyString("__main__")); - } + if (processor == null) + { + return (false, "Unable to execute python code script."); + } - // Execute Python script - PythonEngine.Exec(codeScript, globals); + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + var response = await processor.RunAsync(codeScript, cancellationToken: cts.Token); - // Get result - var result = stringIO.getvalue()?.ToString() as string; - return (true, result?.TrimEnd('\r', '\n') ?? string.Empty); - } - catch (Exception ex) - { - var errorMsg = $"Error when executing inner python code. {ex.Message}"; - _logger.LogError(ex, errorMsg); - return (false, errorMsg); - } - finally - { - // Restore the original stdout/stderr/argv - sys.stdout = sys.__stdout__; - sys.stderr = sys.__stderr__; - sys.argv = new PyList(); - } + if (response == null || !response.Success) + { + return (false, !string.IsNullOrEmpty(response?.ErrorMsg) ? response.ErrorMsg : "Failed to execute python code script."); } + + return (true, response.Result); } private async Task GetChatCompletion(Agent agent, List dialogs) @@ -188,8 +166,8 @@ private string GetPyCodeInterpreterInstruction(string agentId) private (string, string) GetLlmProviderModel() { - var provider = _settings.CodeGeneration?.Provider; - var model = _settings.CodeGeneration?.Model; + var provider = _codingSettings.CodeGeneration?.Provider; + var model = _codingSettings.CodeGeneration?.Model; if (!string.IsNullOrEmpty(provider) && !string.IsNullOrEmpty(model)) { @@ -204,8 +182,8 @@ private string GetPyCodeInterpreterInstruction(string agentId) private AgentLlmConfig GetLlmConfig() { - var maxOutputTokens = _settings?.CodeGeneration?.MaxOutputTokens ?? 8192; - var reasoningEffortLevel = _settings?.CodeGeneration?.ReasoningEffortLevel ?? "minimal"; + var maxOutputTokens = _codingSettings?.CodeGeneration?.MaxOutputTokens ?? 8192; + var reasoningEffortLevel = _codingSettings?.CodeGeneration?.ReasoningEffortLevel ?? "minimal"; return new AgentLlmConfig { diff --git a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyCodeInterpreter.cs b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyCodeInterpreter.cs index 212d8c0cb..97e277314 100644 --- a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyCodeInterpreter.cs +++ b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyCodeInterpreter.cs @@ -1,5 +1,3 @@ -using BotSharp.Abstraction.Coding.Models; -using BotSharp.Abstraction.Coding.Settings; using Microsoft.Extensions.Logging; using Python.Runtime; using System.Diagnostics; @@ -28,7 +26,7 @@ public PyCodeInterpreter( _settings = settings; } - public string Provider => "botsharp-py-interpreter"; + public string Provider => BuiltInCodeProcessor.PyInterpreter; public async Task RunAsync(string codeScript, CodeInterpretOptions? options = null, CancellationToken cancellationToken = default) { @@ -49,10 +47,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); } @@ -70,10 +68,10 @@ public async Task GenerateCodeScriptAsync(string text, Cod Instruction = instruction, LlmConfig = new AgentLlmConfig { - Provider = options?.Provider ?? "openai", - Model = options?.Model ?? "gpt-5-mini", - MaxOutputTokens = options?.MaxOutputTokens, - ReasoningEffortLevel = options?.ReasoningEffortLevel + Provider = options?.Provider ?? provider, + Model = options?.Model ?? model, + MaxOutputTokens = options?.MaxOutputTokens ?? _settings?.CodeGeneration?.MaxOutputTokens, + ReasoningEffortLevel = options?.ReasoningEffortLevel ?? _settings?.CodeGeneration?.ReasoningEffortLevel }, TemplateDict = options?.Data ?? new() }; @@ -92,7 +90,7 @@ public async Task GenerateCodeScriptAsync(string text, Cod { Success = true, Content = response.Content, - Language = options?.Language ?? "python" + Language = options?.ProgrammingLanguage ?? "python" }; } @@ -167,7 +165,7 @@ private async Task CoreRunScript(string codeScript, CodeI var list = new PyList(); if (options?.Arguments?.Any() == true) { - list.Append(new PyString(options?.ScriptName ?? "script.py")); + list.Append(new PyString(options?.ScriptName ?? $"{Guid.NewGuid()}.py")); foreach (var arg in options!.Arguments) { diff --git a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Settings/PythonInterpreterSettings.cs b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Settings/PythonInterpreterSettings.cs index 8fa79dc8a..0c86d9daa 100644 --- a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Settings/PythonInterpreterSettings.cs +++ b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Settings/PythonInterpreterSettings.cs @@ -1,5 +1,3 @@ -using BotSharp.Abstraction.Models; - namespace BotSharp.Plugin.PythonInterpreter.Settings; public class PythonInterpreterSettings @@ -9,10 +7,4 @@ public class PythonInterpreterSettings /// public string InstallLocation { get; set; } public string PythonVersion { get; set; } - public CodeGenerationSetting? CodeGeneration { get; set; } -} - -public class CodeGenerationSetting : LlmConfigBase -{ - public int? MessageLimit { get; set; } } \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Using.cs b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Using.cs index bd2c943b7..9296406a3 100644 --- a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Using.cs +++ b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Using.cs @@ -21,6 +21,9 @@ global using BotSharp.Abstraction.Messaging.Models.RichContent.Template; global using BotSharp.Abstraction.Routing; 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 57dd1c50d..fe0dacb4f 100644 --- a/src/WebStarter/appsettings.json +++ b/src/WebStarter/appsettings.json @@ -474,9 +474,13 @@ "Coding": { "CodeGeneration": { "Provider": "openai", - "Model": "gpt-5-mini" + "Model": "gpt-5-mini", + "MaxOutputTokens": 8192, + "ReasoningEffortLevel": "minimal", + "MessageLimit": 50 }, "CodeExecution": { + "Processor": null, "MaxConcurrency": 1 } }, @@ -784,14 +788,7 @@ "PythonInterpreter": { "InstallLocation": "C:/Users/xxx/AppData/Local/Programs/Python/Python313/python313.dll", - "PythonVersion": "3.13.3", - "CodeGeneration": { - "Provider": "openai", - "Model": "gpt-5", - "MaxOutputTokens": 8192, - "ReasoningEffortLevel": "minimal", - "MessageLimit": 50 - } + "PythonVersion": "3.13.3" }, "RealtimeModel": {