Merge pull request #1177 from iceljc/features/refine-py-interpreter

refine running py code script
This commit is contained in:
iceljc 2025-10-08 15:29:39 -05:00 committed by GitHub
commit 88b18c04c2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 269 additions and 77 deletions

View file

@ -51,4 +51,14 @@ public static class BuiltInAgentId
/// Translates user-defined natural language rules into programmatic code
/// </summary>
public const string RulesInterpreter = "201e49a2-40b3-4ccd-b8cc-2476565a1b40";
/// <summary>
/// Generate code script
/// </summary>
public const string AIProgrammer = "c2a2faf6-b8b5-47fe-807b-f4714cf25dd4";
/// <summary>
/// Handle files
/// </summary>
public const string FileAssistant = "f1e09a73-9efe-46ce-ba02-b3aaf96d97e0";
}

View file

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

View file

@ -11,6 +11,7 @@
</PropertyGroup>
<ItemGroup>
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-crontab-task_wait.json" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-crontab-schedule_task.json" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-crontab-schedule_task.fn.liquid" />
</ItemGroup>

View file

@ -96,13 +96,16 @@
<None Remove="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\templates\instruction.simulator.liquid" />
<None Remove="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\templates\instruction.simulator.liquid" />
<None Remove="data\plugins\config.json" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-file-select_file_instruction.liquid" />
<None Remove="data\agents\01e2fc5c-2c89-4ec7-8470-7688608b496c\functions\get_weather.json" />
<None Remove="data\agents\01e2fc5c-2c89-4ec7-8470-7688608b496c\functions\get_fun_events.json" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-web-intelligent_search.json" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-web-intelligent_search.fn.liquid" />
<None Remove="data\agents\c2a2faf6-b8b5-47fe-807b-f4714cf25dd4\agent.json" />
<None Remove="data\agents\c2a2faf6-b8b5-47fe-807b-f4714cf25dd4\instructions\instruction.liquid" />
<None Remove="data\agents\f1e09a73-9efe-46ce-ba02-b3aaf96d97e0\agent.json" />
<None Remove="data\agents\f1e09a73-9efe-46ce-ba02-b3aaf96d97e0\instructions\instruction.liquid" />
</ItemGroup>
<ItemGroup>
@ -172,6 +175,9 @@
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-routing-fallback_to_router.fn.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\c2a2faf6-b8b5-47fe-807b-f4714cf25dd4\instructions\instruction.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\agent.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@ -193,9 +199,6 @@
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\instructions\instruction.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-file-select_file_instruction.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\templates\instruction.simulator.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@ -208,6 +211,18 @@
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-instruct-execute_template.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\c2a2faf6-b8b5-47fe-807b-f4714cf25dd4\agent.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\c2a2faf6-b8b5-47fe-807b-f4714cf25dd4\instructions\instruction.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\f1e09a73-9efe-46ce-ba02-b3aaf96d97e0\agent.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\f1e09a73-9efe-46ce-ba02-b3aaf96d97e0\instructions\instruction.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\plugins\config.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>

View file

@ -0,0 +1,34 @@
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<T> Execute<T>(Func<Task<T>> func, CancellationToken cancellationToken = default)
{
await _semLock.WaitAsync(cancellationToken);
try
{
return await func();
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error in {nameof(CodeScriptExecutor)}.");
return default(T);
}
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

@ -114,8 +114,8 @@ public partial class FileInstructService
return new NameDesc(text, desc);
}).ToList();
var agentId = !string.IsNullOrWhiteSpace(options.AgentId) ? options.AgentId : BuiltInAgentId.UtilityAssistant;
var template = !string.IsNullOrWhiteSpace(options.Template) ? options.Template : "util-file-select_file_instruction";
var agentId = !string.IsNullOrWhiteSpace(options.AgentId) ? options.AgentId : BuiltInAgentId.FileAssistant;
var template = !string.IsNullOrWhiteSpace(options.Template) ? options.Template : "select_chat_file_instruction";
var prompt = db.GetAgentTemplate(agentId, template);
var data = new Dictionary<string, object>

View file

