BUG: LocalPythonExecutor silently ignores decorators, so a decorated function returns the undecorated result
Describe the bug
LocalPythonExecutor never applies decorators. ast.FunctionDef.decorator_list and ast.ClassDef.decorator_list are read nowhere in local_python_executor.py: evaluate_function_def builds the function from the node and returns it, and evaluate_class_def puts each method into the class dict through the same path.
The decorated object is therefore the undecorated one, and nothing raises. That matters more than the missing feature: everywhere else the executor refuses loudly, for example InterpreterError: Forbidden function evaluation: 'super' is not among the explicitly allowed tools. Here it returns a wrong value silently, so an agent gets a plausible answer computed by code that did not run as written.
Reproduction
A decorator defined entirely inside the sandbox, using nothing restricted:
from smolagents.local_python_executor import LocalPythonExecutor
from smolagents.default_tools import FinalAnswerTool
ex = LocalPythonExecutor(additional_authorized_imports=[])
ex.send_tools({"final_answer": FinalAnswerTool()})
code = '''
def double(fn):
def wrapper(*a, **k):
return fn(*a, **k) * 2
return wrapper
@double
def value():
return 21
final_answer(value())
'''
print(ex(code).output)smolagents LocalPythonExecutor -> 21
real Python -> 42Two more shapes, same cause:
# 1. @property returns the bound method instead of the value.
class A:
@property
def v(self):
return 7
A().v # -> <bound method ...>, real Python -> 7
# 2. An UNDEFINED decorator is not an error.
@undefined_decorator
def f():
return 1
f() # -> 1, real Python -> NameErrorCase 2 is the one I would weigh most: an undefined name is silently accepted, which is the opposite of how every other name resolution in the executor behaves.
Expected behavior
Either apply the decorators, or refuse the code the way the executor refuses everything else it does not support. Silently returning the undecorated object is the outcome that cannot be detected from the agent's side.
Notes on scope
super, repr, format, frozenset, bytes, slice and hash are also absent from BASE_PYTHON_TOOLS, so super().__init__(...) fails and ordinary inheritance does not work. Those all fail LOUDLY, so they are a separate and much smaller problem; I mention them only because I found them while checking whether @property failing was about property being unavailable. It is not: a user-defined decorator is dropped too.
I did check the tracker first. The existing decorator issues are about the @tool decorator and the timeout() decorator, and I did not find this one open or closed. I also checked that this is not a documented limitation: the class docstring scopes the restrictions to imports and built-in functions, and says the executor is not a security sandbox, which is a different claim.
Packages version
main at 30bb1161, Python 3.12.
Would a PR be welcome?
I am happy to send one, but the shape is a design call I would rather you make. Applying a decorator means calling it, and the natural implementation resolves the decorator expression and applies it to the function. Routing that through the existing call checks in evaluate_call keeps the current restrictions intact; applying it directly would not. If you would rather refuse decorated definitions than support them, that is a much smaller change and I am glad to do that instead.
Source: huggingface/smolagents