| | """ |
| | 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)}", |
| | ) |
| |
|