hiyouga/LlamaFactory · error · ValueError

tool_call must be a JSON object with 'name' and 'arguments'

Error message

tool_call must be a JSON object with 'name' and 'arguments' keys, got {tc!r}

What it means

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.

Source

Thrown at src/llamafactory/v1/core/rendering/format.py:58

    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":
                    text += content["value"]
                elif content["type"] == "reasoning":

View on GitHub (pinned to f28afaf635)

Solutions

  1. Normalize each tool_call to a flat JSON object with exactly accessible 'name' and 'arguments' keys
  2. Unwrap OpenAI-style nesting: tc = json.loads(v); tc = tc['function'] if 'function' in tc else tc, then re-serialize
  3. Unwrap array-wrapped entries: take the first element if json.loads(v) returns a list of one call

Example fix

# before (OpenAI request shape)
value = json.dumps({"type": "function", "function": {"name": "f", "arguments": "{}"}})

# after (flat shape this renderer expects)
value = json.dumps({"name": "f", "arguments": {}})
Defensive patterns

Strategy: validation

Validate before calling

import json

def is_flat_tool_call(value: str) -> bool:
    try:
        tc = json.loads(value)
    except json.JSONDecodeError:
        return False
    return isinstance(tc, dict) and "name" in tc and "arguments" in tc

Prevention

When it happens

Trigger: 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.

Common situations: Feeding raw OpenAI-style payloads where the object is nested under 'function'; dataset normalization that wraps single objects in arrays; hand-written sample fixtures.

Related errors


AI-assisted analysis of hiyouga/LlamaFactory@f28afaf635 (2026-08-14). Data as JSON: /api/errors/0314258c2ff7ee05. Report an issue: GitHub.