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 System.Threading;
namespace BotSharp.Abstraction.CodeInterpreter;

View file

@ -1,6 +1,10 @@
using System.Threading;
namespace BotSharp.Abstraction.CodeInterpreter.Models;
public class CodeInterpretOptions
{
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.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<ITokenStatistics, TokenStatistics>();
services.AddScoped<IAgentUtilityHook, WebSearchUtilityHook>();
services.AddSingleton<CodeScriptExecutor>();
}
public bool AttachMenu(List<PluginMenuDef> menu)

View file

@ -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<IRichMessage>
{
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;
}
/// <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.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<string> GetChatCompletion(Agent agent, List<RoleDialogModel> dialogs)

View file

@ -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<PyInterpretService> _logger;
private readonly CodeScriptExecutor _executor;
public PyInterpretService(
IServiceProvider services,
ILogger<PyInterpretService> logger)
ILogger<PyInterpretService> logger,
CodeScriptExecutor executor)
{
_services = services;
_logger = logger;
_executor = executor;
}
public string Provider => "botsharp-py-interpreter";
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
{
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();
}
}
}
}