refine running py code script

This commit is contained in:
Jicheng Lu 2025-10-07 21:57:08 -05:00
parent 6a0153db28
commit edd2ad6002
6 changed files with 168 additions and 53 deletions

View file

@ -1,4 +1,5 @@
using BotSharp.Abstraction.CodeInterpreter.Models; using BotSharp.Abstraction.CodeInterpreter.Models;
using System.Threading;
namespace BotSharp.Abstraction.CodeInterpreter; namespace BotSharp.Abstraction.CodeInterpreter;

View file

@ -1,6 +1,10 @@
using System.Threading;
namespace BotSharp.Abstraction.CodeInterpreter.Models; namespace BotSharp.Abstraction.CodeInterpreter.Models;
public class CodeInterpretOptions public class CodeInterpretOptions
{ {
public IEnumerable<KeyValue>? Arguments { get; set; } public IEnumerable<KeyValue>? Arguments { get; set; }
public bool LockFree { get; set; }
public CancellationToken? CancellationToken { get; set; }
} }

View file

@ -0,0 +1,38 @@
using BotSharp.Abstraction.CodeInterpreter.Models;
namespace BotSharp.Core.CodeInterpreter;
public class CodeScriptExecutor
{
private readonly ILogger<CodeScriptExecutor> _logger;
private readonly SemaphoreSlim _semLock = new(initialCount: 1, maxCount: 1);
public CodeScriptExecutor(
ILogger<CodeScriptExecutor> logger)
{
_logger = logger;
}
public async Task<CodeInterpretResult> Execute(Func<Task<CodeInterpretResult>> 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();
}
}
}

View file

@ -8,6 +8,7 @@ using BotSharp.Abstraction.Planning;
using BotSharp.Abstraction.Plugins.Models; using BotSharp.Abstraction.Plugins.Models;
using BotSharp.Abstraction.Settings; using BotSharp.Abstraction.Settings;
using BotSharp.Abstraction.Templating; using BotSharp.Abstraction.Templating;
using BotSharp.Core.CodeInterpreter;
using BotSharp.Core.Instructs; using BotSharp.Core.Instructs;
using BotSharp.Core.MessageHub; using BotSharp.Core.MessageHub;
using BotSharp.Core.MessageHub.Observers; using BotSharp.Core.MessageHub.Observers;
@ -70,6 +71,7 @@ public class ConversationPlugin : IBotSharpPlugin
services.AddScoped<ITokenStatistics, TokenStatistics>(); services.AddScoped<ITokenStatistics, TokenStatistics>();
services.AddScoped<IAgentUtilityHook, WebSearchUtilityHook>(); services.AddScoped<IAgentUtilityHook, WebSearchUtilityHook>();
services.AddSingleton<CodeScriptExecutor>();
} }
public bool AttachMenu(List<PluginMenuDef> menu) public bool AttachMenu(List<PluginMenuDef> menu)

View file

@ -1,3 +1,4 @@
using Microsoft.AspNetCore.Cors.Infrastructure;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Python.Runtime; using Python.Runtime;
using System.Text.Json; using System.Text.Json;
@ -66,30 +67,10 @@ public class PyProgrammerFn : IFunctionCallback
try try
{ {
using (Py.GIL()) var (isSuccess, result) = InnerRunCode(ret.PythonCode);
if (isSuccess)
{ {
// Import necessary Python modules message.Content = result;
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.RichContent = new RichContent<IRichMessage> message.RichContent = new RichContent<IRichMessage>
{ {
Recipient = new Recipient { Id = convService.ConversationId }, Recipient = new Recipient { Id = convService.ConversationId },
@ -100,21 +81,70 @@ public class PyProgrammerFn : IFunctionCallback
} }
}; };
message.StopCompletion = true; 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;
}
/// <summary>
/// Run python code script => (isSuccess, result)
/// </summary>
/// <param name="codeScript"></param>
/// <returns></returns>
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.stdout = sys.__stdout__;
sys.stderr = sys.__stderr__; sys.stderr = sys.__stderr__;
sys.argv = new PyList(); 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<string> GetChatCompletion(Agent agent, List<RoleDialogModel> dialogs) private async Task<string> GetChatCompletion(Agent agent, List<RoleDialogModel> dialogs)

View file

@ -1,5 +1,7 @@
using BotSharp.Core.CodeInterpreter;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Python.Runtime; using Python.Runtime;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace BotSharp.Plugin.PythonInterpreter.Services; namespace BotSharp.Plugin.PythonInterpreter.Services;
@ -8,27 +10,63 @@ public class PyInterpretService : ICodeInterpretService
{ {
private readonly IServiceProvider _services; private readonly IServiceProvider _services;
private readonly ILogger<PyInterpretService> _logger; private readonly ILogger<PyInterpretService> _logger;
private readonly CodeScriptExecutor _executor;
public PyInterpretService( public PyInterpretService(
IServiceProvider services, IServiceProvider services,
ILogger<PyInterpretService> logger) ILogger<PyInterpretService> logger,
CodeScriptExecutor executor)
{ {
_services = services; _services = services;
_logger = logger; _logger = logger;
_executor = executor;
} }
public string Provider => "botsharp-py-interpreter"; public string Provider => "botsharp-py-interpreter";
public async Task<CodeInterpretResult> RunCode(string codeScript, CodeInterpretOptions? options = null) public async Task<CodeInterpretResult> 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 try
{ {
using (Py.GIL()) return CoreRun(codeScript, options);
{ }
// Import necessary Python modules catch (Exception ex)
dynamic sys = Py.Import("sys"); {
dynamic io = Py.Import("io"); 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 // Redirect standard output/error to capture it
dynamic stringIO = io.StringIO(); dynamic stringIO = io.StringIO();
sys.stdout = stringIO; sys.stdout = stringIO;
@ -64,28 +102,30 @@ public class PyInterpretService : ICodeInterpretService
// Get result // Get result
var result = stringIO.getvalue()?.ToString() as string; 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 return new CodeInterpretResult
{ {
Result = result?.TrimEnd('\r', '\n'), Result = result?.TrimEnd('\r', '\n'),
Success = true Success = true
}; };
} }
} catch (Exception ex)
catch (Exception ex)
{
var errorMsg = $"Error when executing python code in {nameof(PyInterpretService)}: {Provider}. {ex.Message}";
_logger.LogError(ex, errorMsg);
return new CodeInterpretResult
{ {
Success = false, var errorMsg = $"Error when executing core python code in {nameof(PyInterpretService)}: {Provider}. {ex.Message}";
ErrorMsg = errorMsg _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();
}
} }
} }
} }