File size: 4,777 Bytes
85bccd0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
from smolagents.tools import Tool
import pdb
import smolagents
import io
import sys

class LocalPythonDebuggerTool(Tool):
    name = "local-python-debugger"
    description = """
    The Local Python Debugger Tool is designed to assist in debugging InterpreterErrors—specifically, errors related to "Code execution failed at line". 
    It is not intended for other types of InterpreterErrors, such as:
    - "Reached the max number of operations"
    - "Code parsing failed on line"

    The tool debugs code executed in the following manner:
    ```python
    expression = ast.parse(code)
    for node in expression.body:
        result = evaluate_ast(node, ...)
    ```
    where evaluate_ast processes the Abstract Syntax Tree (AST) by evaluating AST functions like: ast.Assign, ast.AugAssign, ast.Call, ast.Constant, ast.Name, ast.BinOp, etc.
    As a result, the debugger output reflects the evaluate_ast execution context rather than a direct Python runtime environment.
    
    Possible debugger commands:
    - bt - Shows the full stack trace, helping to understand where the error occurred in the call stack.
    - args - Shows the arguments passed to the function.
    - l - Lists the source code of the current file.
    - p {variable} - Prints the value of the variable. NOTE: variable names are different than in the code. They should be inspected first with `l` command.

    """
    inputs = {'code': {'type': 'string', 'description': 'code to debug'}, 'command': {'type': 'string', 'description': 'pdb command to execute'}}
    output_type = "string"

    def setup(self):
        from smolagents import LocalPythonInterpreter
        from smolagents.default_tools import FinalAnswerTool

        import io
        import pdb

        self.python_executor = LocalPythonInterpreter(
            [],
            {"final_answer": FinalAnswerTool()},
            max_print_outputs_length=None,
        )

        self.output_capture = io.StringIO()

        self.pdb_instance = pdb.Pdb(stdout=self.output_capture)

    def wrap_code_with_trycatch(self, code: str) -> str:
        from smolagents.utils import parse_code_blobs

        try:
            code = parse_code_blobs(code)
        except Exception as e:
            pass

        code_lines = code.split("\n")
        for i, line in enumerate(code_lines):
            code_lines[i] = f"\t{line}"
        code = "\n".join(code_lines)

        # add extra indentation
        code = (
            f"try:\n{code}\n" + "except Exception as e:" + "\n\t" + "out=sys.exc_info()"
        )
        code = "out=None" + "\n" + code + "\n" + "out"  # provide results in output

        return code

    def execute_code(self, code: str, state: dict):
        from smolagents.utils import parse_code_blobs

        code = parse_code_blobs(code)

        # Silencing the tool_validation error: execute_code: Name '' is undefined.
        py_executor_output = None
        py_executor_logs = None
        py_executor_is_final_answer = None

        py_executor_output, py_executor_logs, py_executor_is_final_answer = (
            self.python_executor(code, state)
        )

        return py_executor_output, py_executor_logs, py_executor_is_final_answer

    def execude_pdb_command(
        self, command: str, traceback
    ):
        output = None
        try:
            # Set up the pdb instance with the current frame and traceback
            frame = traceback.tb_frame
            while frame.f_back:
                frame = frame.f_back
            self.pdb_instance.reset()
            self.pdb_instance.setup(frame, traceback)
            # Execute the pdb command
            self.pdb_instance.onecmd(command)
            # Retrieve the output
            output = self.output_capture.getvalue()
        finally:
            # Clear the output capture for future use
            self.output_capture.truncate(0)
            self.output_capture.seek(0)

        return output

    def forward(self, code: str, command: str):
        import sys

        code = self.wrap_code_with_trycatch(code)

        state = {"sys": sys}

        # Silencing the tool_validation error: execute_code: Name '' is undefined.
        py_executor_output = None
        tb = None

        py_executor_output, py_executor_logs, py_executor_is_final_answer = (
            self.execute_code(code, state)
        )

        if py_executor_output is not None:
            try:
                exc_type, exc_value, tb = py_executor_output
            except Exception as e:
                return "No traceback found"
            pdb_result = self.execude_pdb_command(command, tb)
            return pdb_result

        return "No traceback found"

    def __init__(self, *args, **kwargs):
        self.is_initialized = False