infiniflow/ragflow · error · TypeError
Tool arguments for {name} must be an object, got {type(argum
Error message
Tool arguments for {name} must be an object, got {type(arguments).__name__} What it means
Raised by LLMToolPluginCallSession.tool_call_async when the `arguments` value dispatched for a tool call is not a Mapping (dict-like). LLMs sometimes emit a JSON string, array, or null for arguments; the session explicitly type-checks before splatting (`**arguments`) or forwarding to MCP bindings, and raises TypeError with the offending Python type name.
Source
Thrown at agent/tools/base.py:62
displayName: str
description: str
displayDescription: str
parameters: dict[str, ToolParameter]
class LLMToolPluginCallSession(ToolCallSession):
def __init__(self, tools_map: dict[str, object], callback: partial):
self.tools_map = tools_map
self.callback = callback
def tool_call(self, name: str, arguments: dict[str, Any], timeout: float | int = 10) -> Any:
return asyncio.run(self.tool_call_async(name, arguments, request_timeout=timeout))
async def tool_call_async(self, name: str, arguments: dict[str, Any], request_timeout: float | int = 10) -> Any:
assert name in self.tools_map, f"LLM tool {name} does not exist"
logging.info(f"[ToolCall] invoke name={name} arguments={str(arguments)[:200]}")
if not isinstance(arguments, Mapping):
raise TypeError(f"Tool arguments for {name} must be an object, got {type(arguments).__name__}")
st = timer()
tool_obj = self.tools_map[name]
if isinstance(tool_obj, MCPToolBinding):
resp = await thread_pool_exec(tool_obj.session.tool_call, tool_obj.original_name, arguments, request_timeout)
elif isinstance(tool_obj, MCPToolCallSession):
resp = await thread_pool_exec(tool_obj.tool_call, name, arguments, request_timeout)
elif hasattr(tool_obj, "invoke_async") and asyncio.iscoroutinefunction(tool_obj.invoke_async):
resp = await tool_obj.invoke_async(**arguments)
else:
resp = await thread_pool_exec(tool_obj.invoke, **arguments)
if resp is None and hasattr(tool_obj, "output") and callable(tool_obj.output):
try:
fallback_output = tool_obj.output()
if isinstance(fallback_output, dict) and fallback_output.get("content") not in (None, ""):
resp = fallback_output["content"]
elif fallback_output not in (None, ""):
resp = fallback_outputView on GitHub (pinned to 554fb1133a)
Solutions
- Parse/normalize arguments before dispatch: `if isinstance(arguments, str): arguments = json.loads(arguments)`.
- Reject non-object argument payloads at the LLM-response parsing layer and re-prompt the model with the schema error.
- Use the type guard below in custom tool-dispatch code that calls tool_call/tool_call_async.
- Upgrade or switch the model endpoint if it persistently emits stringified arguments.
Example fix
# before resp = await session.tool_call_async(name, raw_args) # raw_args is a JSON string -> TypeError # after args = json.loads(raw_args) if isinstance(raw_args, str) else raw_args resp = await session.tool_call_async(name, args)
Defensive patterns
Strategy: type-guard
Validate before calling
import json
from collections.abc import Mapping
if isinstance(arguments, str):
arguments = json.loads(arguments) # may raise; handle parse errors at this boundary
if not isinstance(arguments, Mapping):
raise ValueError(f"tool arguments must be an object, got {type(arguments).__name__}")
resp = await session.tool_call_async(name, dict(arguments)) Type guard
from collections.abc import Mapping
def is_tool_arguments_object(arguments) -> bool:
"""True when arguments is usable as **kwargs for a tool call."""
return isinstance(arguments, Mapping) Try / catch
try:
resp = await session.tool_call_async(name, arguments)
except TypeError as e:
if "must be an object" in str(e):
arguments = json.loads(arguments) if isinstance(arguments, str) else {}
resp = await session.tool_call_async(name, arguments)
else:
raise Prevention
- json.loads string arguments once, at the LLM-response parsing layer, before dispatch.
- Assert Mapping (not dict) — typed dicts, MappingProxyType and pydantic models all pass Mapping but some fail dict checks.
- Constrain tool schemas so models emit object arguments; reject array-form arguments with a corrective re-prompt.
When it happens
Trigger: An LLM returns tool-call arguments as a raw JSON string (e.g. `'{"query": "x"}'`), a list, or None instead of a parsed object; or custom orchestration forwards unparsed JSON into session.tool_call(). Note the assert above it also requires the tool name to exist in tools_map.
Common situations: Models that stringify JSON arguments (common with some OpenAI-compatible/older models); frameworks that skip json.loads on the arguments field; function-calling payloads where arguments arrive as an array of positional values.
Related errors
- JSON payload must be an object.
- flow.formatTypeError
- Login failed: invalid JSON response ({exc})
- Invoke JSON argument '{key}' is not JSON-serializable.
- Invoke headers must be a JSON object.
AI-assisted analysis of infiniflow/ragflow@554fb1133a (2026-08-15).
Data as JSON: /api/errors/9b435aa1c7aef657.
Report an issue: GitHub.