ComposioHQ/composio · error · ValidationError
{context}: input_params must be a Pydantic BaseModel subclas
Error message
{context}: input_params must be a Pydantic BaseModel subclass. Tool input parameters are always an object with named properties. What it means
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.
Source
Thrown at python/composio/core/models/custom_tool.py:181
description: str,
input_params: t.Type[BaseModel],
execute: CustomToolExecuteFn,
extends_toolkit: t.Optional[str] = None,
output_params: t.Optional[t.Type[BaseModel]] = None,
preload: t.Optional[bool] = None,
) -> CustomTool:
"""Internal: create and validate a CustomTool."""
context = "experimental.tool"
_validate_slug(slug, context)
if not name:
raise ValidationError(f"{context}: name is required")
if not description:
raise ValidationError(f"{context}: description is required")
if not isinstance(input_params, type) or not issubclass(input_params, BaseModel):
raise ValidationError(
f"{context}: input_params must be a Pydantic BaseModel subclass. "
f"Tool input parameters are always an object with named properties."
)
try:
from pydantic import RootModel
if issubclass(input_params, RootModel):
raise ValidationError(
f"{context}: input_params must be a regular BaseModel with named fields, "
f"not a RootModel. Tool input parameters are always an object with "
f"named properties."
)
except ImportError:
pass
if not callable(execute):
raise ValidationError(f"{context}: execute must be a callable")View on GitHub (pinned to 64b1b85502)
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
Example fix
# before
class SearchInput(TypedDict):
query: str
Tool(slug="search", ..., input_params=SearchInput)
# after
from pydantic import BaseModel
class SearchInput(BaseModel):
query: str
Tool(slug="search", ..., input_params=SearchInput) Defensive patterns
Strategy: type-guard
Validate before calling
from pydantic import BaseModel
def as_input_model(params) -> type[BaseModel]:
if isinstance(params, type) and issubclass(params, BaseModel):
return params
raise TypeError("input_params must be a BaseModel subclass") Type guard
from pydantic import BaseModel
from pydantic import RootModel
def is_tool_input_model(t: object) -> bool:
return (
isinstance(t, type)
and issubclass(t, BaseModel)
and not issubclass(t, RootModel)
) Try / catch
from composio.core.exceptions import ValidationError
try:
Tool(input_params=input_params, ...)
except ValidationError:
class _Input(BaseModel):
__root__: input_params # wrap scalar/legacy schema
Tool(input_params=_Input, ...) Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Could not determine a home directory to store the Composio c
- Cache directory {directory} is not writable please provide a
- module {__name__!r} has no attribute {name!r}
- Failed to upload to S3: {_sanitize_url_for_logging(url)}. Er
- Failed to upload to S3. Status: {response.status_code}. This
AI-assisted analysis of ComposioHQ/composio@64b1b85502 (2026-08-28).
Data as JSON: /api/errors/2d84e76569ae2104.
Report an issue: GitHub.