langchain-ai/langchain · error · ValueError

The first argument must be a string or a callable with a __n

Error message

The first argument must be a string or a callable with a __name__ for tool decorator. Got {type(name_or_callable)}

What it means

The first positional argument of @tool must be either a string (tool name) or a callable with __name__ (the function to wrap). Anything else — int, None-likes without __name__, module, list — triggers this ValueError with the offending type.

Source

Thrown at libs/core/langchain_core/tools/convert.py:392

            #    pass
            return _create_tool_factory(name_or_callable.__name__)(name_or_callable)
        if isinstance(name_or_callable, str):
            # Used with a new name for the tool
            # @tool("search")
            # def my_tool():
            #    pass
            #
            # or
            #
            # @tool("search", parse_docstring=True)
            # def my_tool():
            #    pass
            return _create_tool_factory(name_or_callable)
        msg = (
            f"The first argument must be a string or a callable with a __name__ "
            f"for tool decorator. Got {type(name_or_callable)}"
        )
        raise ValueError(msg)

    # Tool is used as a decorator with parameters specified
    # @tool(parse_docstring=True)
    # def my_tool():
    #    pass
    def _partial(func: Callable[..., Any] | Runnable[Any, Any]) -> BaseTool:
        """Partial function that takes a `Callable` and returns a tool."""
        name_ = func.get_name() if isinstance(func, Runnable) else func.__name__
        tool_factory = _create_tool_factory(name_)
        return tool_factory(func)

    return _partial


def _get_description_from_runnable(runnable: Runnable[Any, Any]) -> str:
    """Generate a placeholder description of a `Runnable`."""
    input_schema = runnable.get_input_jsonschema()
    return f"Takes {input_schema}."

View on GitHub (pinned to e32fa9a52e)

Solutions

  1. Pass a string name or the decorated function itself positionally
  2. For Runnables use tool(name, runnable=runnable)
  3. Sanitize dynamically generated names: assert isinstance(name, str) and name before calling tool()

Example fix

# before
name = get_tool_name()          # returns 42 or None
t = tool(name)(my_func)

# after
name = get_tool_name()
assert isinstance(name, str) and name, "tool name must be a non-empty string"
t = tool(name)(my_func)
Defensive patterns

Strategy: type-guard

Validate before calling

def validate_tool_arg(arg) -> str:
    if isinstance(arg, str) and arg:
        return arg
    if callable(arg) and hasattr(arg, "__name__"):
        return arg.__name__
    msg = f"Bad @tool argument: {arg!r} of type {type(arg)}"
    raise TypeError(msg)

Type guard

def is_valid_first_tool_arg(x: object) -> bool:
    return (isinstance(x, str) and bool(x)) or (callable(x) and hasattr(x, "__name__"))

Try / catch

try:
    t = tool(maybe_name)(func)
except ValueError as e:
    if "first argument" in str(e):
        t = tool("default_name")(func)
    else:
        raise

Prevention

When it happens

Trigger: tool(123); tool(['a','b']); tool(SomeClassWithoutName); passing a variable holding a non-callable as the first argument.

Common situations: Programmatic tool creation where a name variable is accidentally None or a non-string; passing a Runnable positionally instead of via runnable=; refactors that change what the first argument holds.

Related errors


AI-assisted analysis of langchain-ai/langchain@e32fa9a52e (2026-08-14). Data as JSON: /api/errors/c15feb920014b722. Report an issue: GitHub.