Why JSONL for fine-tuning
When you fine-tune a language model, the provider expects your training examples in JSONL (JSON Lines): one complete JSON object per line, with no enclosing array. It is the standard because each line is an independent record that can be streamed, validated, and counted without loading the whole file.
{"messages":[{"role":"system","content":"You are a support agent."},{"role":"user","content":"Where is my order?"},{"role":"assistant","content":"Let me check that for you."}]}
{"messages":[{"role":"user","content":"Reset my password"},{"role":"assistant","content":"I've sent a reset link to your email."}]}Each line is a full training example; the newline is the only delimiter.
The chat message schema
Modern fine-tuning uses the same message format as the chat API. Each example is an object with a messages array, and the assistant message is the target — what you want the model to learn to produce. For structured-output fine-tuning, that target is itself JSON (escaped, since it lives inside a JSON string).
Building the file
Generate it programmatically and write one object per line:
import json
examples = [
{"messages": [
{"role": "user", "content": "Translate 'hello' to French"},
{"role": "assistant", "content": "bonjour"},
]},
# ...more...
]
with open("train.jsonl", "w", encoding="utf-8") as f:
for ex in examples:
f.write(json.dumps(ex, ensure_ascii=False) + "\n")Use ensure_ascii=False so non-English text stays readable, and always end each line with a newline.
Validate before you upload
A single malformed line can fail an entire fine-tuning job. Validate that every line parses:
import json
with open("train.jsonl", encoding="utf-8") as f:
for n, line in enumerate(f, 1):
line = line.strip()
if not line:
continue
try:
obj = json.loads(line)
assert "messages" in obj
except Exception as e:
print(f"Line {n} invalid: {e}")Common JSONL mistakes
| Mistake | Symptom | Fix |
|---|---|---|
Wrapping lines in a [ ... ] array | "expected object, got array" | Remove brackets; one object per line |
| Trailing comma after each line | Parse error | JSONL has no commas between records |
| Pretty-printed (multi-line) objects | Each line fails to parse | Each record must be on one line |
| Unescaped quotes in content | Invalid JSON | Let the JSON library do the escaping |
| Blank lines / BOM | Skipped or failed lines | Strip blanks; save as UTF-8 without BOM |
Quality beats quantity
A few hundred clean, on-task examples often outperform thousands of noisy ones. Keep formatting consistent — if the assistant target is JSON, make every example's target valid JSON with the same schema — and hold out a separate validation set to measure whether fine-tuning actually helped.