@ -155,7 +155,7 @@ public partial class InstructService
var db = _services.GetRequiredService<IBotSharpRepository>();
var hooks = _services.GetHooks<IInstructHook>(agent.Id);
var codeProvider = codeOptions?.CodeInterpretProvider.IfNullOrEmptyAs("botsharp-py-interpreter");
var codeProvider = codeOptions?.CodeInterpretProvider ?? "botsharp-py-interpreter";
var codeInterpreter = _services.GetServices<ICodeInterpretService>()
.FirstOrDefault(x => x.Provider.IsEqualTo(codeProvider));
@ -228,6 +228,7 @@ public partial class InstructService
// Run code script
var result = await codeInterpreter.RunCode(context.CodeScript, options: new()
{
ScriptName = scriptName,
Arguments = context.Arguments
});

View file

@ -1,12 +1,17 @@
import argparse
import json
def main():
parser = argparse.ArgumentParser(description="Receive named arguments")
parser.add_argument("--first_name", required=True, help="The first name")
parser.add_argument("--last_name", required=True, help="The last name")
args = parser.parse_args()
print(f"Hello, {args.first_name} {args.last_name}!")
args, _ = parser.parse_known_args()
obj = {
"first_name": args.first_name,
"last_name":args.last_name
}
print(f"{json.dumps(obj)}")
if __name__ == "__main__":
main()

View file

@ -0,0 +1,18 @@
{
"id": "c2a2faf6-b8b5-47fe-807b-f4714cf25dd4",
"name": "AI programmer",
"description": "AI programmer is designed to generate code scripts.",
"type": "task",
"createdDateTime": "2025-10-07T10:39:32Z",
"updatedDateTime": "2025-10-07T14:39:32Z",
"iconUrl": "/images/logo.png",
"disabled": false,
"isPublic": true,
"llmConfig": {
"is_inherit": false,
"provider": "openai",
"model": "gpt-5",
"max_recursion_depth": 3,
"reasoning_effort_level": "minimal"
}
}

View file

@ -0,0 +1 @@
You are a AI programmer to help coding.

View file

@ -0,0 +1,18 @@
{
"id": "f1e09a73-9efe-46ce-ba02-b3aaf96d97e0",
"name": "File Assistant",
"description": "File Assistant is designed to handle files.",
"type": "task",
"createdDateTime": "2025-10-07T10:39:32Z",
"updatedDateTime": "2025-10-07T14:39:32Z",
"iconUrl": "/images/logo.png",
"disabled": false,
"isPublic": true,
"llmConfig": {
"is_inherit": false,
"provider": "openai",
"model": "gpt-5-mini",
"max_recursion_depth": 3,
"reasoning_effort_level": "minimal"
}
}

View file

@ -0,0 +1 @@
You are a File Assistant to help handle files.

View file

@ -13,7 +13,7 @@
<ItemGroup>
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-chart-plot_chart.json" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-chart-plot_chart.fn.liquid" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-chart-plot_instruction.liquid" />
<None Remove="data\agents\c2a2faf6-b8b5-47fe-807b-f4714cf25dd4\templates\chart-js-generate_instruction.liquid" />
</ItemGroup>
<ItemGroup>
@ -23,7 +23,7 @@
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-chart-plot_chart.fn.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-chart-plot_instruction.liquid">
<Content Include="data\agents\c2a2faf6-b8b5-47fe-807b-f4714cf25dd4\templates\chart-js-generate_instruction.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>

View file

@ -123,8 +123,8 @@ public class PlotChartFn : IFunctionCallback
}
else
{
templateName = "util-chart-plot_instruction";
templateContent = db.GetAgentTemplate(BuiltInAgentId.UtilityAssistant, templateName);
templateName = "chart-js-generate_instruction";
templateContent = db.GetAgentTemplate(BuiltInAgentId.AIProgrammer, templateName);
}
return templateContent;

View file

@ -19,6 +19,7 @@
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-file-read_pdf.fn.liquid" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-file-edit_image.json" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-file-edit_image.fn.liquid" />
<None Remove="data\agents\f1e09a73-9efe-46ce-ba02-b3aaf96d97e0\templates\select-chat-file_instruction.liquid" />
</ItemGroup>
<ItemGroup>
@ -46,6 +47,9 @@
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-file-edit_image.fn.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\f1e09a73-9efe-46ce-ba02-b3aaf96d97e0\templates\select-chat-file_instruction.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>

