{"record":{"id":"0314258c2ff7ee05","repo":"hiyouga/LlamaFactory","slug":"tool-call-must-be-a-json-object-with-name-and-a","errorCode":null,"errorMessage":"tool_call must be a JSON object with 'name' and 'arguments' keys, got {tc!r}","messagePattern":"tool_call must be a JSON object with 'name' and 'arguments' keys, got (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/llamafactory/v1/core/rendering/format.py","lineNumber":58,"sourceCode":"    hf_messages = []\n    for message in messages:\n        tool_calls: list[dict] = []\n        reasoning_content = \"\"\n\n        if is_multimodal:\n            hf_content = []\n            for content in message[\"content\"]:\n                if content[\"type\"] == \"text\":\n                    hf_content.append({\"type\": \"text\", \"text\": content[\"value\"]})\n                elif content[\"type\"] == \"reasoning\":\n                    reasoning_content += content[\"value\"]\n                elif content[\"type\"] == \"tool_call\":\n                    try:\n                        tc = json.loads(content[\"value\"])\n                    except json.JSONDecodeError as e:\n                        raise ValueError(f\"tool_call value is not valid JSON: {content['value']!r}\") from e\n                    if not isinstance(tc, dict) or \"name\" not in tc or \"arguments\" not in tc:\n                        raise ValueError(\n                            f\"tool_call must be a JSON object with 'name' and 'arguments' keys, got {tc!r}\"\n                        )\n                    tool_calls.append(\n                        {\"type\": \"function\", \"function\": {\"name\": tc[\"name\"], \"arguments\": tc[\"arguments\"]}}\n                    )\n                elif content[\"type\"] == \"image_url\":\n                    hf_content.append({\"type\": \"image\", \"image\": content[\"value\"]})\n                elif content[\"type\"] == \"video_url\":\n                    hf_content.append({\"type\": \"video\", \"video\": content[\"value\"]})\n                elif content[\"type\"] == \"audio_url\":\n                    hf_content.append({\"type\": \"audio\", \"audio\": content[\"value\"]})\n            hf_msg = {\"role\": message[\"role\"], \"content\": hf_content}\n        else:\n            text = \"\"\n            for content in message[\"content\"]:\n                if content[\"type\"] == \"text\":\n                    text += content[\"value\"]\n                elif content[\"type\"] == \"reasoning\":","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/hiyouga/LlamaFactory/blob/f28afaf6355af515454dfb16c97d728307c93897/src/llamafactory/v1/core/rendering/format.py#L40-L76","documentation":"In the multimodal branch of _to_hf_messages, the tool_call value parsed as JSON but the result is not a dict containing both 'name' and 'arguments' keys. The renderer needs those two fields to build the HF {'type': 'function', 'function': {...}} structure, so anything else (a list, a string, a dict missing keys) is rejected.","triggerScenarios":"A multimodal message with a tool_call block whose value parses to e.g. \"[{\\\"name\\\": ...}]\" (array-wrapped), a bare string, or an object like {\"function\": {...}} (OpenAI request shape) instead of the expected flat {'name', 'arguments'} object.","commonSituations":"Feeding raw OpenAI-style payloads where the object is nested under 'function'; dataset normalization that wraps single objects in arrays; hand-written sample fixtures.","solutions":["Normalize each tool_call to a flat JSON object with exactly accessible 'name' and 'arguments' keys","Unwrap OpenAI-style nesting: tc = json.loads(v); tc = tc['function'] if 'function' in tc else tc, then re-serialize","Unwrap array-wrapped entries: take the first element if json.loads(v) returns a list of one call"],"exampleFix":"# before (OpenAI request shape)\nvalue = json.dumps({\"type\": \"function\", \"function\": {\"name\": \"f\", \"arguments\": \"{}\"}})\n\n# after (flat shape this renderer expects)\nvalue = json.dumps({\"name\": \"f\", \"arguments\": {}})","handlingStrategy":"validation","validationCode":"import json\n\ndef is_flat_tool_call(value: str) -> bool:\n    try:\n        tc = json.loads(value)\n    except json.JSONDecodeError:\n        return False\n    return isinstance(tc, dict) and \"name\" in tc and \"arguments\" in tc","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Standardize on the flat {'name', 'arguments'} shape in converters","Unwrap OpenAI 'function'-nested objects before serializing"],"tags":["tool-calls","json","schema","multimodal","rendering"],"backgroundTag":null,"analyzedSha":"f28afaf6355af515454dfb16c97d728307c93897","analyzedAt":"2026-08-14T21:57:28.298Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}