{"record":{"id":"b3858d8c0bda2780","repo":"sgl-project/sglang","slug":"expected-a-json-array-of-tool-calls-got-type-too","errorCode":null,"errorMessage":"expected a JSON array of tool calls, got {type(tool_call_data).__name__}","messagePattern":"expected a JSON array of tool calls, got (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":500,"severity":"error","filePath":"python/sglang/srt/entrypoints/openai/serving_chat.py","lineNumber":2169,"sourceCode":"                    \"Required tool call missing from %s output (%d chars)\",\n                    self.tool_call_parser,\n                    len(text),\n                )\n                logger.debug(\"Unparsed required tool call output: %r\", text[:2000])\n                return ToolCallProcessingResult(None, text, finish_reason)\n\n        # json_schema constraint → JSON array output for required/named\n        if is_required:\n            original_finish_type = finish_reason[\"type\"]\n            if finish_reason[\"type\"] == \"stop\":\n                finish_reason[\"type\"] = \"tool_calls\"\n                finish_reason[\"matched\"] = None\n            try:\n                tool_call_data = orjson.loads(text)\n                if isinstance(tool_call_data, dict):\n                    tool_call_data = [tool_call_data]\n                if not isinstance(tool_call_data, list):\n                    raise ValueError(\n                        \"expected a JSON array of tool calls, got \"\n                        f\"{type(tool_call_data).__name__}\"\n                    )\n                if not all(\n                    isinstance(tool, dict) and \"name\" in tool for tool in tool_call_data\n                ):\n                    raise ValueError(\n                        \"every tool call must be a JSON object with a 'name'\"\n                    )\n                tool_calls = []\n                for i, tool in enumerate(tool_call_data):\n                    parameters = json.dumps(\n                        tool.get(\"parameters\", {}), ensure_ascii=False\n                    )\n                    call_info = ToolCallItem(\n                        tool_index=i,\n                        name=tool[\"name\"],\n                        parameters=parameters,","sourceCodeStart":2151,"sourceCodeEnd":2187,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/srt/entrypoints/openai/serving_chat.py#L2151-L2187","documentation":"In the JSON-fallback tool-call path, SGLang parsed the model's text output as JSON but the top-level value was neither a JSON object nor an array (e.g. a string or number). The parser expected [{\"name\": ..., \"parameters\": ...}, ...] and raises this ValueError during _process_tool_calls.","triggerScenarios":"A tool-calling request where the model emits malformed tool-call JSON (e.g. \"`[{...}]`\" with markdown fences left after stripping, or a bare scalar), and no native parser handled it, so the JSON fallback path in serving_chat.py:2169 runs.","commonSituations":"Weak models producing wrapped/fenced JSON; a custom function-call parser whose normalizer doesn't strip code fences; truncated outputs from max_tokens hitting mid-JSON.","solutions":["Use a model + --tool-call-parser pair that reliably emits the expected JSON format","If output is fenced/truncated, increase max_tokens or strip markdown fences in the parser's normalizer","Catch the error server-side and treat the message as plain content (the surrounding code already logs and drops some malformed tool calls)","Validate model output format with a small smoke test before deploying tool use"],"exampleFix":"# before\n[{\"name\": \"get_weather\", ...}]```   # trailing fence -> orjson yields list OK; but '\"[{...}]\"' string fails\n# after\n[{\"name\": \"get_weather\", \"parameters\": {\"city\": \"SF\"}}]","handlingStrategy":"try-catch","validationCode":"def parse_tool_json(text):\n    data = orjson.loads(text)\n    if isinstance(data, dict): data = [data]\n    assert isinstance(data, list), 'not a JSON array'\n    return data","typeGuard":"def is_tool_array(v) -> bool:\n    return isinstance(v, list) and all(isinstance(t, dict) and \"name\" in t for t in v)","tryCatchPattern":"try: resp = client.chat.completions.create(tools=[...])\nexcept BadRequestError: retry with stricter tool prompt or no tools","preventionTips":["Use a verified --tool-call-parser for the model","Strip code fences from model output before JSON parse","Add tool-format smoke tests per model"],"tags":["tool-calls","json-parsing","openai-api"],"backgroundTag":"tool-call-json-parse-failed","analyzedSha":"0132848349585cfe6aae51c4941cbae872505f8a","analyzedAt":"2026-08-28T05:10:05.995Z","schemaVersion":2},"datasetVersion":"2026-08-28T06:17:29.519Z"}