{"record":{"id":"b8d742ca6c243740","repo":"langchain-ai/langchain","slug":"tool-input-must-be-str-or-dict-if-dict-dict-argu","errorCode":null,"errorMessage":"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}","messagePattern":"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\\. (.+?)","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"libs/core/langchain_core/tools/convert.py","lineNumber":428,"sourceCode":"    return f\"Takes {input_schema}.\"\n\n\ndef _get_schema_from_runnable_and_arg_types(\n    runnable: Runnable[Any, Any],\n    name: str,\n    arg_types: dict[str, type] | None = None,\n) -> type[BaseModel]:\n    \"\"\"Infer `args_schema` for tool.\"\"\"\n    if arg_types is None:\n        try:\n            arg_types = get_type_hints(runnable.InputType)\n        except TypeError as e:\n            msg = (\n                \"Tool input must be str or dict. If dict, dict arguments must be \"\n                \"typed. Either annotate types (e.g., with TypedDict) or pass \"\n                f\"arg_types into `.as_tool` to specify. {e}\"\n            )\n            raise TypeError(msg) from e\n    fields = {key: (key_type, Field(...)) for key, key_type in arg_types.items()}\n    return cast(\"type[BaseModel]\", create_model(name, **fields))  # type: ignore[call-overload]\n\n\ndef convert_runnable_to_tool(\n    runnable: Runnable[Any, Any],\n    args_schema: TypeBaseModel | None = None,\n    *,\n    name: str | None = None,\n    description: str | None = None,\n    arg_types: dict[str, type] | None = None,\n) -> BaseTool:\n    \"\"\"Convert a `Runnable` into a `BaseTool`.\n\n    Args:\n        runnable: The `Runnable` to convert.\n        args_schema: The schema for the tool's input arguments.\n        name: The name of the tool.","sourceCodeStart":410,"sourceCodeEnd":446,"githubUrl":"https://github.com/langchain-ai/langchain/blob/e32fa9a52eab3b61ad7a45399bfde59b3e580fc4/libs/core/langchain_core/tools/convert.py#L410-L446","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Pass arg_types: runnable.as_tool(arg_types={'text': str, 'count': int})","Or type the function parameter with a TypedDict/pydantic model so get_type_hints succeeds","For single-string inputs, annotate the parameter as str"],"exampleFix":"# before\ndef _run(data):                 # untyped dict\n    return process(data)\n\ntool = RunnableLambda(_run).as_tool()   # TypeError\n\n# after\nclass Data(TypedDict):\n    text: str\n    count: int\n\ndef _run(data: Data) -> str:\n    return process(data)\n\ntool = RunnableLambda(_run).as_tool()\n# or quick fix: RunnableLambda(_run).as_tool(arg_types={\"text\": str, \"count\": int})","handlingStrategy":"validation","validationCode":"from typing import get_type_hints\n\ndef as_tool_typed(runnable, name=\"tool\", arg_types=None):\n    if arg_types is None:\n        try:\n            arg_types = get_type_hints(runnable.InputType)\n        except TypeError:\n            arg_types = None\n    if not arg_types:\n        msg = \"Runnable input is untyped; pass arg_types=...\"\n        raise ValueError(msg)\n    return runnable.as_tool(name=name, arg_types=arg_types)","typeGuard":"from typing import get_type_hints\n\ndef has_typed_input(runnable) -> bool:\n    try:\n        return bool(get_type_hints(runnable.InputType))\n    except TypeError:\n        return False","tryCatchPattern":"try:\n    t = runnable.as_tool()\nexcept TypeError as e:\n    if \"must be typed\" in str(e):\n        t = runnable.as_tool(arg_types={\"text\": str})\n    else:\n        raise","preventionTips":["Always annotate RunnableLambda functions with a TypedDict parameter when tools are planned","Keep TypedDicts at module level so get_type_hints resolves forward references","Pass arg_types explicitly when wrapping third-party, untyped runnables"],"tags":["langchain","tools","runnable","typing","schema"],"backgroundTag":null,"analyzedSha":"e32fa9a52eab3b61ad7a45399bfde59b3e580fc4","analyzedAt":"2026-08-14T18:42:09.092Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}