{"record":{"id":"26c065d6fa6ca3fb","repo":"huggingface/smolagents","slug":"multiple-tool-decorators-found-on-function-func","errorCode":null,"errorMessage":"Multiple @tool decorators found on function '{func_node.name}'. Only one @tool decorator is allowed.","messagePattern":"Multiple @tool decorators found on function '(.+?)'\\. Only one @tool decorator is allowed\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/smolagents/tools.py","lineNumber":1130,"sourceCode":"    # Create and attach the source code of the dynamically created tool class and forward method\n    # - Get the source code of tool_function\n    tool_source = textwrap.dedent(inspect.getsource(tool_function))\n    # - Remove the tool decorator and function definition line\n    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 = (","sourceCodeStart":1112,"sourceCodeEnd":1148,"githubUrl":"https://github.com/huggingface/smolagents/blob/30bb1161095dbae2271e6bc3cc4c219cc3897a57/src/smolagents/tools.py#L1112-L1148","documentation":"While parsing the decorated function's AST, smolagents found more than one plain `@tool` decorator (ast.Name nodes named 'tool') applied to the same function. Duplicate decoration would wrap the tool twice and corrupt schema generation, so it raises ValueError.","triggerScenarios":"Stacking @tool twice, e.g. `@tool\\n@tool\\ndef f(...)`, or applying @tool to a function that was already converted to a Tool by another @tool call. Calling `tool(tool(func))` programmatically can produce the same result.","commonSituations":"Copy-paste accidents during refactoring; migration code that applies @tool on top of already-decorated imports; lint-disabled duplicates after merges.","solutions":["Remove the duplicate @tool decorator, leaving exactly one","If decorating programmatically, ensure the input is a raw function, not an already-wrapped Tool","Check imports to confirm you're not decorating an already-processed function from another module"],"exampleFix":"# before\n@tool\n@tool\ndef add(a: int, b: int) -> int:\n    return a + b\n# after\n@tool\ndef add(a: int, b: int) -> int:\n    return a + b","handlingStrategy":"validation","validationCode":"import ast, inspect\nsrc = inspect.getsource(my_func)\nfn = next(n for n in ast.walk(ast.parse(src)) if isinstance(n, ast.FunctionDef))\nnames = [d.id for d in fn.decorator_list if isinstance(d, ast.Name)]\nassert names.count(\"tool\") <= 1, \"remove duplicate @tool\"","typeGuard":null,"tryCatchPattern":"try:\n    my_tool = tool(my_func)\nexcept ValueError as e:\n    if \"Multiple @tool decorators\" in str(e):\n        raise  # fix the source; do not retry programmatically\n    raise","preventionTips":["Apply @tool exactly once per function","Never decorate a function that is already a Tool instance","Review merged/duplicated decorator lines during refactors"],"tags":["tool-decorator","ast","duplicate-decorator"],"backgroundTag":"duplicate-decorator-application","analyzedSha":"30bb1161095dbae2271e6bc3cc4c219cc3897a57","analyzedAt":"2026-08-28T18:52:54.169Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}