{"record":{"id":"a11a920e433fce66","repo":"langchain-ai/langchain","slug":"runnable-must-have-an-object-schema","errorCode":null,"errorMessage":"Runnable must have an object schema.","messagePattern":"Runnable must have an object schema\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"libs/core/langchain_core/tools/convert.py","lineNumber":291,"sourceCode":"        \"\"\"Create a decorator that takes a callable and returns a tool.\n\n        Args:\n            tool_name: The name that will be assigned to the tool.\n\n        Returns:\n            A function that takes a callable or `Runnable` and returns a tool.\n        \"\"\"\n\n        def _tool_factory(\n            dec_func: Callable[..., Any] | Runnable[Any, Any],\n        ) -> BaseTool:\n            tool_description = description\n            if isinstance(dec_func, Runnable):\n                runnable = dec_func\n\n                if runnable.get_input_jsonschema().get(\"type\") != \"object\":\n                    msg = \"Runnable must have an object schema.\"\n                    raise ValueError(msg)\n\n                async def ainvoke_wrapper(\n                    callbacks: Callbacks | None = None, **kwargs: Any\n                ) -> Any:\n                    return await runnable.ainvoke(kwargs, {\"callbacks\": callbacks})\n\n                def invoke_wrapper(\n                    callbacks: Callbacks | None = None, **kwargs: Any\n                ) -> Any:\n                    return runnable.invoke(kwargs, {\"callbacks\": callbacks})\n\n                coroutine = ainvoke_wrapper\n                func = invoke_wrapper\n                schema: ArgsSchema | None = runnable.input_schema\n                tool_description = description or repr(runnable)\n            elif inspect.iscoroutinefunction(dec_func):\n                coroutine = dec_func\n                func = None","sourceCodeStart":273,"sourceCodeEnd":309,"githubUrl":"https://github.com/langchain-ai/langchain/blob/e32fa9a52eab3b61ad7a45399bfde59b3e580fc4/libs/core/langchain_core/tools/convert.py#L273-L309","documentation":"Raised by the @tool decorator when the object being converted is a Runnable whose input JSON schema is not of type 'object'. Tools expose their inputs as named keyword arguments, so the Runnable must accept a single dict/TypedDict/pydantic-model input; scalar (str, int) or tuple inputs cannot be mapped to tool arguments.","triggerScenarios":"@tool applied to a Runnable whose InputType is str (e.g. a bare prompt template or a lambda taking a string); runnable.as_tool() / convert_runnable_to_tool on a chain with InputType = int or a Union; a Runnable built with a func whose input schema is an array.","commonSituations":"Wrapping a prompt | llm chain that takes a plain string input; converting a parser or embedding-style Runnable into a tool; using .with_structured_output(...) on something whose input is not a dict.","solutions":["Give the Runnable a dict-like input: wrap it so InputType is a TypedDict or pydantic BaseModel, e.g. lambda x: chain(x['text']) with InputType typed as a TypedDict","Or skip @tool and use Tool(func=lambda text: runnable.invoke(text)) for single-string inputs","Check runnable.get_input_jsonschema() first and restructure until it returns {\"type\": \"object\", ...}"],"exampleFix":"# before\nchain = prompt | llm          # InputType: str\n@tool\ndef my_tool(): ...             # or tool(chain)\n\n# after\nclass ChainInput(TypedDict):\n    question: str\n\ndef _run(x: ChainInput) -> str:\n    return (prompt | llm).invoke({\"question\": x[\"question\"]})\n\nchain = RunnableLambda(_run)  # get_input_jsonschema() -> type: object\ntool = chain.as_tool(name=\"answer\")","handlingStrategy":"type-guard","validationCode":"def is_object_schema_runnable(runnable) -> bool:\n    try:\n        schema = runnable.get_input_jsonschema()\n    except Exception:\n        return False\n    return isinstance(schema, dict) and schema.get(\"type\") == \"object\"","typeGuard":"from langchain_core.runnables import Runnable\n\ndef can_convert_to_tool(runnable: object) -> bool:\n    return (\n        isinstance(runnable, Runnable)\n        and runnable.get_input_jsonschema().get(\"type\") == \"object\"\n    )","tryCatchPattern":"try:\n    t = runnable.as_tool()\nexcept ValueError as e:\n    if \"object schema\" in str(e):\n        t = Tool(name=\"runnable\", func=lambda **kw: runnable.invoke(kw),\n                 description=\"Wrapped runnable\")\n    else:\n        raise","preventionTips":["Type Runnable inputs as TypedDict or pydantic BaseModel when you plan to call .as_tool()","Check runnable.get_input_jsonschema() during development before decorating","Keep single-string-input runnables out of @tool; wrap them with RunnableLambda(typed_fn) first"],"tags":["langchain","tools","runnable","schema"],"backgroundTag":null,"analyzedSha":"e32fa9a52eab3b61ad7a45399bfde59b3e580fc4","analyzedAt":"2026-08-14T18:42:09.092Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}