langchain-ai/langchain · error · TypeError

Tool input must be str or dict. If dict, dict arguments must

Error message

Tool input must be str or dict. If dict, dict arguments must be typed. Either annotate types (e.g., with TypedDict) or pass arg_types into `.as_tool` to specify. {e}

What it means

When converting a Runnable to a tool (.as_tool / convert_runnable_to_tool), LangChain derives the args schema from runnable.InputType's type hints. If InputType is untyped (plain dict without hints) or malformed, get_type_hints raises TypeError, which is re-raised with guidance: dict inputs must be typed, or arg_types passed explicitly.

Source

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

    return f"Takes {input_schema}."


def _get_schema_from_runnable_and_arg_types(
    runnable: Runnable[Any, Any],
    name: str,
    arg_types: dict[str, type] | None = None,
) -> type[BaseModel]:
    """Infer `args_schema` for tool."""
    if arg_types is None:
        try:
            arg_types = get_type_hints(runnable.InputType)
        except TypeError as e:
            msg = (
                "Tool input must be str or dict. If dict, dict arguments must be "
                "typed. Either annotate types (e.g., with TypedDict) or pass "
                f"arg_types into `.as_tool` to specify. {e}"
            )
            raise TypeError(msg) from e
    fields = {key: (key_type, Field(...)) for key, key_type in arg_types.items()}
    return cast("type[BaseModel]", create_model(name, **fields))  # type: ignore[call-overload]


def convert_runnable_to_tool(
    runnable: Runnable[Any, Any],
    args_schema: TypeBaseModel | None = None,
    *,
    name: str | None = None,
    description: str | None = None,
    arg_types: dict[str, type] | None = None,
) -> BaseTool:
    """Convert a `Runnable` into a `BaseTool`.

    Args:
        runnable: The `Runnable` to convert.
        args_schema: The schema for the tool's input arguments.
        name: The name of the tool.

View on GitHub (pinned to e32fa9a52e)

Solutions

  1. Pass arg_types: runnable.as_tool(arg_types={'text': str, 'count': int})
  2. Or type the function parameter with a TypedDict/pydantic model so get_type_hints succeeds
  3. For single-string inputs, annotate the parameter as str

Example fix

# before
def _run(data):                 # untyped dict
    return process(data)

tool = RunnableLambda(_run).as_tool()   # TypeError

# after
class Data(TypedDict):
    text: str
    count: int

def _run(data: Data) -> str:
    return process(data)

tool = RunnableLambda(_run).as_tool()
# or quick fix: RunnableLambda(_run).as_tool(arg_types={"text": str, "count": int})
Defensive patterns

Strategy: validation

Validate before calling

from typing import get_type_hints

def as_tool_typed(runnable, name="tool", arg_types=None):
    if arg_types is None:
        try:
            arg_types = get_type_hints(runnable.InputType)
        except TypeError:
            arg_types = None
    if not arg_types:
        msg = "Runnable input is untyped; pass arg_types=..."
        raise ValueError(msg)
    return runnable.as_tool(name=name, arg_types=arg_types)

Type guard

from typing import get_type_hints

def has_typed_input(runnable) -> bool:
    try:
        return bool(get_type_hints(runnable.InputType))
    except TypeError:
        return False

Try / catch

try:
    t = runnable.as_tool()
except TypeError as e:
    if "must be typed" in str(e):
        t = runnable.as_tool(arg_types={"text": str})
    else:
        raise

Prevention

When it happens

Trigger: runnable.as_tool() where the RunnableLambda's function parameter is an untyped dict; InputType is dict (bare) or object; a TypedDict defined in a local scope whose hints cannot be resolved.

Common situations: Wrapping quick lambdas like RunnableLambda(lambda data: process(data)) into tools; chains whose input type inference falls back to untyped dict; TypedDicts failing get_type_hints due to forward references / local classes.

Related errors


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