ComposioHQ/composio · error · InvalidParams

Tool arguments were provided as a string that is not valid J

Error message

Tool arguments were provided as a string that is not valid JSON: {e}

What it means

normalize_tool_arguments accepts tool arguments as a JSON string (as produced by many LLM providers), but json.loads failed on the non-empty string. The InvalidParams error wraps the json.JSONDecodeError so you can see the exact parse position.

Source

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

    - A string is JSON-parsed; an empty / whitespace-only string becomes ``{}``.
    - Anything that does not resolve to a dict (lists, primitives, unparseable
      strings, JSON that parses to a non-object) raises :class:`InvalidParams`.

    :param arguments: Raw arguments as received from the model / framework.
    :return: The normalized arguments as a dict.
    :raises InvalidParams: If the arguments cannot be resolved to a dict.
    """
    if arguments is None:
        return {}

    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],

View on GitHub (pinned to 64b1b85502)

Solutions

  1. Pass a dict when you have structured data: normalize_tool_arguments({...}) instead of a string
  2. If you must handle strings, pre-validate with json.loads and repair common issues (single quotes, fences, truncation) before calling
  3. Enable provider-side JSON/structured output so the model emits valid JSON
  4. Log the raw string on failure — the wrapped JSONDecodeError position pinpoints the breakage

Example fix

# before
result = tool.run(arguments="{'query': 'x'}")
# after
result = tool.run(arguments='{"query": "x"}')
# or better
result = tool.run(arguments={"query": "x"})
Defensive patterns

Strategy: try-catch

Validate before calling

import json
def parse_args_string(s):
    if not isinstance(s, str): return s
    s = s.strip()
    if not s: return {}
    try: return json.loads(s)
    except json.JSONDecodeError: return None  # caller repairs or rejects

Type guard

def is_json_object_string(s: str) -> bool:
    try:
        return isinstance(json.loads(s), dict)
    except (json.JSONDecodeError, TypeError):
        return False

Try / catch

from composio.exceptions import InvalidParams
try:
    args = normalize_tool_arguments(raw)
except InvalidParams as e:
    raw = repair_json_like(raw)  # fix quotes/fences, or ask model to retry
    args = normalize_tool_arguments(raw)

Prevention

When it happens

Trigger: Passing arguments='{'query': 'x'}' (single quotes, invalid JSON), a truncated response, a string with a leading/trailing code fence or prose, or a Python-repr dict ("{'a': 1}") straight from an LLM or str(dict).

Common situations: Models emitting Python-dict syntax instead of strict JSON; tool-call arguments streamed and truncated; markdown fences around JSON; double-encoded JSON ('\"{\\"a\\":1}\"') from a provider quirk; ad-hoc scripts passing str(payload).

Related errors


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