huggingface/smolagents · error · ValueError

Source code must define a class

Error message

Source code must define a class

What it means

validate_tool_attributes parses the source code of a Tool subclass via inspect/ast and expects the first top-level statement to be an ast.ClassDef. If the retrieved source starts with something else (decorator-heavy definitions, dynamically generated classes, comments/encoding lines preceding in odd ways, or a class created via type() or exec), the check fails with ValueError('Source code must define a class').

Source

Thrown at src/smolagents/tool_validation.py:231

                    self.invalid_attributes.append(
                        f"Class attribute 'name' must be a valid Python identifier and not a reserved keyword, found '{node.value.value}'"
                    )

        def _check_init_function_parameters(self, node):
            # Check defaults in parameters
            for arg, default in reversed(list(zip_longest(reversed(node.args.args), reversed(node.args.defaults)))):
                if default is None:
                    if arg.arg != "self":
                        self.non_defaults.add(arg.arg)
                elif not isinstance(default, (ast.Constant, ast.Dict, ast.List, ast.Set)):
                    self.non_literal_defaults.add(arg.arg)

    class_level_checker = ClassLevelChecker()
    source = get_source(cls)
    tree = ast.parse(source)
    class_node = tree.body[0]
    if not isinstance(class_node, ast.ClassDef):
        raise ValueError("Source code must define a class")
    class_level_checker.visit(class_node)

    errors = []
    # Check invalid class attributes
    if class_level_checker.invalid_attributes:
        errors += class_level_checker.invalid_attributes
    if class_level_checker.complex_attributes:
        errors.append(
            f"Complex attributes should be defined in __init__, not as class attributes: "
            f"{', '.join(class_level_checker.complex_attributes)}"
        )
    if class_level_checker.non_defaults:
        errors.append(
            f"Parameters in __init__ must have default values, found required parameters: "
            f"{', '.join(class_level_checker.non_defaults)}"
        )
    if class_level_checker.non_literal_defaults:
        errors.append(

View on GitHub (pinned to 30bb116109)

Solutions

  1. Define the Tool subclass statically at module top level so inspect.getsource returns a clean class statement
  2. If generating tools dynamically, emit real source text and exec it in a module so a ClassDef exists
  3. Ensure the file defining the tool ships as .py source (not a binary/REPL-only artifact)
  4. Drop validation (skip validate_tool_attributes / avoid to_dict) for throwaway dynamic classes

Example fix

# before
MyTool = type("MyTool", (Tool,), {"name": "my_tool", ...})  # no source ClassDef

# after
class MyTool(Tool):
    name = "my_tool"
    description = "..."
    inputs = {...}
    output_type = "text"
    def forward(self, ...):
        ...
Defensive patterns

Strategy: type-guard

Validate before calling

import inspect

def has_classdef_source(cls) -> bool:
    try:
        tree = __import__("ast").parse(inspect.getsource(cls))
    except (OSError, TypeError, SyntaxError):
        return False
    return isinstance(tree.body[0], __import__("ast").ClassDef)

Type guard

def is_statically_defined(cls) -> bool:
    import inspect
    try:
        return inspect.getsourcefile(cls) is not None and has_classdef_source(cls)
    except TypeError:
        return False

Try / catch

try:
    validate_tool_attributes(MyTool)
except ValueError as e:
    if "must define a class" in str(e):
        # rewrite tool as a static class definition
        raise

Prevention

When it happens

Trigger: Calling validate_tool_attributes (directly or via Tool.to_dict / get_tools_definition_code) on a class whose get_source output does not begin with a class statement: classes built dynamically (type(...)), defined in REPL/exec/lambda contexts, or whose source retrieval returns a module/statement other than the class.

Common situations: Programmatically generating tool classes at runtime; defining tools in Jupyter cells where source introspection is unreliable; tools defined inside functions or via metaclass factories; source-unavailable frozen/compiled environments (.pyc-only installs).

Related errors


AI-assisted analysis of huggingface/smolagents@30bb116109 (2026-08-28). Data as JSON: /api/errors/0fd2b5239ca59ecd. Report an issue: GitHub.