add package handling

This commit is contained in:
Jicheng Lu 2025-09-29 17:58:00 -05:00
parent a5bf44a44a
commit ed902b0f57
8 changed files with 215 additions and 9 deletions

View file

@ -108,7 +108,9 @@ public class PyProgrammerFn : IFunctionCallback
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error when executing python code.");
var errorMsg = $"Error when executing python code.";
message.Content = $"{errorMsg} {ex.Message}";
_logger.LogError(ex, errorMsg);
}
return true;

View file

@ -0,0 +1,178 @@
using System.Diagnostics;
using System.Threading.Tasks;
namespace BotSharp.Plugin.PythonInterpreter.Helpers;
internal static class PyPackageHelper
{
/// <summary>
/// Install python packages
/// </summary>
/// <param name="packages"></param>
/// <returns></returns>
internal static async Task<PackageInstallResult> InstallPackages(List<string>? packages)
{
if (packages.IsNullOrEmpty())
{
return new PackageInstallResult { Success = true };
}
try
{
var packageList = string.Join(" ", packages);
var startInfo = new ProcessStartInfo
{
FileName = "pip",
Arguments = $"install {packageList}",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
using var process = Process.Start(startInfo);
if (process == null)
{
return new PackageInstallResult
{
Success = false,
ErrorMsg = "Failed to start pip process"
};
}
await process.WaitForExitAsync();
var output = await process.StandardOutput.ReadToEndAsync();
var error = await process.StandardError.ReadToEndAsync();
if (process.ExitCode == 0)
{
return new PackageInstallResult { Success = true };
}
else
{
var errorMsg = $"Failed to install packages. Exit code: {process.ExitCode}. Error: {error}";
return new PackageInstallResult
{
Success = false,
ErrorMsg = errorMsg
};
}
}
catch (Exception ex)
{
var errorMsg = $"Exception occurred while installing packages: {ex.Message}";
return new PackageInstallResult
{
Success = false,
ErrorMsg = errorMsg
};
}
}
/// <summary>
/// Get packages that are not installed
/// </summary>
/// <param name="packages"></param>
/// <returns></returns>
public static async Task<List<string>> GetUninstalledPackages(List<string>? packages)
{
var missingPackages = new List<string>();
if (packages.IsNullOrEmpty())
{
return missingPackages;
}
try
{
var installedPackages = await GetInstalledPackages();
foreach (var package in packages)
{
// Check for common package name mappings
var mappedPackageName = MapToPackageName(package);
var isInstalled = installedPackages.Any(x => x.IsEqualTo(mappedPackageName));
if (!isInstalled)
{
missingPackages.Add(mappedPackageName);
}
}
return missingPackages;
}
catch (Exception ex)
{
throw;
}
}
private static async Task<List<string>> GetInstalledPackages()
{
try
{
var startInfo = new ProcessStartInfo
{
FileName = "pip",
Arguments = "list --format=freeze",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
using var process = Process.Start(startInfo);
if (process == null)
{
throw new InvalidOperationException("Failed to start pip process");
}
await process.WaitForExitAsync();
var output = await process.StandardOutput.ReadToEndAsync();
if (process.ExitCode != 0)
{
var error = await process.StandardError.ReadToEndAsync();
throw new InvalidOperationException($"pip list failed with exit code {process.ExitCode}: {error}");
}
// Parse pip list output (format: package==version)
var packages = output.Split("\n", StringSplitOptions.RemoveEmptyEntries)
.Select(line => line.Split("==", StringSplitOptions.None)[0].Trim().ToLowerInvariant())
.Where(pkg => !string.IsNullOrEmpty(pkg))
.ToList();
return packages;
}
catch (Exception ex)
{
throw;
}
}
/// <summary>
/// Map common import name to actual package name
/// </summary>
/// <param name="importName"></param>
/// <returns></returns>
private static string MapToPackageName(string importName)
{
var packageMappings = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "cv2", "opencv-python" },
{ "PIL", "Pillow" },
{ "sklearn", "scikit-learn" },
{ "yaml", "PyYAML" },
{ "bs4", "beautifulsoup4" },
{ "dateutil", "python-dateutil" },
{ "serial", "pyserial" },
{ "psutil", "psutil" },
{ "requests", "requests" },
{ "numpy", "numpy" },
{ "pandas", "pandas" },
{ "matplotlib", "matplotlib" },
{ "scipy", "scipy" },
{ "seaborn", "seaborn" },
{ "plotly", "plotly" }
};
return packageMappings.TryGetValue(importName, out var actualName) ? actualName : importName;
}
}

