hiyouga/LlamaFactory · error · ValueError

A placeholder is required in the string formatter.

Error message

A placeholder is required in the string formatter.

What it means

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.

Source

Thrown at src/llamafactory/data/formatter.py:69

        if has_placeholder:
            raise ValueError("Empty formatter should not contain any placeholder.")

    @override
    def apply(self, **kwargs) -> SLOTS:
        return self.slots


@dataclass
class StringFormatter(Formatter):
    def __post_init__(self):
        has_placeholder = False
        for slot in filter(lambda s: isinstance(s, str), self.slots):
            if re.search(r"\{\{[a-zA-Z_][a-zA-Z0-9_]*\}\}", slot):
                has_placeholder = True

        if not has_placeholder:
            raise ValueError("A placeholder is required in the string formatter.")

    @override
    def apply(self, **kwargs) -> SLOTS:
        elements = []
        for slot in self.slots:
            if isinstance(slot, str):
                for name, value in kwargs.items():
                    if not isinstance(value, str):
                        raise RuntimeError(f"Expected a string, got {value}")

                    slot = slot.replace("{{" + name + "}}", value, 1)
                elements.append(slot)
            elif isinstance(slot, (dict, set)):
                elements.append(slot)
            else:
                raise RuntimeError(f"Input must be string, set[str] or dict[str, str], got {type(slot)}.")

        return elements

View on GitHub (pinned to f28afaf635)

Solutions

  1. Add the intended placeholder, e.g. {{query}} for alpaca-style or {{content}} for sharegpt-style slots.
  2. Ensure the placeholder is a valid identifier (letters/digits/underscore, not starting with a digit, no spaces).
  3. If the slot is truly constant, use EmptyFormatter instead.

Example fix

# before
StringFormatter(slots=["Human: question\nAssistant:"])

# after
StringFormatter(slots=["Human: {{query}}\nAssistant:"])
Defensive patterns

Strategy: validation

Validate before calling

import re
ph = any(re.search(r"\{\{[a-zA-Z_][a-zA-Z0-9_]*\}\}", s) for s in slots if isinstance(s, str))
if formatter_cls is StringFormatter:
    assert ph, "StringFormatter needs at least one valid {{identifier}} placeholder"

Prevention

When it happens

Trigger: 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_]*.

Common situations: Hand-written custom templates; placeholders renamed to non-identifier forms during dataset schema changes; copy-paste where {{}} got stripped by templating tools.

Related errors


AI-assisted analysis of hiyouga/LlamaFactory@f28afaf635 (2026-08-14). Data as JSON: /api/errors/38a589a4012e172d. Report an issue: GitHub.