Fill in the SFT JSONL: the three required fields of a typical chat example.
Top-level key is `messages` (an array). Each message has `role` (system/user/assistant) and `content` (the text). One JSON object per JSONL line.
Imagine a script for a short scene. Each line of the script names the speaker and then their dialogue. That is exactly what an SFT chat example looks like on disk, a list of small speaker and line entries. The whole list is wrapped under one heading called messages because a conversation is a sequence of messages. Each message inside carries two fields, who is talking (role) and what they are saying (content). The trainer reads through the script and uses the speaker labels to figure out which lines it should grade the model on, the assistant lines, and which lines are just context.
Concept explanation~2 min read
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
Concept explanation~2 min read
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
The chat-format JSONL is the single most important data convention in modern fine-tuning. Three field names (messages, role, content) carry the whole structure, and that minimalism is the point. The shape ported from the OpenAI chat-completions API to community training tooling, then to other vendors' fine-tuning APIs, until it became the lingua franca of the entire ecosystem. Knowing it cold is foundational.
This deep dive walks through the structure of a single chat-format JSONL line, explains why the design choices matter, traces what the trainer actually does with this shape during SFT, and surveys the provider variations that bite when you port data between platforms.
The headline shape is one JSON object per line with a messages array, where each message is a {role, content} dict. The role is one of system, user, assistant, plus optional tool for tool-call templates. The content is text by default, or structured blocks for multimodal data. No other fields are needed; the role tags carry the structure that the trainer uses to apply prompt loss masking and chat template rendering automatically.
The historical context is worth pulling apart. Before the chat-format converged in 2023, datasets used a zoo of shapes. Alpaca had flat instruction / input / output fields. ShareGPT used from / value with role values human and gpt. The OpenAI completions API used prompt / completion. Each shape had its own loaders, its own quirks, its own assumptions about multi-turn and tool calls. The chat-format collapsed all of this into one schema that handles single-turn, multi-turn, system messages, and tool calls uniformly. The convergence is one of the quiet success stories of the modern LLM stack.
The three fields and what each carries
messages
The top-level key of each JSONL line. Its value is an array of message objects representing the conversation. The plural form reflects the fact that a conversation is a sequence of turns; even a single-turn example wraps its messages in this array.
The array is ordered. The first message is typically a system message (though this is not mandatory in all templates); subsequent messages alternate between user and assistant. A multi-turn conversation can have any number of back and forth pairs, plus tool turns where applicable.
role
A field on each message identifying the speaker. Three canonical values:
system: behavioural setup, persona, response style, safety rules.user: a turn from the human.assistant: a turn from the model.
A fourth optional value:
tool(orfunctionin some templates): output from an external tool the assistant invoked.
The role field is what drives prompt loss masking during training: only assistant tokens contribute to the loss.
content
The actual text of the message. Plain string by default. Some modern templates support structured content (a list of {type, text} or {type, image_url} blocks) for multimodal data; in those cases content is an array rather than a string.
The content is whatever the model needs to see or produce: an instruction, a question, a code snippet, a JSON tool-call payload, a math expression. The trainer treats it as opaque text and lets the model handle the semantics.
Situations where this technique stops working.
2–4 min · Everything important, quickly.
| Field | Position | Purpose |
|---|---|---|
| messages | Top-level key | Wraps the array of conversation turns |
| role | Inside each message | Identifies the speaker (system/user/assistant/tool) |
| content | Inside each message | The actual text of that turn |
Real products, models, and research that use this idea.
- OpenAI fine-tuning endpoint accepts exactly the `{messages: [{role, content}, ...]}` shape, one example per line in a JSONL file.
- Hugging Face TRL SFTTrainer auto-detects this shape and applies the model's chat template for prompt masking.
What an interviewer would ask next. Try answering before peeking at the approach.
QWhy does the trainer not need a separate prompt and completion split when consuming this format?
The role field tells the trainer which tokens belong to the assistant (which contributes to the loss) and which belong to user or system (which is masked). The role tags carry the same information that a prompt and completion split would, but in a way that generalises to multi-turn and tool calls.
Red flags & common mistakes
The phrases that signal junior thinking. Click to expand.
Red flags & common mistakes
The phrases that signal junior thinking. Click to expand.
Trying to add a separate `prompt` and `completion` split next to the messages array. The trainer derives which spans to grade from the role tags; no extra split is needed.
60 second bullets to scan on the way to the call.
The three field names in the standard chat-format JSONL
Why messages is plural and wraps an array
Primary sources. Browse if you want the original framing.
Same topic, related formats. Practice these next.