diff --git a/src/Infrastructure/BotSharp.Abstraction/CodeInterpreter/ICodeInterpretService.cs b/src/Infrastructure/BotSharp.Abstraction/CodeInterpreter/ICodeInterpretService.cs index bdc10f5c..58e55438 100644 --- a/src/Infrastructure/BotSharp.Abstraction/CodeInterpreter/ICodeInterpretService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/CodeInterpreter/ICodeInterpretService.cs @@ -1,4 +1,5 @@ using BotSharp.Abstraction.CodeInterpreter.Models; +using System.Threading; namespace BotSharp.Abstraction.CodeInterpreter; diff --git a/src/Infrastructure/BotSharp.Abstraction/CodeInterpreter/Models/CodeInterpretOptions.cs b/src/Infrastructure/BotSharp.Abstraction/CodeInterpreter/Models/CodeInterpretOptions.cs index 23e78a97..46f05ebd 100644 --- a/src/Infrastructure/BotSharp.Abstraction/CodeInterpreter/Models/CodeInterpretOptions.cs +++ b/src/Infrastructure/BotSharp.Abstraction/CodeInterpreter/Models/CodeInterpretOptions.cs @@ -1,6 +1,10 @@ +using System.Threading; + namespace BotSharp.Abstraction.CodeInterpreter.Models; public class CodeInterpretOptions { public IEnumerable? Arguments { get; set; } + public bool LockFree { get; set; } + public CancellationToken? CancellationToken { get; set; } } diff --git a/src/Infrastructure/BotSharp.Core/CodeInterpreter/CodeScriptExecutor.cs b/src/Infrastructure/BotSharp.Core/CodeInterpreter/CodeScriptExecutor.cs new file mode 100644 index 00000000..5b544f11 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/CodeInterpreter/CodeScriptExecutor.cs @@ -0,0 +1,38 @@ +using BotSharp.Abstraction.CodeInterpreter.Models; + +namespace BotSharp.Core.CodeInterpreter; + +public class CodeScriptExecutor +{ + private readonly ILogger _logger; + private readonly SemaphoreSlim _semLock = new(initialCount: 1, maxCount: 1); + + public CodeScriptExecutor( + ILogger logger) + { + _logger = logger; + } + + public async Task Execute(Func> func, CancellationToken cancellationToken = default) + { + await _semLock.WaitAsync(cancellationToken); + + try + { + return await func(); + } + catch (Exception ex) + { + _logger.LogError(ex, $"Error in {nameof(CodeScriptExecutor)}."); + return new CodeInterpretResult + { + Success = false, + ErrorMsg = ex.Message + }; + } + finally + { + _semLock.Release(); + } + } +} diff --git a/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs b/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs index 64899ec4..798fe307 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs @@ -8,6 +8,7 @@ using BotSharp.Abstraction.Planning; using BotSharp.Abstraction.Plugins.Models; using BotSharp.Abstraction.Settings; using BotSharp.Abstraction.Templating; +using BotSharp.Core.CodeInterpreter; using BotSharp.Core.Instructs; using BotSharp.Core.MessageHub; using BotSharp.Core.MessageHub.Observers; @@ -70,6 +71,7 @@ public class ConversationPlugin : IBotSharpPlugin services.AddScoped(); services.AddScoped(); + services.AddSingleton(); } public bool AttachMenu(List menu) diff --git a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Functions/PyProgrammerFn.cs b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Functions/PyProgrammerFn.cs index 30d30658..fb0b12c0 100644 --- a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Functions/PyProgrammerFn.cs +++ b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Functions/PyProgrammerFn.cs @@ -1,3 +1,4 @@ +using Microsoft.AspNetCore.Cors.Infrastructure; using Microsoft.Extensions.Logging; using Python.Runtime; using System.Text.Json; @@ -66,30 +67,10 @@ public class PyProgrammerFn : IFunctionCallback try { - using (Py.GIL()) + var (isSuccess, result) = InnerRunCode(ret.PythonCode); + if (isSuccess) { - // Import necessary Python modules - dynamic sys = Py.Import("sys"); - dynamic io = Py.Import("io"); - - // Redirect standard output/error to capture it - dynamic stringIO = io.StringIO(); - sys.stdout = stringIO; - sys.stderr = stringIO; - - // Set global items - using var globals = new PyDict(); - if (ret.PythonCode?.Contains("__main__") == true) - { - globals.SetItem("__name__", new PyString("__main__")); - } - - // Execute Python script - PythonEngine.Exec(ret.PythonCode, globals); - - // Get result - var result = stringIO.getvalue()?.ToString() as string; - message.Content = result?.TrimEnd('\r', '\n') ?? string.Empty; + message.Content = result; message.RichContent = new RichContent { Recipient = new Recipient { Id = convService.ConversationId }, @@ -100,21 +81,70 @@ public class PyProgrammerFn : IFunctionCallback } }; message.StopCompletion = true; + } + else + { + message.Content = result; + } + } + catch (Exception ex) + { + var errorMsg = $"Error when executing python code. {ex.Message}"; + message.Content = errorMsg; + _logger.LogError(ex, errorMsg); + } - // Restore the original stdout/stderr + return true; + } + + /// + /// Run python code script => (isSuccess, result) + /// + /// + /// + private (bool, string) InnerRunCode(string codeScript) + { + using (Py.GIL()) + { + // Import necessary Python modules + dynamic sys = Py.Import("sys"); + dynamic io = Py.Import("io"); + + try + { + // Redirect standard output/error to capture it + dynamic stringIO = io.StringIO(); + sys.stdout = stringIO; + sys.stderr = stringIO; + + // Set global items + using var globals = new PyDict(); + if (codeScript?.Contains("__main__") == true) + { + globals.SetItem("__name__", new PyString("__main__")); + } + + // Execute Python script + PythonEngine.Exec(codeScript, globals); + + // 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(); } } - catch (Exception ex) - { - var errorMsg = $"Error when executing python code."; - message.Content = $"{errorMsg} {ex.Message}"; - _logger.LogError(ex, errorMsg); - } - - return true; } private async Task GetChatCompletion(Agent agent, List dialogs) diff --git a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyInterpretService.cs b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyInterpretService.cs index 740499d5..34a1f03e 100644 --- a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyInterpretService.cs +++ b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyInterpretService.cs @@ -1,5 +1,7 @@ +using BotSharp.Core.CodeInterpreter; using Microsoft.Extensions.Logging; using Python.Runtime; +using System.Threading; using System.Threading.Tasks; namespace BotSharp.Plugin.PythonInterpreter.Services; @@ -8,27 +10,63 @@ public class PyInterpretService : ICodeInterpretService { private readonly IServiceProvider _services; private readonly ILogger _logger; + private readonly CodeScriptExecutor _executor; public PyInterpretService( IServiceProvider services, - ILogger logger) + ILogger logger, + CodeScriptExecutor executor) { _services = services; _logger = logger; + _executor = executor; } public string Provider => "botsharp-py-interpreter"; public async Task RunCode(string codeScript, CodeInterpretOptions? options = null) + { + if (options?.LockFree != true) + { + return await _executor.Execute(async () => + { + return InnerRunCode(codeScript, options); + }, cancellationToken: options?.CancellationToken ?? CancellationToken.None); + + } + + return InnerRunCode(codeScript, options); + } + + private CodeInterpretResult InnerRunCode(string codeScript, CodeInterpretOptions? options = null) { try { - using (Py.GIL()) - { - // Import necessary Python modules - dynamic sys = Py.Import("sys"); - dynamic io = Py.Import("io"); + return CoreRun(codeScript, options); + } + catch (Exception ex) + { + var errorMsg = $"Error when executing inner python code in {nameof(PyInterpretService)}: {Provider}."; + _logger.LogError(ex, errorMsg); + return new CodeInterpretResult + { + Success = false, + ErrorMsg = errorMsg + }; + } + } + + private CodeInterpretResult CoreRun(string codeScript, CodeInterpretOptions? options = null) + { + using (Py.GIL()) + { + // Import necessary Python modules + dynamic sys = Py.Import("sys"); + dynamic io = Py.Import("io"); + + try + { // Redirect standard output/error to capture it dynamic stringIO = io.StringIO(); sys.stdout = stringIO; @@ -64,28 +102,30 @@ public class PyInterpretService : ICodeInterpretService // Get result var result = stringIO.getvalue()?.ToString() as string; - // Restore the original stdout/stderr - sys.stdout = sys.__stdout__; - sys.stderr = sys.__stderr__; - sys.argv = new PyList(); - return new CodeInterpretResult { Result = result?.TrimEnd('\r', '\n'), Success = true }; } - } - catch (Exception ex) - { - var errorMsg = $"Error when executing python code in {nameof(PyInterpretService)}: {Provider}. {ex.Message}"; - _logger.LogError(ex, errorMsg); - - return new CodeInterpretResult + catch (Exception ex) { - Success = false, - ErrorMsg = errorMsg - }; + var errorMsg = $"Error when executing core python code in {nameof(PyInterpretService)}: {Provider}. {ex.Message}"; + _logger.LogError(ex, errorMsg); + + return new CodeInterpretResult + { + Success = false, + ErrorMsg = errorMsg + }; + } + finally + { + // Restore the original stdout/stderr/argv + sys.stdout = sys.__stdout__; + sys.stderr = sys.__stderr__; + sys.argv = new PyList(); + } } } }