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
- Define the Tool subclass statically at module top level so inspect.getsource returns a clean class statement
- If generating tools dynamically, emit real source text and exec it in a module so a ClassDef exists
- Ensure the file defining the tool ships as .py source (not a binary/REPL-only artifact)
- 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
- Define tools as plain module-level classes, never via type()/exec
- Test tool validation in CI with the real source files present
- Avoid defining tools solely in notebooks; move them to .py modules
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
- Unsupported comparison operator: {op}
- {expression.__class__.__name__} is not supported.
- Tool validation failed for {cls.__name__}:\n
- Expected class or callable, got {type(obj)}
- Function '{func_node.name}' has decorators other than @tool.
AI-assisted analysis of huggingface/smolagents@30bb116109 (2026-08-28).
Data as JSON: /api/errors/0fd2b5239ca59ecd.
Report an issue: GitHub.