refine py code generation

This commit is contained in:
Jicheng Lu 2025-09-29 15:07:54 -05:00
parent 09c008f76f
commit 9abe315952
16 changed files with 160 additions and 93 deletions

View file

@ -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!;
}

View file

@ -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; }
}

View file

@ -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;

View file

@ -11,16 +11,14 @@
</PropertyGroup>
<ItemGroup>
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-code-python_interpreter.json" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-code-python_interpreter.fn.liquid" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-code-python_generate_instruction.liquid" />
</ItemGroup>
<ItemGroup>
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-code-python_interpreter.json">
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-code-python_programmer.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-code-python_interpreter.fn.liquid">
<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">

View file

@ -2,5 +2,5 @@ namespace BotSharp.Plugin.PythonInterpreter.Enums;
public class UtilityName
{
public const string PythonInterpreter = "python-interpreter";
public const string PythonProgrammer = "python-programmer";
}

View file

@ -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<PyInterpretationFn> _logger;
private readonly ILogger<PyProgrammerFn> _logger;
private readonly PythonInterpreterSettings _settings;
public PyInterpretationFn(
public PyProgrammerFn(
IServiceProvider services,
ILogger<PyInterpretationFn> logger,
ILogger<PyProgrammerFn> logger,
PythonInterpreterSettings settings)
{
_services = services;
@ -44,6 +42,7 @@ public class PyInterpretationFn : IFunctionCallback
LlmConfig = GetLlmConfig(),
TemplateDict = new Dictionary<string, object>
{
{ "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<LlmContextOut>();
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<IRichMessage>
{
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<IConversationStateService>();
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<IConversationStateService>();
maxOutputTokens = int.TryParse(state.GetState("py_intepreter_max_output_tokens"), out var tokens) ? tokens : maxOutputTokens;

View file

@ -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<AgentUtility> 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);
}
}

View file

@ -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<AgentUtility> 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);
}
}

View file

@ -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<ISettingService>();
return settingService.Bind<PythonInterpreterSettings>("PythonInterpreter");
});
var settings = new PythonInterpreterSettings();
config.Bind("PythonInterpreter", settings);
services.AddSingleton(x => settings);
services.AddScoped<IAgentUtilityHook, PythonInterpreterUtilityHook>();
services.AddScoped<IAgentUtilityHook, PyProgrammerUtilityHook>();
}
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<IHostApplicationLifetime>();
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();
}
}

View file

@ -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; }
}

View file

@ -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;

View file

@ -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": {

View file

@ -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 variables 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."
}

View file

@ -1 +0,0 @@
Please call function util-code-python_interpreter if user wants to generate python code to complete tasks.

View file

@ -0,0 +1 @@
Please call function util-code-python_interpreter if you think it is necessary to fulfill user's request through python code.

View file

@ -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": {