View file

@ -5,5 +5,8 @@ namespace BotSharp.Plugin.PythonInterpreter.LlmContext;
public class LlmContextIn
{
[JsonPropertyName("user_requirement")]
public string UserRquirement { get; set; }
public string UserRquirement { get; set; } = string.Empty;
[JsonPropertyName("imported_packages")]
public List<string> ImportedPackages { get; set; } = [];
}

View file

@ -6,4 +6,7 @@ public class LlmContextOut
{
[JsonPropertyName("python_code")]
public string PythonCode { get; set; }
[JsonPropertyName("imported_packages")]
public List<string>? ImportedPackages { get; set; }
}

View file

@ -0,0 +1,7 @@
namespace BotSharp.Plugin.PythonInterpreter.Models;
internal class PackageInstallResult
{
internal bool Success { get; set; }
internal string ErrorMsg { get; set; }
}

View file

@ -28,3 +28,5 @@ global using BotSharp.Plugin.PythonInterpreter.LlmContext;
global using BotSharp.Plugin.PythonInterpreter.Settings;
global using BotSharp.Plugin.PythonInterpreter.Functions;
global using BotSharp.Plugin.PythonInterpreter.Hooks;
global using BotSharp.Plugin.PythonInterpreter.Models;
global using BotSharp.Plugin.PythonInterpreter.Helpers;

View file

@ -8,11 +8,12 @@ You must strictly follow the "Hard Requirements", "Code Requirements", and "Resp
***** 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.
1. You must import packages at the beginning of the code. DO NOT include any import statement in the middle of the functions.
2. Your output python code must be well constructed inside one or multiple functions.
3. You must not include any code, explanations, or comments outside these functions.
4. Do not include explanations, comments outside code.
5. You must use print() once to output the final result only. Do not print intermediate values, logs, or debug info.
6. If randomness is required, set a fixed seed inside a function.
***** Code Requirements *****
@ -26,14 +27,21 @@ You must strictly follow the "Hard Requirements", "Code Requirements", and "Resp
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.
b). Do not apply try/except block for package import.
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 }}"
6. Package Usage
a). Prefer using Python standard library modules when possible (os, sys, json, csv, math, statistics, datetime, etc.)
b). Only use external packages (like pandas, numpy, matplotlib) when absolutely necessary for the task
c). If using external packages, provide fallback implementations using standard library when feasible
d). Always include external packages in the "imported_packages" list in your response
***** Response Format *****
You must output the response in the following JSON format:
{
"python_code": "The python code that can fulfill user's request."
"python_code": "The python code that can fulfill user's request.",
"imported_packages": a list of strings that contains the packages that are imported in the code you generated.
}

View file

@ -567,7 +567,10 @@
"PythonInterpreter": {
"InstallLocation": "C:/Users/xxx/AppData/Local/Programs/Python/Python313/python313.dll",
"PythonVersion": "3.13",
"PythonVersion": "3.13.3",
"AutoInstallPackages": false,
"AllowedPackages": [ "pandas", "numpy", "matplotlib", "requests", "beautifulsoup4", "scipy", "seaborn", "plotly" ],
"EnableFallbackGeneration": true,
"CodeGeneration": {
"LlmProvider": "openai",
"LlmModel": "gpt-5",