From 9abe3159523a81490ae5b96836097ee01e1afa61 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Mon, 29 Sep 2025 15:07:54 -0500 Subject: [PATCH] refine py code generation --- .../Models/InterpretationRequest.cs | 10 --- .../Settings/InterpreterSettings.cs | 11 --- .../BotSharp.Core/BotSharpCoreExtensions.cs | 1 - .../BotSharp.Plugin.PythonInterpreter.csproj | 6 +- .../Enums/UtilityName.cs | 2 +- ...yInterpretationFn.cs => PyProgrammerFn.cs} | 79 +++++++++++++------ .../Hooks/PyProgrammerUtilityHook.cs | 24 ++++++ .../Hooks/PythonInterpreterUtilityHook.cs | 24 ------ .../PythonInterpreterPlugin.cs | 27 ++++--- .../Settings/PythonInterpreterSettings.cs | 9 +++ .../Using.cs | 4 + ....json => util-code-python_programmer.json} | 2 +- ...il-code-python_generate_instruction.liquid | 39 +++++++++ .../util-code-python_interpreter.fn.liquid | 1 - .../util-code-python_programmer.fn.liquid | 1 + src/WebStarter/appsettings.json | 13 ++- 16 files changed, 160 insertions(+), 93 deletions(-) delete mode 100644 src/Infrastructure/BotSharp.Abstraction/Interpreters/Models/InterpretationRequest.cs delete mode 100644 src/Infrastructure/BotSharp.Abstraction/Interpreters/Settings/InterpreterSettings.cs rename src/Plugins/BotSharp.Plugin.PythonInterpreter/Functions/{PyInterpretationFn.cs => PyProgrammerFn.cs} (63%) create mode 100644 src/Plugins/BotSharp.Plugin.PythonInterpreter/Hooks/PyProgrammerUtilityHook.cs delete mode 100644 src/Plugins/BotSharp.Plugin.PythonInterpreter/Hooks/PythonInterpreterUtilityHook.cs rename src/Plugins/BotSharp.Plugin.PythonInterpreter/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/{util-code-python_interpreter.json => util-code-python_programmer.json} (66%) delete mode 100644 src/Plugins/BotSharp.Plugin.PythonInterpreter/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-code-python_interpreter.fn.liquid create mode 100644 src/Plugins/BotSharp.Plugin.PythonInterpreter/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-code-python_programmer.fn.liquid diff --git a/src/Infrastructure/BotSharp.Abstraction/Interpreters/Models/InterpretationRequest.cs b/src/Infrastructure/BotSharp.Abstraction/Interpreters/Models/InterpretationRequest.cs deleted file mode 100644 index 5a8f7552..00000000 --- a/src/Infrastructure/BotSharp.Abstraction/Interpreters/Models/InterpretationRequest.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace BotSharp.Abstraction.Interpreters.Models; - -public class InterpretationRequest -{ - [JsonPropertyName("script")] - public string Script { get; set; } = null!; - - [JsonPropertyName("language")] - public string Language { get; set; } = null!; -} diff --git a/src/Infrastructure/BotSharp.Abstraction/Interpreters/Settings/InterpreterSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Interpreters/Settings/InterpreterSettings.cs deleted file mode 100644 index e6ea6cf3..00000000 --- a/src/Infrastructure/BotSharp.Abstraction/Interpreters/Settings/InterpreterSettings.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace BotSharp.Abstraction.Interpreters.Settings; - -public class InterpreterSettings -{ - public PythonInterpreterSetting Python { get; set; } -} - -public class PythonInterpreterSetting -{ - public string PythonDLL { get; set; } -} diff --git a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs index a3fa3965..9c34de59 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs +++ b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs @@ -6,7 +6,6 @@ using BotSharp.Abstraction.Settings; using BotSharp.Abstraction.Options; using BotSharp.Abstraction.Messaging.JsonConverters; using BotSharp.Abstraction.Users.Settings; -using BotSharp.Abstraction.Interpreters.Settings; using BotSharp.Abstraction.Infrastructures; using BotSharp.Core.Processors; using StackExchange.Redis; diff --git a/src/Plugins/BotSharp.Plugin.PythonInterpreter/BotSharp.Plugin.PythonInterpreter.csproj b/src/Plugins/BotSharp.Plugin.PythonInterpreter/BotSharp.Plugin.PythonInterpreter.csproj index db85df01..8b0775e8 100644 --- a/src/Plugins/BotSharp.Plugin.PythonInterpreter/BotSharp.Plugin.PythonInterpreter.csproj +++ b/src/Plugins/BotSharp.Plugin.PythonInterpreter/BotSharp.Plugin.PythonInterpreter.csproj @@ -11,16 +11,14 @@ - - - + PreserveNewest - + PreserveNewest diff --git a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Enums/UtilityName.cs b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Enums/UtilityName.cs index 95a5b8a2..813031af 100644 --- a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Enums/UtilityName.cs +++ b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Enums/UtilityName.cs @@ -2,5 +2,5 @@ namespace BotSharp.Plugin.PythonInterpreter.Enums; public class UtilityName { - public const string PythonInterpreter = "python-interpreter"; + public const string PythonProgrammer = "python-programmer"; } diff --git a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Functions/PyInterpretationFn.cs b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Functions/PyProgrammerFn.cs similarity index 63% rename from src/Plugins/BotSharp.Plugin.PythonInterpreter/Functions/PyInterpretationFn.cs rename to src/Plugins/BotSharp.Plugin.PythonInterpreter/Functions/PyProgrammerFn.cs index f3876e8f..c44bf5cf 100644 --- a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Functions/PyInterpretationFn.cs +++ b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Functions/PyProgrammerFn.cs @@ -1,24 +1,22 @@ -using BotSharp.Abstraction.Routing; using Microsoft.Extensions.Logging; using Python.Runtime; -using System.Runtime; using System.Text.Json; using System.Threading.Tasks; namespace BotSharp.Plugin.PythonInterpreter.Functions; -public class PyInterpretationFn : IFunctionCallback +public class PyProgrammerFn : IFunctionCallback { - public string Name => "util-code-python_interpreter"; - public string Indication => "Executing python code"; + public string Name => "util-code-python_programmer"; + public string Indication => "Programming and executing code"; private readonly IServiceProvider _services; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly PythonInterpreterSettings _settings; - public PyInterpretationFn( + public PyProgrammerFn( IServiceProvider services, - ILogger logger, + ILogger logger, PythonInterpreterSettings settings) { _services = services; @@ -44,6 +42,7 @@ public class PyInterpretationFn : IFunctionCallback LlmConfig = GetLlmConfig(), TemplateDict = new Dictionary { + { "python_version", _settings.PythonVersion ?? "3.11" }, { "user_requirement", args?.UserRquirement ?? string.Empty } } }; @@ -54,6 +53,8 @@ public class PyInterpretationFn : IFunctionCallback dialogs = convService.GetDialogHistory(); } + var messageLimit = _settings.CodeGeneration?.MessageLimit > 0 ? _settings.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.") { CurrentAgentId = message.CurrentAgentId, @@ -63,25 +64,51 @@ public class PyInterpretationFn : IFunctionCallback var response = await GetChatCompletion(innerAgent, dialogs); var ret = response.JsonContent(); - using (Py.GIL()) + try { - // Import necessary Python modules - dynamic sys = Py.Import("sys"); - dynamic io = Py.Import("io"); + using (Py.GIL()) + { + // Import necessary Python modules + dynamic sys = Py.Import("sys"); + dynamic io = Py.Import("io"); - // Redirect standard output to capture it - dynamic stringIO = io.StringIO(); - sys.stdout = stringIO; + // Redirect standard output/error to capture it + dynamic stringIO = io.StringIO(); + sys.stdout = stringIO; + sys.stderr = stringIO; - // Execute a simple Python script - using var locals = new PyDict(); - PythonEngine.Exec(ret.PythonCode, null, locals); + // Set global items + using var globals = new PyDict(); + if (ret.PythonCode?.Contains("__main__") == true) + { + globals.SetItem("__name__", new PyString("__main__")); + } - // Console.WriteLine($"Result from Python: {result}"); - message.Content = stringIO.getvalue(); + // Execute Python script + PythonEngine.Exec(ret.PythonCode, globals); - // Restore the original stdout - sys.stdout = sys.__stdout__; + // Get result + var result = stringIO.getvalue().ToString(); + message.Content = result; + message.RichContent = new RichContent + { + Recipient = new Recipient { Id = convService.ConversationId }, + Message = new ProgramCodeTemplateMessage + { + Text = ret.PythonCode ?? string.Empty, + Language = "python" + } + }; + message.StopCompletion = true; + + // Restore the original stdout/stderr + sys.stdout = sys.__stdout__; + sys.stderr = sys.__stderr__; + } + } + catch (Exception ex) + { + _logger.LogError(ex, $"Error when executing python code."); } return true; @@ -132,10 +159,10 @@ public class PyInterpretationFn : IFunctionCallback var state = _services.GetRequiredService(); provider = state.GetState("py_intepreter_llm_provider") - //.IfNullOrEmptyAs(_settings.ChartPlot?.LlmProvider) + .IfNullOrEmptyAs(_settings.CodeGeneration?.LlmProvider) .IfNullOrEmptyAs(provider); model = state.GetState("py_intepreter_llm_model") - //.IfNullOrEmptyAs(_settings.ChartPlot?.LlmModel) + .IfNullOrEmptyAs(_settings.CodeGeneration?.LlmModel) .IfNullOrEmptyAs(model); return (provider, model); @@ -143,8 +170,8 @@ public class PyInterpretationFn : IFunctionCallback private AgentLlmConfig GetLlmConfig() { - var maxOutputTokens = 8192; - var reasoningEffortLevel = "minimal"; + var maxOutputTokens = _settings?.CodeGeneration?.MaxOutputTokens ?? 8192; + var reasoningEffortLevel = _settings?.CodeGeneration?.ReasoningEffortLevel ?? "minimal"; var state = _services.GetRequiredService(); maxOutputTokens = int.TryParse(state.GetState("py_intepreter_max_output_tokens"), out var tokens) ? tokens : maxOutputTokens; diff --git a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Hooks/PyProgrammerUtilityHook.cs b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Hooks/PyProgrammerUtilityHook.cs new file mode 100644 index 00000000..0866478a --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Hooks/PyProgrammerUtilityHook.cs @@ -0,0 +1,24 @@ +namespace BotSharp.Plugin.PythonInterpreter.Hooks; + +public class PyProgrammerUtilityHook : IAgentUtilityHook +{ + private const string PY_PROGRAMMER_FN = "util-code-python_programmer"; + + public void AddUtilities(List utilities) + { + var utility = new AgentUtility() + { + Category = "code", + Name = UtilityName.PythonProgrammer, + Items = [ + new UtilityItem + { + FunctionName = PY_PROGRAMMER_FN, + TemplateName = $"{PY_PROGRAMMER_FN}.fn" + } + ] + }; + + utilities.Add(utility); + } +} diff --git a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Hooks/PythonInterpreterUtilityHook.cs b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Hooks/PythonInterpreterUtilityHook.cs deleted file mode 100644 index 2d243e35..00000000 --- a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Hooks/PythonInterpreterUtilityHook.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace BotSharp.Plugin.PythonInterpreter.Hooks; - -public class PythonInterpreterUtilityHook : IAgentUtilityHook -{ - private const string PY_INTERPRETER_FN = "util-code-python_interpreter"; - - public void AddUtilities(List utilities) - { - var utility = new AgentUtility() - { - Category = "coding", - Name = UtilityName.PythonInterpreter, - Items = [ - new UtilityItem - { - FunctionName = PY_INTERPRETER_FN, - TemplateName = $"{PY_INTERPRETER_FN}.fn" - } - ] - }; - - utilities.Add(utility); - } -} diff --git a/src/Plugins/BotSharp.Plugin.PythonInterpreter/PythonInterpreterPlugin.cs b/src/Plugins/BotSharp.Plugin.PythonInterpreter/PythonInterpreterPlugin.cs index e1c1bd63..ed55ae4c 100644 --- a/src/Plugins/BotSharp.Plugin.PythonInterpreter/PythonInterpreterPlugin.cs +++ b/src/Plugins/BotSharp.Plugin.PythonInterpreter/PythonInterpreterPlugin.cs @@ -1,6 +1,6 @@ -using BotSharp.Abstraction.Settings; using BotSharp.Plugin.PythonInterpreter.Hooks; using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Hosting; using Python.Runtime; using System.IO; @@ -13,15 +13,15 @@ public class PythonInterpreterPlugin : IBotSharpAppPlugin public string Description => "Python Interpreter enables AI to write and execute Python code within a secure, sandboxed environment."; public string? IconUrl => "https://static.vecteezy.com/system/resources/previews/012/697/295/non_2x/3d-python-programming-language-logo-free-png.png"; + private nint _pyState; + public void RegisterDI(IServiceCollection services, IConfiguration config) { - services.AddSingleton(provider => - { - var settingService = provider.GetRequiredService(); - return settingService.Bind("PythonInterpreter"); - }); + var settings = new PythonInterpreterSettings(); + config.Bind("PythonInterpreter", settings); + services.AddSingleton(x => settings); - services.AddScoped(); + services.AddScoped(); } public void Configure(IApplicationBuilder app) @@ -33,14 +33,17 @@ public class PythonInterpreterPlugin : IBotSharpAppPlugin { Runtime.PythonDLL = settings.DllLocation; PythonEngine.Initialize(); - PythonEngine.BeginAllowThreads(); + _pyState = PythonEngine.BeginAllowThreads(); + + var lifetime = app.ApplicationServices.GetRequiredService(); + lifetime.ApplicationStopping.Register(() => { + PythonEngine.EndAllowThreads(_pyState); + PythonEngine.Shutdown(); + }); } else { - Serilog.Log.Error("Python DLL found at {PythonDLL}", settings.DllLocation); + Serilog.Log.Error($"Python DLL found at {settings.DllLocation}"); } - - // Shut down the Python engine - // PythonEngine.Shutdown(); } } diff --git a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Settings/PythonInterpreterSettings.cs b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Settings/PythonInterpreterSettings.cs index 01f5a9d1..7e380ba1 100644 --- a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Settings/PythonInterpreterSettings.cs +++ b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Settings/PythonInterpreterSettings.cs @@ -1,6 +1,15 @@ +using BotSharp.Abstraction.Models; + namespace BotSharp.Plugin.PythonInterpreter.Settings; public class PythonInterpreterSettings { public string DllLocation { 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 88ed52e6..84bac034 100644 --- a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Using.cs +++ b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Using.cs @@ -16,6 +16,10 @@ global using BotSharp.Abstraction.Functions.Models; global using BotSharp.Abstraction.Repositories; global using BotSharp.Abstraction.Conversations.Models; global using BotSharp.Abstraction.Functions; +global using BotSharp.Abstraction.Messaging; +global using BotSharp.Abstraction.Messaging.Models.RichContent; +global using BotSharp.Abstraction.Messaging.Models.RichContent.Template; +global using BotSharp.Abstraction.Routing; global using BotSharp.Core.Infrastructures; diff --git a/src/Plugins/BotSharp.Plugin.PythonInterpreter/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-code-python_interpreter.json b/src/Plugins/BotSharp.Plugin.PythonInterpreter/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-code-python_programmer.json similarity index 66% rename from src/Plugins/BotSharp.Plugin.PythonInterpreter/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-code-python_interpreter.json rename to src/Plugins/BotSharp.Plugin.PythonInterpreter/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-code-python_programmer.json index ee5a708e..c9aabc20 100644 --- a/src/Plugins/BotSharp.Plugin.PythonInterpreter/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-code-python_interpreter.json +++ b/src/Plugins/BotSharp.Plugin.PythonInterpreter/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-code-python_programmer.json @@ -1,6 +1,6 @@ { "name": "util-code-python_interpreter", - "description": "If the user requests you generating python code to complete tasks, you can call this function to generate python code to execute.", + "description": "If user's requirement can be fulfilled by python code, you can call this function to generate python code to execute.", "parameters": { "type": "object", "properties": { diff --git a/src/Plugins/BotSharp.Plugin.PythonInterpreter/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-code-python_generate_instruction.liquid b/src/Plugins/BotSharp.Plugin.PythonInterpreter/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-code-python_generate_instruction.liquid index e69de29b..3ac455a1 100644 --- a/src/Plugins/BotSharp.Plugin.PythonInterpreter/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-code-python_generate_instruction.liquid +++ b/src/Plugins/BotSharp.Plugin.PythonInterpreter/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-code-python_generate_instruction.liquid @@ -0,0 +1,39 @@ +You are a Python code generator that can produce python code to fulfill user's requirement. +Please read "User Requirement" and 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. + +=== User Requirement === +{{ user_requirement }} + + +***** Hard Requirements ***** +1. Your output python code must be well constructed inside one or multiple functions. +2. You must not include any code, explanations, or comments outside these functions. +3. Do not include explanations, comments outside code. +4. You must use print() once to output the final result only. Do not print intermediate values, logs, or debug info. +5. If randomness is required, set a fixed seed inside a function. + + +***** Code Requirements ***** +1. Structure + a). You need to create small and focused functions. + b). You need to include a main() function to orchestrates these steps. + c). You must only call main() in "if __name__ == '__main__'" block. +2. Data type & Validation + a). When unsure about a variable’s type, check it at runtime (e.g., isinstance) before applying methods. + b). Validate inputs and handle edge cases, such as empty lists, division by zero, out-of-range values. + c). Except the main() function, it is preferable to generate functions that accept parameters and return values. +3. Error handling + a). If necessary, use try/except block inside main() to catch any errors and produce a single final printed message. +4. Output + a). You must print the final result once in main(). +5. Compatibility + a). You must keep the code compatible with "Python {{ python_version }}" + + +***** Response Format ***** +You must output the response in the following JSON format: +{ + "python_code": "The python code that can fulfill user's request." +} \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.PythonInterpreter/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-code-python_interpreter.fn.liquid b/src/Plugins/BotSharp.Plugin.PythonInterpreter/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-code-python_interpreter.fn.liquid deleted file mode 100644 index 9037aaff..00000000 --- a/src/Plugins/BotSharp.Plugin.PythonInterpreter/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-code-python_interpreter.fn.liquid +++ /dev/null @@ -1 +0,0 @@ -Please call function util-code-python_interpreter if user wants to generate python code to complete tasks. \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.PythonInterpreter/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-code-python_programmer.fn.liquid b/src/Plugins/BotSharp.Plugin.PythonInterpreter/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-code-python_programmer.fn.liquid new file mode 100644 index 00000000..604e986f --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.PythonInterpreter/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-code-python_programmer.fn.liquid @@ -0,0 +1 @@ +Please call function util-code-python_interpreter if you think it is necessary to fulfill user's request through python code. \ No newline at end of file diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json index f00c4899..65b78111 100644 --- a/src/WebStarter/appsettings.json +++ b/src/WebStarter/appsettings.json @@ -488,7 +488,8 @@ "LlmProvider": "openai", "LlmModel": "gpt-5", "MaxOutputTokens": 8192, - "ReasoningEffortLevel": "minimal" + "ReasoningEffortLevel": "minimal", + "MessageLimit": 50 } }, @@ -565,7 +566,15 @@ }, "PythonInterpreter": { - "DllLocation": "C:/Users/xxx/AppData/Local/Programs/Python/Python313/python313.dll" + "DllLocation": "C:/Users/xxx/AppData/Local/Programs/Python/Python313/python313.dll", + "PythonVersion": "3.13", + "CodeGeneration": { + "LlmProvider": "openai", + "LlmModel": "gpt-5", + "MaxOutputTokens": 8192, + "ReasoningEffortLevel": "minimal", + "MessageLimit": 50 + } }, "RealtimeModel": {