View file

@ -11,7 +11,9 @@
</PropertyGroup>
<ItemGroup>
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-code-python_generate_instruction.liquid" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-code-python_programmer.json" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-code-python_programmer.fn.liquid" />
<None Remove="data\agents\c2a2faf6-b8b5-47fe-807b-f4714cf25dd4\templates\py-code_generate_instruction.liquid" />
</ItemGroup>
<ItemGroup>
@ -21,7 +23,7 @@
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-code-python_programmer.fn.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-code-python_generate_instruction.liquid">
<Content Include="data\agents\c2a2faf6-b8b5-47fe-807b-f4714cf25dd4\templates\py-code_generate_instruction.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>

View file

@ -8,7 +8,7 @@ namespace BotSharp.Plugin.PythonInterpreter.Functions;
public class PyProgrammerFn : IFunctionCallback
{
public string Name => "util-code-python_programmer";
public string Indication => "Programming and executing code";
public string Indication => "Coding";
private readonly IServiceProvider _services;
private readonly ILogger<PyProgrammerFn> _logger;
@ -66,30 +66,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 +80,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)
@ -148,8 +177,8 @@ public class PyProgrammerFn : IFunctionCallback
}
else
{
templateName = "util-code-python_generate_instruction";
templateContent = db.GetAgentTemplate(BuiltInAgentId.UtilityAssistant, templateName);
templateName = "py-code_generate_instruction";
templateContent = db.GetAgentTemplate(BuiltInAgentId.AIProgrammer, templateName);
}
return templateContent;

View file

@ -29,6 +29,7 @@ public class PythonInterpreterPlugin : IBotSharpAppPlugin
{
var sp = app.ApplicationServices;
var settings = sp.GetRequiredService<PythonInterpreterSettings>();
var lifetime = app.ApplicationServices.GetRequiredService<IHostApplicationLifetime>();
var logger = sp.GetRequiredService<ILogger<PyProgrammerFn>>();
var pyLoc = settings.InstallLocation;
@ -38,12 +39,19 @@ public class PythonInterpreterPlugin : IBotSharpAppPlugin
{
Runtime.PythonDLL = pyLoc;
PythonEngine.Initialize();
#if DEBUG
_pyState = PythonEngine.BeginAllowThreads();
#endif
var lifetime = app.ApplicationServices.GetRequiredService<IHostApplicationLifetime>();
lifetime.ApplicationStopping.Register(() => {
try
{
#if DEBUG
PythonEngine.EndAllowThreads(_pyState);
#endif
PythonEngine.Shutdown();
}
catch { }
});
}
else

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,20 +10,52 @@ 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?.UseMutex == 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
{
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())
{
@ -29,6 +63,8 @@ public class PyInterpretService : ICodeInterpretService
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;
@ -45,7 +81,7 @@ public class PyInterpretService : ICodeInterpretService
var list = new PyList();
if (options?.Arguments?.Any() == true)
{
list.Append(new PyString("code.py"));
list.Append(new PyString(options?.ScriptName ?? "script.py"));
foreach (var arg in options.Arguments)
{
@ -64,21 +100,15 @@ 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}";
var errorMsg = $"Error when executing core python code in {nameof(PyInterpretService)}: {Provider}. {ex.Message}";
_logger.LogError(ex, errorMsg);
return new CodeInterpretResult
@ -87,5 +117,13 @@ public class PyInterpretService : ICodeInterpretService
ErrorMsg = errorMsg
};
}
finally
{
// Restore the original stdout/stderr/argv
sys.stdout = sys.__stdout__;
sys.stderr = sys.__stderr__;
sys.argv = new PyList();
}
}
}
}

View file

@ -1,4 +1,4 @@
You are a Python code generator that can produce python code to fulfill user's requirement.
You are a Python Coding Assistant that can produce python code to fulfill user's requirement.
Please read {% if user_requirement != empty %}"User Requirement" and{% endif %} the chat context, and then generate valid python code that can fulfill user's requirement.
You must strictly follow the "Hard Requirements", "Code Requirements", and "Response Format" below.