{"record":{"id":"38a589a4012e172d","repo":"hiyouga/LlamaFactory","slug":"a-placeholder-is-required-in-the-string-formatter","errorCode":null,"errorMessage":"A placeholder is required in the string formatter.","messagePattern":"A placeholder is required in the string formatter\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/llamafactory/data/formatter.py","lineNumber":69,"sourceCode":"\n        if has_placeholder:\n            raise ValueError(\"Empty formatter should not contain any placeholder.\")\n\n    @override\n    def apply(self, **kwargs) -> SLOTS:\n        return self.slots\n\n\n@dataclass\nclass StringFormatter(Formatter):\n    def __post_init__(self):\n        has_placeholder = False\n        for slot in filter(lambda s: isinstance(s, str), self.slots):\n            if re.search(r\"\\{\\{[a-zA-Z_][a-zA-Z0-9_]*\\}\\}\", slot):\n                has_placeholder = True\n\n        if not has_placeholder:\n            raise ValueError(\"A placeholder is required in the string formatter.\")\n\n    @override\n    def apply(self, **kwargs) -> SLOTS:\n        elements = []\n        for slot in self.slots:\n            if isinstance(slot, str):\n                for name, value in kwargs.items():\n                    if not isinstance(value, str):\n                        raise RuntimeError(f\"Expected a string, got {value}\")\n\n                    slot = slot.replace(\"{{\" + name + \"}}\", value, 1)\n                elements.append(slot)\n            elif isinstance(slot, (dict, set)):\n                elements.append(slot)\n            else:\n                raise RuntimeError(f\"Input must be string, set[str] or dict[str, str], got {type(slot)}.\")\n\n        return elements","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/hiyouga/LlamaFactory/blob/f28afaf6355af515454dfb16c97d728307c93897/src/llamafactory/data/formatter.py#L51-L87","documentation":"StringFormatter.__post_init__ requires at least one string slot matching the {{identifier}} placeholder regex; otherwise it raises ValueError. Since StringFormatter.apply() works purely by substituting {{name}} patterns, a formatter with no placeholder would silently drop all example content.","triggerScenarios":"Registering a custom template with StringFormatter(slots=[\"User asked something.\"]) — literal text with no {{query}}/{{content}} token; also placeholders with invalid identifier characters ({{user query}} with a space, {{1}}) do not count because the regex requires [a-zA-Z_][a-zA-Z0-9_]*.","commonSituations":"Hand-written custom templates; placeholders renamed to non-identifier forms during dataset schema changes; copy-paste where {{}} got stripped by templating tools.","solutions":["Add the intended placeholder, e.g. {{query}} for alpaca-style or {{content}} for sharegpt-style slots.","Ensure the placeholder is a valid identifier (letters/digits/underscore, not starting with a digit, no spaces).","If the slot is truly constant, use EmptyFormatter instead."],"exampleFix":"# before\nStringFormatter(slots=[\"Human: question\\nAssistant:\"])\n\n# after\nStringFormatter(slots=[\"Human: {{query}}\\nAssistant:\"])","handlingStrategy":"validation","validationCode":"import re\nph = any(re.search(r\"\\{\\{[a-zA-Z_][a-zA-Z0-9_]*\\}\\}\", s) for s in slots if isinstance(s, str))\nif formatter_cls is StringFormatter:\n    assert ph, \"StringFormatter needs at least one valid {{identifier}} placeholder\"","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use identifier-style placeholder names matching dataset columns (query, response, content...).","Add a template-definition unit test to catch formatter misuse before training."],"tags":["formatter","template","config","custom-template"],"backgroundTag":null,"analyzedSha":"f28afaf6355af515454dfb16c97d728307c93897","analyzedAt":"2026-08-14T21:57:28.298Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}