{"record":{"id":"79dda504a0ceeaee","repo":"xtekky/gpt4free","slug":"tool-function-arguments-must-be-a-dictionary-or-a","errorCode":null,"errorMessage":"Tool function arguments must be a dictionary or a json string","messagePattern":"Tool function arguments must be a dictionary or a json string","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"g4f/tools/run_tools.py","lineNumber":166,"sourceCode":"\n    Providers that extend ``OpenaiTemplate`` (or set ``supports_native_tools = True``)\n    are assumed to forward ``tools``/``tool_choice`` to an OpenAI-compatible endpoint\n    and therefore do not need prompt-injection emulation.\n    \"\"\"\n    return bool(getattr(provider, \"supports_native_tools\", False))\n\n\nclass ToolHandler:\n    \"\"\"Handles processing of different tool types\"\"\"\n\n    @staticmethod\n    def validate_arguments(data: dict) -> dict:\n        \"\"\"Validate and parse tool arguments\"\"\"\n        if \"arguments\" in data:\n            if isinstance(data[\"arguments\"], str):\n                data[\"arguments\"] = json.loads(data[\"arguments\"])\n            if not isinstance(data[\"arguments\"], dict):\n                raise ValueError(\n                    \"Tool function arguments must be a dictionary or a json string\"\n                )\n            else:\n                return filter_none(**data[\"arguments\"])\n        else:\n            return {}\n\n    @staticmethod\n    async def process_search_tool(messages: Messages, tool: dict) -> Messages:\n        \"\"\"Process search tool requests\"\"\"\n        messages = messages.copy()\n        args = ToolHandler.validate_arguments(tool[\"function\"])\n        messages[-1][\"content\"], sources = await do_search(\n            messages[-1][\"content\"], **args\n        )\n        return messages, sources\n\n    @staticmethod","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/xtekky/gpt4free/blob/973504e1770928ed5fb82f43da528f441ad9ddc3/g4f/tools/run_tools.py#L148-L184","documentation":"ToolHandler.validate_arguments in g4f/tools/run_tools.py raises this ValueError when a tool call's 'arguments' field, after being parsed, is not a JSON object (dict). The method accepts either a dict directly or a JSON string (which it json.loads first), but any other JSON type (array, number, boolean, null) fails validation. Note that a malformed JSON string fails earlier, inside json.loads, with a JSONDecodeError.","triggerScenarios":"Calling a tool-processing API (e.g. ToolHandler.process_search_tool or any handler that calls validate_arguments(tool['function'])) where tool['function']['arguments'] is a JSON string like '[1,2]', '\"text\"', '5', 'null', or an already-parsed Python list/None. Typically happens when an LLM provider emits malformed tool-call arguments (a common failure of smaller models).","commonSituations":"Switching to a provider whose models emit non-object tool arguments; aggressive logit or cheap models returning arrays instead of objects; hand-built tool payloads in tests; middleware that pre-parses arguments into non-dict types.","solutions":["Fix the payload so arguments is a JSON object string, e.g. '{\"query\": \"...\"}' — not an array or scalar.","If the model produced the bad arguments, switch to a model/provider with reliable function-calling, or add a repair prompt asking the model to re-emit arguments as a JSON object.","Pre-validate before calling the handler: parse the string yourself and confirm isinstance(parsed, dict), returning a user-facing error instead of a crash.","Catch ValueError (and json.JSONDecodeError) around the tool-processing call to skip/log the malformed tool call."],"exampleFix":"// before\narguments = \"['latest news']\"  // array string -> ValueError\n\n// after\narguments = \"{\\\"query\\\": \\\"latest news\\\"}\"  // JSON object string","handlingStrategy":"validation","validationCode":"import json\n\ndef is_valid_tool_arguments(fn: dict) -> bool:\n    args = fn.get(\"arguments\")\n    if isinstance(args, str):\n        try:\n            args = json.loads(args)\n        except json.JSONDecodeError:\n            return False\n    return isinstance(args, dict)","typeGuard":"def is_tool_args_dict(value) -> bool:\n    if isinstance(value, str):\n        try:\n            value = json.loads(value)\n        except json.JSONDecodeError:\n            return False\n    return isinstance(value, dict)","tryCatchPattern":"try:\n    result = await ToolHandler.process_search_tool(messages, tool)\nexcept ValueError as e:\n    if \"arguments\" in str(e):\n        # log and skip the malformed tool call, ask the model to re-emit\n        ...\n    raise","preventionTips":["Always serialize tool-call arguments as a JSON object string, never an array or scalar","Prefer providers/models with reliable native function calling","Validate arguments shape before handing tool dicts to g4f handlers"],"tags":["tool-calling","json","validation","llm-output"],"backgroundTag":null,"analyzedSha":"973504e1770928ed5fb82f43da528f441ad9ddc3","analyzedAt":"2026-08-14T23:45:32.408Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}