{"record":{"id":"c75a4c2f34efe816","repo":"hiyouga/LlamaFactory","slug":"input-must-be-string-set-str-or-dict-str-str","errorCode":null,"errorMessage":"Input must be string, set[str] or dict[str, str], got {type(slot)}.","messagePattern":"Input must be string, set\\[str\\] or dict\\[str, str\\], got (.+?)\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"src/llamafactory/data/formatter.py","lineNumber":85,"sourceCode":"\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\n\n\n@dataclass\nclass FunctionFormatter(StringFormatter):\n    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:","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/hiyouga/LlamaFactory/blob/f28afaf6355af515454dfb16c97d728307c93897/src/llamafactory/data/formatter.py#L67-L103","documentation":"Thrown by StringFormatter.apply while building prompt elements from a template's slots. Every entry in a formatter's `slots` list must be a plain string, a set of strings (choice of stop tokens), or a dict[str, str]; any other Python type reaches the else-branch and raises this RuntimeError. It almost always indicates a malformed custom template registered in data/template.py or a programmatic Template construction with a non-conforming slot.","triggerScenarios":"Registering a custom template whose formatter slots contain a non-str/set/dict value (e.g. a list, tuple, int, or None), or constructing a StringFormatter/Template object directly with invalid slot types. Also triggered by a plugin/template that programmatically appends unsupported objects to slots.","commonSituations":"Users copying a template definition from an older LlamaFactory version or another project where slot conventions differed; passing a tokenizer-produced list or an int (e.g. token id) where a string slot is expected; typos when hand-writing TEMPLATES entries.","solutions":["Inspect the template definition referenced by your `template` config value and check every element of each formatter's slots: they must be str, set[str], or dict[str, str].","If you construct a Template in code, validate slots before registration and wrap non-string special tokens as a set, e.g. slots=['user: {{content}} ', {'eos_token'}].","Switch to a known-good built-in template to confirm the error comes from your custom template, then re-apply your changes incrementally.","Upgrade to the latest LlamaFactory in case the template API changed between versions."],"exampleFix":"# before\nTEMPLATES[\"my_tpl\"] = Template(\n    format_slots=[\"user: {{content}}\", [\"\\n\", \"\\n\\n\"]],  # list is invalid\n)\n\n# after\nTEMPLATES[\"my_tpl\"] = Template(\n    format_slots=[\"user: {{content}} \", {\"eos_token\"}],  # str or set[str]/dict[str, str] only\n)","handlingStrategy":"type-guard","validationCode":"from llamafactory.data.formatter import StringFormatter\n\ndef valid_slots(formatter) -> bool:\n    return all(\n        isinstance(s, str) or (isinstance(s, (set, dict)))\n        for f in (formatter,) if hasattr(f, \"slots\")\n        for s in f.slots\n    )","typeGuard":"def is_valid_slot(slot: object) -> bool:\n    if isinstance(slot, str):\n        return True\n    if isinstance(slot, set):\n        return all(isinstance(x, str) for x in slot)\n    if isinstance(slot, dict):\n        return all(isinstance(k, str) and isinstance(v, str) for k, v in slot.items())\n    return False","tryCatchPattern":"try:\n    elements = formatter.apply(**kwargs)\nexcept RuntimeError as e:\n    if \"Input must be string\" in str(e):\n        raise ValueError(f\"Malformed template slots in {formatter}\") from e\n    raise","preventionTips":["Register custom templates in a single reviewed module and unit-test slot types.","Reuse built-in templates unless a custom one is truly required.","After upgrading LlamaFactory, re-run a 1-sample preprocessing smoke test before full runs."],"tags":["templates","data-format","custom-template"],"backgroundTag":null,"analyzedSha":"f28afaf6355af515454dfb16c97d728307c93897","analyzedAt":"2026-08-14T21:57:28.298Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}