{"record":{"id":"cd5bedc6d3e67d25","repo":"huggingface/smolagents","slug":"function-func-node-name-has-decorators-other-t","errorCode":null,"errorMessage":"Function '{func_node.name}' has decorators other than @tool. This may cause issues with serialization in the remote executor. See issue #1626.","messagePattern":"Function '(.+?)' has decorators other than @tool\\. This may cause issues with serialization in the remote executor\\. See issue #1626\\.","errorType":"console","errorClass":"UserWarning","httpStatus":null,"severity":"warning","filePath":"src/smolagents/tools.py","lineNumber":1134,"sourceCode":"    lines = tool_source.splitlines()\n    tree = ast.parse(tool_source)\n    #   - Find function definition\n    func_node = next((node for node in ast.walk(tree) if isinstance(node, ast.FunctionDef)), None)\n    if not func_node:\n        raise ValueError(\n            f\"No function definition found in the provided source of {tool_function.__name__}. \"\n            \"Ensure the input is a standard function.\"\n        )\n    #   - Extract decorator lines\n    decorator_lines = \"\"\n    if func_node.decorator_list:\n        tool_decorators = [d for d in func_node.decorator_list if isinstance(d, ast.Name) and d.id == \"tool\"]\n        if len(tool_decorators) > 1:\n            raise ValueError(\n                f\"Multiple @tool decorators found on function '{func_node.name}'. Only one @tool decorator is allowed.\"\n            )\n        if len(tool_decorators) < len(func_node.decorator_list):\n            warnings.warn(\n                f\"Function '{func_node.name}' has decorators other than @tool. \"\n                \"This may cause issues with serialization in the remote executor. See issue #1626.\"\n            )\n        decorator_start = tool_decorators[0].end_lineno if tool_decorators else 0\n        decorator_end = func_node.decorator_list[-1].end_lineno\n        decorator_lines = \"\\n\".join(lines[decorator_start:decorator_end])\n    #   - Extract tool source body\n    body_start = func_node.body[0].lineno - 1  # AST lineno starts at 1\n    tool_source_body = \"\\n\".join(lines[body_start:])\n    # - Create the forward method source, including def line and indentation\n    forward_method_source = f\"def forward{new_sig}:\\n{tool_source_body}\"\n    # - Create the class source\n    indent = \" \" * 4  # for class method\n    class_source = (\n        textwrap.dedent(f\"\"\"\n        class SimpleTool(Tool):\n            name: str = \"{tool_json_schema[\"name\"]}\"\n            description: str = {json.dumps(textwrap.dedent(tool_json_schema[\"description\"]).strip())}","sourceCodeStart":1116,"sourceCodeEnd":1152,"githubUrl":"https://github.com/huggingface/smolagents/blob/30bb1161095dbae2271e6bc3cc4c219cc3897a57/src/smolagents/tools.py#L1116-L1152","documentation":"The `@tool` decorator serializes your function's source code (it strips/reconstructs decorators) so it can be shipped to a remote code executor. If the function has decorators beyond a single `@tool`, they cannot be serialized faithfully, and smolagents warns that this may break remote execution (GitHub issue #1626).","triggerScenarios":"Applying `@tool` to a function that also has other decorators, e.g. `@functools.lru_cache`, `@staticmethod`, `@some_decorator` stacked with `@tool`. The AST check `len(tool_decorators) < len(func_node.decorator_list)` fires the warning during `tool()` decoration.","commonSituations":"Decorating cached or wrapped helper functions with `@tool`; using `@tool` on methods inside classes with additional decorators; decorator order like `@lru_cache` above `@tool` causing wrong serialization for egress-executed tools.","solutions":["Remove the extra decorators from the @tool function; move caching/wrapping inside the function body","Apply the other decorator OUTSIDE @tool only if remote execution isn't needed, otherwise restructure: write an undecorated function, decorate it with @tool, and cache at call sites","If you don't use remote/egress code execution, the warning can be ignored, but reordering so @tool is outermost avoids the serialization issue"],"exampleFix":"# before\n@tool\n@lru_cache(maxsize=None)\ndef fetch_price(ticker: str) -> str:\n    ...\n\n# after\ndef _fetch_price(ticker: str) -> str: ...\n\n@tool\ndef fetch_price(ticker: str) -> str:\n    return _fetch_price(ticker)","handlingStrategy":"validation","validationCode":"import ast, inspect\n\ndef has_only_tool_decorator(func) -> bool:\n    src = inspect.getsource(func.__wrapped__ if hasattr(func, '__wrapped__') else func)\n    tree = ast.parse(textwrap.dedent(src))\n    fn = tree.body[0]\n    return all(isinstance(d, ast.Name) and d.id == 'tool' for d in fn.decorator_list)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep @tool functions free of other decorators; implement caching/wrapping inside the body","If stacking is unavoidable, apply other decorators outside @tool and skip remote execution for that tool","Add a unit test asserting each registered tool serializes cleanly for the remote executor"],"tags":["decorator","serialization","remote-execution","smolagents","ast"],"backgroundTag":"decorator-serialization-conflict","analyzedSha":"30bb1161095dbae2271e6bc3cc4c219cc3897a57","analyzedAt":"2026-08-28T18:52:54.169Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}