sgl-project/sglang · error · ValueError

Assistant tool call function.arguments must be valid JSON.

Error message

Assistant tool call function.arguments must be valid JSON.

What it means

parse_tool_call_arguments fails when an assistant message's tool_call function.arguments string is not valid JSON. Prior assistant turns are re-rendered through chat templates, so their arguments must parse as JSON (SGLang uses orjson).

Source

Thrown at python/sglang/srt/entrypoints/openai/serving_chat.py:132

    if role != "tool" or not isinstance(content, list):
        return content
    parts = content
    is_openai_text_parts = all(
        (isinstance(p, dict) and p.get("type") == "text") or isinstance(p, str)
        for p in parts
    )
    if is_openai_text_parts:
        text_parts = [p.get("text", "") if isinstance(p, dict) else p for p in parts]
        return " ".join(text_parts)
    return content


def parse_tool_call_arguments(arguments: str) -> Dict[str, Any]:
    """Parse OpenAI tool call arguments for chat templates."""
    try:
        parsed_arguments = orjson.loads(arguments)
    except orjson.JSONDecodeError as exc:
        raise ValueError(
            "Assistant tool call function.arguments must be valid JSON."
        ) from exc

    if not isinstance(parsed_arguments, dict):
        raise ValueError(
            "Assistant tool call function.arguments must be a JSON object."
        )

    return parsed_arguments


def normalize_assistant_tool_call_arguments(
    message: Dict[str, Any], *, strict: bool = True
) -> None:
    """Normalize assistant history tool call arguments in-place."""
    if message.get("role") != "assistant" or not isinstance(
        message.get("tool_calls"), list
    ):

View on GitHub (pinned to 0132848349)

Solutions

  1. Ensure arguments is a serialized JSON object string, e.g. '{"city": "SF"}'
  2. If replaying streamed tool calls, accumulate argument fragments until finish_reason before sending back
  3. Repair malformed history with orjson.loads + json.dumps round-trip

Example fix

// before
{"role":"assistant","tool_calls":[{"id":"1","type":"function","function":{"name":"f","arguments":"{city: SF}"}}]}
// after
{"role":"assistant","tool_calls":[{"id":"1","type":"function","function":{"name":"f","arguments":"{\"city\": \"SF\"}"}}]}
Defensive patterns

Strategy: validation

Validate before calling

import json
for tc in assistant_msg.get("tool_calls", []):
    json.loads(tc["function"]["arguments"])  # must not raise

Type guard

def valid_tool_call_args(s):
    try:
        return isinstance(json.loads(s), dict)
    except (json.JSONDecodeError, TypeError):
        return False

Try / catch

except ValueError as e: if 'valid JSON' in str(e): repair args via json.dumps(parsed_obj) and resend

Prevention

When it happens

Trigger: POST /v1/chat/completions with messages containing {"role":"assistant","tool_calls":[{"function":{"arguments":"not json"}}]}.

Common situations: Feeding back partial/streamed tool-call deltas whose arguments were concatenated incorrectly; models producing malformed arguments that are echoed back; hand-crafted conversation histories with placeholder arguments like '{...}'.

Related errors


AI-assisted analysis of sgl-project/sglang@0132848349 (2026-08-28). Data as JSON: /api/errors/f80fe575facd6fac. Report an issue: GitHub.