{"record":{"id":"95e5517ff2c9ac28","repo":"hiyouga/LlamaFactory","slug":"unknown-identifier-node-id","errorCode":null,"errorMessage":"Unknown identifier: {node.id}","messagePattern":"Unknown identifier: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/llamafactory/data/tool_utils.py","lineNumber":822,"sourceCode":"                    kwargs_parts.append(f\"{key}={json.dumps(value, ensure_ascii=False)}\")\n\n            calls.append(f\"{name}({', '.join(kwargs_parts)})\")\n\n        return f\"<|tool_call_start|>[{', '.join(calls)}]<|tool_call_end|>\"\n\n    @staticmethod\n    def _ast_to_value(node: ast.AST) -> Any:\n        \"\"\"Convert an AST node to a Python value, handling JSON-style booleans/null.\"\"\"\n        # Handle JSON-style true/false/null as Name nodes\n        if isinstance(node, ast.Name):\n            if node.id == \"true\":\n                return True\n            elif node.id == \"false\":\n                return False\n            elif node.id == \"null\":\n                return None\n            else:\n                raise ValueError(f\"Unknown identifier: {node.id}\")\n\n        # Use literal_eval for other cases (strings, numbers, lists, dicts)\n        return ast.literal_eval(node)\n\n    @override\n    @staticmethod\n    def tool_extractor(content: str) -> Union[str, list[\"FunctionCall\"]]:\n        # Extract content between tool call markers\n        start_marker = \"<|tool_call_start|>\"\n        end_marker = \"<|tool_call_end|>\"\n\n        start_idx = content.find(start_marker)\n        if start_idx == -1:\n            return content\n\n        end_idx = content.find(end_marker, start_idx)\n        if end_idx == -1:\n            return content","sourceCodeStart":804,"sourceCodeEnd":840,"githubUrl":"https://github.com/hiyouga/LlamaFactory/blob/f28afaf6355af515454dfb16c97d728307c93897/src/llamafactory/data/tool_utils.py#L804-L840","documentation":"The GLM-4-MoE / Qwen3.5-style tool extractor parses tool-call arguments with Python's ast module but accepts JSON-style literals: bare identifiers true/false/null are converted to True/False/None. Any other bare Name identifier (e.g. none, None, True, False, undefined) inside the arguments string raises ValueError('Unknown identifier: ...') in _ast_to_value.","triggerScenarios":"Extracting tool calls from generated text whose arguments JSON contains Python-style None/True/False or a typo'd bare word (e.g. {\"flag\": True} instead of {\"flag\": true}); running the tool extractor during dataset construction or inference post-processing on model output that emitted Python literals.","commonSituations":"Fine-tuning on or evaluating models that emit Python-style booleans/None in JSON tool arguments; round-tripping tool call data through Python's repr instead of json.dumps.","solutions":["Normalize the arguments text to strict JSON before extraction: replace True/False/None with true/false/null (or json.dumps-serialize the dict instead of str/repr).","Regenerate the offending dataset so arguments are serialized with json.dumps, never str().","If the text is model output, add a cleaning step or few-shot examples enforcing JSON booleans."],"exampleFix":"# before\n{\"name\": \"set_flag\", \"arguments\": \"{\\\"flag\\\": True}\"}\n\n# after\n{\"name\": \"set_flag\", \"arguments\": \"{\\\"flag\\\": true}\"}","handlingStrategy":"fallback","validationCode":"import re, json\n\ndef normalize_tool_args(text: str) -> str:\n    # convert Python literals to JSON before extraction\n    text = re.sub(r\"\\bTrue\\b\", \"true\", text)\n    text = re.sub(r\"\\bFalse\\b\", \"false\", text)\n    text = re.sub(r\"\\bNone\\b\", \"null\", text)\n    json.loads(text)  # raises if still invalid\n    return text","typeGuard":null,"tryCatchPattern":"try:\n    calls = utils.tool_extractor(content)\nexcept ValueError as e:\n    if \"Unknown identifier\" in str(e):\n        calls = utils.tool_extractor(normalize_tool_args(content))  # one retry after JSON normalization\n    else:\n        raise","preventionTips":["Always serialize tool arguments with json.dumps, never str()/repr().","Reject or clean model outputs containing True/False/None literals before feeding extraction."],"tags":["tools","json","ast","glm4-moe"],"backgroundTag":null,"analyzedSha":"f28afaf6355af515454dfb16c97d728307c93897","analyzedAt":"2026-08-14T21:57:28.298Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}