{"record":{"id":"23e2c2eefa97b9b4","repo":"hiyouga/LlamaFactory","slug":"invalid-json-format-in-function-message-str-con","errorCode":null,"errorMessage":"Invalid JSON format in function message: {str([content])}.","messagePattern":"Invalid JSON format in function message: (.+?)\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"src/llamafactory/data/formatter.py","lineNumber":110,"sourceCode":"    def __post_init__(self):\n        super().__post_init__()\n        self.tool_utils = get_tool_utils(self.tool_format)\n\n    @override\n    def apply(self, **kwargs) -> SLOTS:\n        content: str = kwargs.pop(\"content\")\n        thought_words = kwargs.pop(\"thought_words\", None)\n        tool_call_words = kwargs.pop(\"tool_call_words\", None)\n\n        def _parse_functions(json_content: str) -> list[\"FunctionCall\"]:\n            try:\n                tool_calls = json.loads(json_content)\n                if not isinstance(tool_calls, list):  # parallel function call\n                    tool_calls = [tool_calls]\n\n                return [FunctionCall(tc[\"name\"], json.dumps(tc[\"arguments\"], ensure_ascii=False)) for tc in tool_calls]\n            except json.JSONDecodeError:\n                raise RuntimeError(f\"Invalid JSON format in function message: {str([content])}.\")\n\n        tool_call_match = None\n        if tool_call_words and len(tool_call_words) == 2:\n            tool_call_regex = re.compile(\n                rf\"{re.escape(tool_call_words[0])}(.*?){re.escape(tool_call_words[1])}\", re.DOTALL\n            )\n            tool_call_match = re.search(tool_call_regex, content)\n\n        if tool_call_match is None:\n            thought_match = None\n            if thought_words and len(thought_words) == 2:\n                regex = re.compile(rf\"{re.escape(thought_words[0])}(.*?){re.escape(thought_words[1])}\", re.DOTALL)\n                thought_match = re.search(regex, content)\n\n            if thought_match:\n                json_part = content.replace(thought_match.group(0), \"\")\n            else:\n                json_part = content","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/hiyouga/LlamaFactory/blob/f28afaf6355af515454dfb16c97d728307c93897/src/llamafactory/data/formatter.py#L92-L128","documentation":"Raised inside FunctionFormatter.apply -> _parse_functions when the `content` of a function/assistant tool-call message cannot be parsed by json.loads (json.JSONDecodeError). LlamaFactory expects the function_call or tool_calls content in your dataset to be valid JSON (an object or a list of objects with 'name' and 'arguments'). This is a per-sample data validation error: one bad row aborts preprocessing.","triggerScenarios":"A sharegpt-format dataset whose assistant messages with function calls contain malformed JSON (truncated responses, single quotes, trailing commas, raw markdown fences, or plain-text tool output placed in the function content field). Occurs during dataset preprocessing for tool-use SFT/DPO when tool_format is set (e.g. react, llama3, qwen).","commonSituations":"Log datasets harvested from model outputs where tool calls were not strictly serialized; datasets converted from other frameworks that wrap JSON in ```json fences or prose; encodings/BOM issues; non-ASCII content saved without proper quoting.","solutions":["Locate the offending sample using the content echoed in the error message and fix its JSON so it is an object or list of {\"name\": ..., \"arguments\": {...}}.","Sanitize the whole dataset with a script that json.loads each tool-call content and drops/repairs failures before training.","Strip markdown fences, leading/trailing prose, and ensure double quotes are used; re-save with ensure_ascii=False.","If the samples are intentionally non-JSON thought traces, move them to the normal content field and keep only real calls in the function-call field."],"exampleFix":"# before (dataset row)\n{\"from\": \"function_call\", \"value\": \"```json\\n{ 'name': 'search', 'arguments': {'q': 'hf'} }\\n```\"}\n\n# after\n{\"from\": \"function_call\", \"value\": \"{\\\"name\\\": \\\"search\\\", \\\"arguments\\\": {\\\"q\\\": \\\"hf\\\"}}\"}","handlingStrategy":"validation","validationCode":"import json\n\ndef tool_call_rows_ok(rows: list[dict]) -> list[int]:\n    bad = []\n    for i, r in enumerate(rows):\n        content = r.get(\"function_call\") or r.get(\"tool_calls\")\n        if content:\n            try:\n                parsed = json.loads(content)\n                if not isinstance(parsed, (dict, list)):\n                    bad.append(i)\n            except json.JSONDecodeError:\n                bad.append(i)\n    return bad","typeGuard":"def is_valid_tool_call_json(content: str) -> bool:\n    try:\n        parsed = json.loads(content)\n        return isinstance(parsed, (dict, list)) and all(\"name\" in tc and \"arguments\" in tc for tc in (parsed if isinstance(parsed, list) else [parsed]))\n    except (json.JSONDecodeError, TypeError):\n        return False","tryCatchPattern":"try:\n    json.loads(content)\nexcept json.JSONDecodeError as e:\n    logger.warning(\"sample %d has malformed tool call JSON: %s\", idx, e)\n    # repair or drop the row instead of aborting the whole run","preventionTips":["Run a JSON lint pass over function_call/tool_calls columns before training.","Never store tool calls inside markdown fences; serialize with json.dumps(..., ensure_ascii=False).","Log dropped-sample counts so silent data loss is visible."],"tags":["json","tool-calls","data-format","sharegpt"],"backgroundTag":null,"analyzedSha":"f28afaf6355af515454dfb16c97d728307c93897","analyzedAt":"2026-08-14T21:57:28.298Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}