ComposioHQ/composio · error · InvalidParams

Tool arguments must resolve to an object, received {type(val

Error message

Tool arguments must resolve to an object, received {type(value).__name__}

What it means

After normalize_tool_arguments (JSON-parsed or passed through), the value is not a dict — _as_dict only accepts mappings and raises InvalidParams naming the actual type (e.g. list, str, int, NoneType). Tool argument payloads must be a JSON object at the top level.

Source

Thrown at python/composio/utils/shared.py:104

    if isinstance(arguments, str):
        stripped = arguments.strip()
        if not stripped:
            return {}
        try:
            parsed = json.loads(stripped)
        except json.JSONDecodeError as e:
            raise InvalidParams(
                f"Tool arguments were provided as a string that is not valid JSON: {e}"
            ) from e
        return _as_dict(parsed)

    return _as_dict(arguments)


def _as_dict(value: t.Any) -> t.Dict[str, t.Any]:
    if isinstance(value, dict):
        return value
    raise InvalidParams(
        f"Tool arguments must resolve to an object, received {type(value).__name__}"
    )


def validate_and_serialize_tool_arguments(
    args_schema: t.Type[BaseModel],
    arguments: t.Dict[str, t.Any],
) -> t.Dict[str, t.Any]:
    """Validate provider arguments and serialize only values the backend should see.

    Pydantic models use ``None`` for optional fields that were not supplied, so a
    plain ``model_dump()`` changes omission into an explicit null. Conversely,
    ``exclude_unset=True`` also drops defaults declared by the source JSON Schema.
    Generated models track fields with declared defaults. Combining that metadata
    recursively with each selected model's ``model_fields_set`` preserves aliases,
    explicit nulls, defaults, and validated dynamic extras without replaying the
    source schema during execution.
    """

View on GitHub (pinned to 64b1b85502)

Solutions

  1. Ensure the top-level arguments value is an object/dict — wrap scalars: use {"value": x} if the schema expects a field, or {} for no args
  2. Check the tool's input schema: if it legitimately takes an array, the SDK still needs {"items": [...]}-style wrapping per the schema
  3. Default None to {} before calling
  4. If an LLM produced it, tighten the prompt/tool schema so it emits a single JSON object

Example fix

# before
tool.run(arguments=[{"query": "a"}, {"query": "b"}])
# after
tool.run(arguments={"queries": [{"query": "a"}, {"query": "b"}]})
Defensive patterns

Strategy: type-guard

Validate before calling

def ensure_args_object(args):
    if args is None: return {}
    if isinstance(args, str):
        import json
        args = json.loads(args) if args.strip() else {}
    return args

Type guard

def is_args_object(args) -> bool:
    if isinstance(args, str):
        import json
        try: args = json.loads(args)
        except json.JSONDecodeError: return False
    return isinstance(args, dict)

Try / catch

from composio.exceptions import InvalidParams
try:
    tool.run(args)
except InvalidParams as e:
    if "must resolve to an object" in str(e):
        tool.run({"value": args})  # wrap per schema

Prevention

When it happens

Trigger: arguments=[1,2,3] (a JSON array), arguments='"just a string"' (valid JSON but a string scalar), arguments=42, or arguments=None slipping through. Also a JSON string containing an array like '[{"a":1}]'.

Common situations: LLMs wrapping args in a list; models returning a bare string; code forwarding an unwrapped variable (passing the value of a 'arguments' key that itself is a scalar); optional-args paths where None isn't defaulted to {}.

Related errors


AI-assisted analysis of ComposioHQ/composio@64b1b85502 (2026-08-28). Data as JSON: /api/errors/394e5f159797f2f8. Report an issue: GitHub.