{"record":{"id":"bf70ad70eb91406f","repo":"hiyouga/LlamaFactory","slug":"tool-call-value-is-not-valid-json-content-value","errorCode":null,"errorMessage":"tool_call value is not valid JSON: {content['value']!r}","messagePattern":"tool_call value is not valid JSON: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/llamafactory/v1/core/rendering/format.py","lineNumber":56,"sourceCode":"def _to_hf_messages(messages: list[Message], is_multimodal: bool = False) -> list[dict]:\n    \"\"\"Convert v1 Message format to HF format for apply_chat_template.\"\"\"\n    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\":","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/hiyouga/LlamaFactory/blob/f28afaf6355af515454dfb16c97d728307c93897/src/llamafactory/v1/core/rendering/format.py#L38-L74","documentation":"While converting internal Message blocks to HuggingFace chat format, a content block of type 'tool_call' must carry a JSON-serializable string. This is the multimodal branch (format.py:56): json.loads(content['value']) raised JSONDecodeError, so the tool_call value is not parseable JSON and the message cannot be rendered.","triggerScenarios":"Passing a multimodal message whose content list contains {\"type\": \"tool_call\", \"value\": ...} where value is a Python dict (not a JSON string), truncated JSON, or plain text. Only triggered on the is_multimodal=True code path.","commonSituations":"Dataset converters that store tool_call arguments as native dicts instead of JSON strings; partially-truncated tool-call samples from scraped agent logs; function-calling datasets formatted for a different schema.","solutions":["Ensure every tool_call block's 'value' is a valid JSON string, e.g. json.dumps({'name': ..., 'arguments': ...})","If value is already a dict, serialize it: value = json.dumps(value)","Validate/repair the dataset: parse each tool_call value with json.loads in preprocessing and drop or fix failures"],"exampleFix":"# before\ncontent = {\"type\": \"tool_call\", \"value\": {\"name\": \"get_weather\", \"arguments\": {\"city\": \"SF\"}}}\n\n# after\nimport json\ncontent = {\"type\": \"tool_call\", \"value\": json.dumps({\"name\": \"get_weather\", \"arguments\": {\"city\": \"SF\"}})}","handlingStrategy":"validation","validationCode":"import json\n\ndef valid_tool_call_value(value) -> bool:\n    if not isinstance(value, str):\n        return False\n    try:\n        json.loads(value)\n        return True\n    except json.JSONDecodeError:\n        return False","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always json.dumps tool_call values at dataset build time","Add a dataset unit test that json.loads every tool_call block"],"tags":["tool-calls","json","multimodal","data-format","rendering"],"backgroundTag":null,"analyzedSha":"f28afaf6355af515454dfb16c97d728307c93897","analyzedAt":"2026-08-14T21:57:28.298Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}