oobabooga/textgen · error · RuntimeError
Dataset row must contain "messages" or "conversations" key.
Error message
Dataset row must contain "messages" or "conversations" key. Found: {list(data_point.keys())} What it means
Thrown by normalize_messages() during LoRA/dataset training when a dataset row contains neither a 'messages' key (OpenAI chat format) nor a 'conversations' key (ShareGPT format). The training tokenizer path requires one of these schemas to build chat-template input, so any other column layout aborts preprocessing. The message lists the actual keys found, which tells you exactly what schema the row does have.
Source
Thrown at modules/training.py:343
target_mods = [f"{name}_proj" for name, enabled in {
"q": q_proj_en, "k": k_proj_en, "v": v_proj_en, "o": o_proj_en,
"gate": gate_proj_en, "down": down_proj_en, "up": up_proj_en,
}.items() if enabled]
return target_mods
def normalize_messages(data_point):
"""Convert a dataset row to OpenAI messages format for apply_chat_template()."""
if "messages" in data_point:
return data_point["messages"]
if "conversations" in data_point:
role_map = {"human": "user", "gpt": "assistant"}
return [
{"role": role_map.get(turn.get("from", ""), turn.get("from", "")), "content": turn["value"]}
for turn in data_point["conversations"]
]
raise RuntimeError(
f'Dataset row must contain "messages" or "conversations" key. '
f'Found: {list(data_point.keys())}'
)
def tokenize_conversation(data_point):
"""Tokenize using apply_chat_template() with assistant-only label masking."""
messages = normalize_messages(data_point)
full_ids = list(shared.tokenizer.apply_chat_template(messages, tokenize=True, return_dict=False))
# Build labels: -100 for everything, then unmask assistant turns.
# This assumes apply_chat_template(messages[:i]) is a token-for-token
# prefix of apply_chat_template(messages[:i+1]), which holds for all
# standard chat templates (Llama, ChatML, Mistral, etc.).
labels = [-100] * len(full_ids)
for i, msg in enumerate(messages):
if msg["role"] == "assistant":
# Tokens up to where this assistant turn starts
header_ids = shared.tokenizer.apply_chat_template(View on GitHub (pinned to ed888c71f2)
Solutions
- Convert the dataset to one of the accepted schemas: either [{'role': 'user'|'assistant'|'system', 'content': str}] under a 'messages' key, or [{'from': 'human'|'gpt', 'value': str}] under a 'conversations' key.
- If the source is Alpaca-style, map instruction/output columns to messages programmatically before training (e.g. df.apply to build the messages list).
- Check for typos or nested structures in the dataset column names by printing dataset[0].keys() and comparing against the two accepted keys.
- As a last resort, pre-transform the dataset file offline (jq/pandas) so every row literally has 'messages' or 'conversations' at the top level.
Example fix
# before (row only has Alpaca columns)
# {"instruction": "Hi", "output": "Hello!"} -> RuntimeError
# after: convert to messages format
import json
rows = [json.loads(l) for l in open('data.jsonl')]
with open('data_fixed.jsonl', 'w') as f:
for r in rows:
f.write(json.dumps({"messages": [
{"role": "user", "content": r["instruction"]},
{"role": "assistant", "content": r["output"]},
]}) + '\n') Defensive patterns
Strategy: validation
Validate before calling
REQUIRED_KEYS = {"messages", "conversations"}
def row_is_trainable(row: dict) -> bool:
return bool(REQUIRED_KEYS & set(row.keys()))
# before mapping the dataset:
bad = [i for i, row in enumerate(dataset) if not row_is_trainable(row)]
if bad:
raise ValueError(f'{len(bad)} rows lack messages/conversations; first bad index: {bad[0]}, '
f'keys: {list(dataset[bad[0]].keys())}') Type guard
from typing import TypedDict
class Message(TypedDict):
role: str
content: str
class MessagesRow(TypedDict):
messages: list[Message]
class Turn(TypedDict):
from_: str # 'human' | 'gpt'
value: str
class ConversationsRow(TypedDict):
conversations: list[Turn]
def is_chat_row(row: dict) -> bool:
if "messages" in row:
msgs = row["messages"]
return isinstance(msgs, list) and all(
isinstance(m, dict) and m.get("role") in {"user", "assistant", "system"} and isinstance(m.get("content"), str)
for m in msgs
)
if "conversations" in row:
turns = row["conversations"]
return isinstance(turns, list) and all(
isinstance(t, dict) and t.get("from") in {"human", "gpt", "system"} and isinstance(t.get("value"), str)
for t in turns
)
return False Prevention
- Standardize every training corpus to the 'messages' (OpenAI) schema before loading; it is the least ambiguous.
- Add a pre-training smoke test that asserts dataset[0] contains an accepted chat key.
- Keep dataset transformation code next to the dataset (a small convert script per source format) instead of ad-hoc edits.
When it happens
Trigger: Loading a Hugging Face dataset whose rows use a different schema (e.g. only 'text', 'prompt'/'response', 'instruction'/'input'/'output' Alpaca-style columns) and starting training with any apply_chat_template-based dataset path. Also triggered by datasets that nest the chat under another column name, or by a mapping step that was skipped.
Common situations: User points --dataset at an Alpaca-format JSON or a raw completion corpus while the trainer expects chat-format data; dataset was regenerated with renamed columns; a ShareGPT export uses 'from'/'value' but the top-level key is 'chat' or 'history' instead of 'conversations'.
AI-assisted analysis of oobabooga/textgen@ed888c71f2 (2026-08-15).
Data as JSON: /api/errors/ab73ec63fb3b7bc0.
Report an issue: GitHub.