hiyouga/LlamaFactory · error · ValueError
tool_call value is not valid JSON: {content['value']!r}
Error message
tool_call value is not valid JSON: {content['value']!r} What it means
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.
Source
Thrown at src/llamafactory/v1/core/rendering/format.py:56
def _to_hf_messages(messages: list[Message], is_multimodal: bool = False) -> list[dict]:
"""Convert v1 Message format to HF format for apply_chat_template."""
hf_messages = []
for message in messages:
tool_calls: list[dict] = []
reasoning_content = ""
if is_multimodal:
hf_content = []
for content in message["content"]:
if content["type"] == "text":
hf_content.append({"type": "text", "text": content["value"]})
elif content["type"] == "reasoning":
reasoning_content += content["value"]
elif content["type"] == "tool_call":
try:
tc = json.loads(content["value"])
except json.JSONDecodeError as e:
raise ValueError(f"tool_call value is not valid JSON: {content['value']!r}") from e
if not isinstance(tc, dict) or "name" not in tc or "arguments" not in tc:
raise ValueError(
f"tool_call must be a JSON object with 'name' and 'arguments' keys, got {tc!r}"
)
tool_calls.append(
{"type": "function", "function": {"name": tc["name"], "arguments": tc["arguments"]}}
)
elif content["type"] == "image_url":
hf_content.append({"type": "image", "image": content["value"]})
elif content["type"] == "video_url":
hf_content.append({"type": "video", "video": content["value"]})
elif content["type"] == "audio_url":
hf_content.append({"type": "audio", "audio": content["value"]})
hf_msg = {"role": message["role"], "content": hf_content}
else:
text = ""
for content in message["content"]:
if content["type"] == "text":View on GitHub (pinned to f28afaf635)
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
Example fix
# before
content = {"type": "tool_call", "value": {"name": "get_weather", "arguments": {"city": "SF"}}}
# after
import json
content = {"type": "tool_call", "value": json.dumps({"name": "get_weather", "arguments": {"city": "SF"}})} Defensive patterns
Strategy: validation
Validate before calling
import json
def valid_tool_call_value(value) -> bool:
if not isinstance(value, str):
return False
try:
json.loads(value)
return True
except json.JSONDecodeError:
return False Prevention
- Always json.dumps tool_call values at dataset build time
- Add a dataset unit test that json.loads every tool_call block
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- tool_call must be a JSON object with 'name' and 'arguments'
- Invalid JSON format in function message: {str([content])}.
- {kind} placeholder count ({seen}) != number of {kind} blocks
- Invalid JSON format in tool description: {str([content])}.
- The number of images does not match the number of {IMAGE_PLA
AI-assisted analysis of hiyouga/LlamaFactory@f28afaf635 (2026-08-14).
Data as JSON: /api/errors/bf70ad70eb91406f.
Report an issue: GitHub.