{"record":{"id":"2d84e76569ae2104","repo":"ComposioHQ/composio","slug":"context-input-params-must-be-a-pydantic-basemod","errorCode":null,"errorMessage":"{context}: input_params must be a Pydantic BaseModel subclass. Tool input parameters are always an object with named properties.","messagePattern":"(.+?): input_params must be a Pydantic BaseModel subclass\\. Tool input parameters are always an object with named properties\\.","errorType":"validation","errorClass":"ValidationError","httpStatus":null,"severity":"error","filePath":"python/composio/core/models/custom_tool.py","lineNumber":181,"sourceCode":"    description: str,\n    input_params: t.Type[BaseModel],\n    execute: CustomToolExecuteFn,\n    extends_toolkit: t.Optional[str] = None,\n    output_params: t.Optional[t.Type[BaseModel]] = None,\n    preload: t.Optional[bool] = None,\n) -> CustomTool:\n    \"\"\"Internal: create and validate a CustomTool.\"\"\"\n    context = \"experimental.tool\"\n\n    _validate_slug(slug, context)\n\n    if not name:\n        raise ValidationError(f\"{context}: name is required\")\n    if not description:\n        raise ValidationError(f\"{context}: description is required\")\n\n    if not isinstance(input_params, type) or not issubclass(input_params, BaseModel):\n        raise ValidationError(\n            f\"{context}: input_params must be a Pydantic BaseModel subclass. \"\n            f\"Tool input parameters are always an object with named properties.\"\n        )\n\n    try:\n        from pydantic import RootModel\n\n        if issubclass(input_params, RootModel):\n            raise ValidationError(\n                f\"{context}: input_params must be a regular BaseModel with named fields, \"\n                f\"not a RootModel. Tool input parameters are always an object with \"\n                f\"named properties.\"\n            )\n    except ImportError:\n        pass\n\n    if not callable(execute):\n        raise ValidationError(f\"{context}: execute must be a callable\")","sourceCodeStart":163,"sourceCodeEnd":199,"githubUrl":"https://github.com/ComposioHQ/composio/blob/64b1b85502b1beeb2379e6c9e8bf1104504fa637/python/composio/core/models/custom_tool.py#L163-L199","documentation":"Raised when a custom tool's input_params is not a Pydantic BaseModel subclass. Composio tool inputs must be object schemas with named properties, so bare types (int, str), TypedDict, dataclass, or dict schemas are rejected.","triggerScenarios":"Passing input_params=int, input_params=SomeTypedDict, a dataclass, or a Pydantic RootModel-style scalar type to Tool(...) / the @tool decorator; also passing an instance instead of the class itself.","commonSituations":"Wanting a scalar-parameter tool (e.g. input is just a string) and trying input_params=str; migrating from frameworks that accept TypedDict or JSON schema dicts; accidentally passing Model() instead of Model.","solutions":["Wrap parameters in a BaseModel with named fields, e.g. class SearchInput(BaseModel): query: str — even for a single parameter","For typed-function tools, let the @tool decorator infer the model from the function signature instead of hand-writing input_params","Pass the class (not an instance): input_params=SearchInput, not SearchInput()","Replace TypedDict/dataclass schemas with an equivalent BaseModel"],"exampleFix":"# before\nclass SearchInput(TypedDict):\n    query: str\nTool(slug=\"search\", ..., input_params=SearchInput)\n\n# after\nfrom pydantic import BaseModel\nclass SearchInput(BaseModel):\n    query: str\nTool(slug=\"search\", ..., input_params=SearchInput)","handlingStrategy":"type-guard","validationCode":"from pydantic import BaseModel\ndef as_input_model(params) -> type[BaseModel]:\n    if isinstance(params, type) and issubclass(params, BaseModel):\n        return params\n    raise TypeError(\"input_params must be a BaseModel subclass\")","typeGuard":"from pydantic import BaseModel\nfrom pydantic import RootModel\ndef is_tool_input_model(t: object) -> bool:\n    return (\n        isinstance(t, type)\n        and issubclass(t, BaseModel)\n        and not issubclass(t, RootModel)\n    )","tryCatchPattern":"from composio.core.exceptions import ValidationError\ntry:\n    Tool(input_params=input_params, ...)\nexcept ValidationError:\n    class _Input(BaseModel):\n        __root__: input_params  # wrap scalar/legacy schema\n    Tool(input_params=_Input, ...)","preventionTips":["Always define parameters as a BaseModel with named fields, even for one param","Prefer letting the @tool decorator infer the model from the function signature","Convert TypedDict/dataclass schemas to BaseModel; check for RootModel wrapping scalars"],"tags":["python","custom-tools","pydantic","schema-validation"],"backgroundTag":"schema-validation-failed","analyzedSha":"64b1b85502b1beeb2379e6c9e8bf1104504fa637","analyzedAt":"2026-08-28T15:39:33.623Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}