hiyouga/LlamaFactory · error · RuntimeError

Expected a string, got {value}

Error message

Expected a string, got {value}

What it means

Inside StringFormatter.apply, for each string slot every kwarg value is substituted; a value that is not a Python str raises RuntimeError. apply receives fields extracted from the dataset example (query, response, system, content, ...), so a non-string column value — None (missing field), int/float, list, or dict — triggers this.

Source

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

@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


@dataclass
class FunctionFormatter(StringFormatter):
    def __post_init__(self):
        super().__post_init__()
        self.tool_utils = get_tool_utils(self.tool_format)

    @override

View on GitHub (pinned to f28afaf635)

Solutions

  1. Inspect the failing example: the message prints the offending value, which identifies the missing/mistyped column.
  2. Fix dataset_info.json column mapping (e.g. output column name) so every placeholder resolves to a string field.
  3. Normalize the dataset: cast columns to str, fill missing fields with "", and ensure single-turn templates get scalar strings not lists.
  4. If a field can legitimately be absent, preprocess to empty string before training.

Example fix

# before: dataset rows have null "output" for some examples
# StringFormatter "{{query}} -> {{response}}" receives response=None

# after: clean the dataset first
ds = ds.map(lambda x: {"output": x["output"] or ""})  # and map output->response in dataset_info
Defensive patterns

Strategy: validation

Validate before calling

# validate dataset fields against template placeholders before training
PLACEHOLDERS = {"query", "response", "system", "content", "tools"}
for ex in itertools.islice(dataset, 100):
    for k in PLACEHOLDERS & set(ex):
        assert ex[k] is None or isinstance(ex[k], str), f"column {k} must be str or None, got {type(ex[k])}"

Type guard

def is_str_or_none(v) -> bool:
    return v is None or isinstance(v, str)

Try / catch

try:
    encoded = template.encode(example)
except RuntimeError as e:
    if "Expected a string" in str(e):
        logger.error("non-string column in example %s — fix column mapping", example)
        raise

Prevention

When it happens

Trigger: Formatting a dataset whose examples lack a field used by the template (None substituted); columns stored as lists (e.g. multi-turn content arrays fed to a single-turn template); numeric labels in a column mapped to a placeholder.

Common situations: Dataset schema drift: field renamed (output -> response) so lookups return None; sharegpt conversations lists passed to alpaca formatting; JSON datasets where a value parses as number instead of string.

Related errors


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