File size: 3,761 Bytes
2d8433b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# Copyright (c) Meta Platforms, Inc. and affiliates.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree.

"""
Local Python Executor.

This module provides functionality for executing Python code locally by wrapping
the smolagents LocalPythonExecutor.
"""

from smolagents import LocalPythonExecutor

from core.env_server.types import CodeExecResult


class PyExecutor:
    """
    Wrapper around smolagents LocalPythonExecutor for executing Python code.

    This class provides a simple interface to execute Python code in a subprocess
    and capture the results including stdout, stderr, and exit code.

    Args:
        additional_imports: List of additional module imports to authorize.
                          For example: ["numpy", "pandas", "matplotlib"]
                          These will be added to the base authorized imports.

    Example:
        >>> # Basic usage with default imports
        >>> executor = PyExecutor()
        >>> result = executor.run("print('Hello, World!')")
        >>> print(result.stdout)  # "Hello, World!\n"
        >>> print(result.exit_code)  # 0
        >>>
        >>> # Usage with additional imports
        >>> executor = PyExecutor(additional_imports=["numpy", "pandas"])
        >>> result = executor.run("import numpy as np\\nprint(np.array([1, 2, 3]))")
        >>> print(result.stdout)  # "[1 2 3]\n"
    """

    def __init__(self, additional_imports: list[str] | None = None):
        """
        Initialize the PyExecutor with a LocalPythonExecutor instance.

        Args:
            additional_imports: List of additional module names to authorize for import.
                              Defaults to an empty list if not provided.
        """
        if additional_imports is None:
            additional_imports = []
        self._executor = LocalPythonExecutor(
            additional_authorized_imports=additional_imports
        )
        # Initialize tools to make BASE_PYTHON_TOOLS available (including print)
        self._executor.send_tools({})

    def run(self, code: str) -> CodeExecResult:
        """
        Execute Python code and return the result.

        Args:
            code: Python code string to execute

        Returns:
            CodeExecResult containing stdout, stderr, and exit_code

        Example:
            >>> executor = PyExecutor()
            >>> result = executor.run("x = 5 + 3\\nprint(x)")
            >>> print(result.stdout)  # "8\n"
            >>> print(result.exit_code)  # 0
            >>>
            >>> # Error handling
            >>> result = executor.run("1 / 0")
            >>> print(result.exit_code)  # 1
            >>> print(result.stderr)  # Contains error message
        """
        try:
            # Execute the code using LocalPythonExecutor
            # LocalPythonExecutor returns a CodeOutput object with output, logs, is_final_answer
            exec_result = self._executor(code)

            # Extract the logs (which contain print outputs) as stdout
            # The output field contains the return value of the code
            stdout = exec_result.logs
            stderr = ""
            exit_code = 0  # Success

            return CodeExecResult(
                stdout=stdout,
                stderr=stderr,
                exit_code=exit_code,
            )

        except Exception as e:
            # LocalPythonExecutor raises InterpreterError for various issues
            # (syntax errors, forbidden operations, runtime errors, etc.)
            return CodeExecResult(
                stdout="",
                stderr=str(e),
                exit_code=1,  # Non-zero indicates error
            )