|
|
""" |
|
|
Code Execution Tools for SPARKNET |
|
|
Tools for executing Python and bash code |
|
|
""" |
|
|
|
|
|
import subprocess |
|
|
import sys |
|
|
from io import StringIO |
|
|
from contextlib import redirect_stdout, redirect_stderr |
|
|
from typing import Optional |
|
|
from loguru import logger |
|
|
from .base_tool import BaseTool, ToolResult |
|
|
|
|
|
|
|
|
class PythonExecutorTool(BaseTool): |
|
|
"""Tool for executing Python code.""" |
|
|
|
|
|
def __init__(self, sandbox: bool = True): |
|
|
super().__init__( |
|
|
name="python_executor", |
|
|
description="Execute Python code and return the output", |
|
|
) |
|
|
self.sandbox = sandbox |
|
|
self.add_parameter("code", "str", "Python code to execute", required=True) |
|
|
self.add_parameter("timeout", "int", "Execution timeout in seconds", required=False, default=30) |
|
|
|
|
|
async def execute(self, code: str, timeout: int = 30, **kwargs) -> ToolResult: |
|
|
""" |
|
|
Execute Python code. |
|
|
|
|
|
Args: |
|
|
code: Python code to execute |
|
|
timeout: Execution timeout |
|
|
|
|
|
Returns: |
|
|
ToolResult with execution output |
|
|
""" |
|
|
try: |
|
|
|
|
|
stdout_capture = StringIO() |
|
|
stderr_capture = StringIO() |
|
|
|
|
|
|
|
|
if self.sandbox: |
|
|
|
|
|
safe_builtins = { |
|
|
"print": print, |
|
|
"len": len, |
|
|
"range": range, |
|
|
"str": str, |
|
|
"int": int, |
|
|
"float": float, |
|
|
"bool": bool, |
|
|
"list": list, |
|
|
"dict": dict, |
|
|
"tuple": tuple, |
|
|
"set": set, |
|
|
"sum": sum, |
|
|
"min": min, |
|
|
"max": max, |
|
|
"abs": abs, |
|
|
"round": round, |
|
|
"enumerate": enumerate, |
|
|
"zip": zip, |
|
|
} |
|
|
namespace = {"__builtins__": safe_builtins} |
|
|
else: |
|
|
namespace = {} |
|
|
|
|
|
|
|
|
with redirect_stdout(stdout_capture), redirect_stderr(stderr_capture): |
|
|
exec(code, namespace) |
|
|
|
|
|
stdout_text = stdout_capture.getvalue() |
|
|
stderr_text = stderr_capture.getvalue() |
|
|
|
|
|
output = stdout_text |
|
|
if stderr_text: |
|
|
output += f"\nSTDERR:\n{stderr_text}" |
|
|
|
|
|
return ToolResult( |
|
|
success=True, |
|
|
output=output or "Code executed successfully (no output)", |
|
|
metadata={ |
|
|
"sandbox": self.sandbox, |
|
|
"stdout": stdout_text, |
|
|
"stderr": stderr_text, |
|
|
}, |
|
|
) |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"Python execution error: {e}") |
|
|
return ToolResult( |
|
|
success=False, |
|
|
output=None, |
|
|
error=f"Execution error: {str(e)}", |
|
|
) |
|
|
|
|
|
|
|
|
class BashExecutorTool(BaseTool): |
|
|
"""Tool for executing bash commands.""" |
|
|
|
|
|
def __init__(self, allowed_commands: Optional[list[str]] = None): |
|
|
super().__init__( |
|
|
name="bash_executor", |
|
|
description="Execute bash commands and return the output", |
|
|
) |
|
|
self.allowed_commands = allowed_commands |
|
|
self.add_parameter("command", "str", "Bash command to execute", required=True) |
|
|
self.add_parameter("timeout", "int", "Execution timeout in seconds", required=False, default=30) |
|
|
self.add_parameter("working_dir", "str", "Working directory", required=False, default=".") |
|
|
|
|
|
async def execute( |
|
|
self, |
|
|
command: str, |
|
|
timeout: int = 30, |
|
|
working_dir: str = ".", |
|
|
**kwargs, |
|
|
) -> ToolResult: |
|
|
""" |
|
|
Execute bash command. |
|
|
|
|
|
Args: |
|
|
command: Bash command to execute |
|
|
timeout: Execution timeout |
|
|
working_dir: Working directory |
|
|
|
|
|
Returns: |
|
|
ToolResult with command output |
|
|
""" |
|
|
try: |
|
|
|
|
|
if self.allowed_commands: |
|
|
cmd_name = command.split()[0] |
|
|
if cmd_name not in self.allowed_commands: |
|
|
return ToolResult( |
|
|
success=False, |
|
|
output=None, |
|
|
error=f"Command '{cmd_name}' not allowed. Allowed: {self.allowed_commands}", |
|
|
) |
|
|
|
|
|
|
|
|
result = subprocess.run( |
|
|
command, |
|
|
shell=True, |
|
|
capture_output=True, |
|
|
text=True, |
|
|
timeout=timeout, |
|
|
cwd=working_dir, |
|
|
) |
|
|
|
|
|
output = result.stdout |
|
|
if result.stderr: |
|
|
output += f"\nSTDERR:\n{result.stderr}" |
|
|
|
|
|
return ToolResult( |
|
|
success=result.returncode == 0, |
|
|
output=output or "(no output)", |
|
|
error=None if result.returncode == 0 else f"Command failed with code {result.returncode}", |
|
|
metadata={ |
|
|
"return_code": result.returncode, |
|
|
"stdout": result.stdout, |
|
|
"stderr": result.stderr, |
|
|
"command": command, |
|
|
}, |
|
|
) |
|
|
|
|
|
except subprocess.TimeoutExpired: |
|
|
return ToolResult( |
|
|
success=False, |
|
|
output=None, |
|
|
error=f"Command timed out after {timeout} seconds", |
|
|
) |
|
|
except Exception as e: |
|
|
logger.error(f"Bash execution error: {e}") |
|
|
return ToolResult( |
|
|
success=False, |
|
|
output=None, |
|
|
error=f"Execution error: {str(e)}", |
|
|
) |
|
|
|