jsonlkit.com
JSONL (JSON Lines) utilities, in the browser
Say hi →

OpenAI Fine-Tuning JSONL Format

Format reference · updated 28 July 2026 · overview · spec · format comparison · examples · best practices · troubleshooting · FAQ · glossary

A reference for the JSONL file OpenAI's fine-tuning API expects — the record shape, every field that is allowed, the rules that get a file rejected, and how it differs from the batch format and from other providers' formats.

The record shape

One conversation per line. Each line is an object with a messages array, and nothing wraps the lines — no enclosing array, no commas between records:

{"messages":[{"role":"system","content":"You are a terse assistant."},{"role":"user","content":"What is 2+2?"},{"role":"assistant","content":"4"}]}
{"messages":[{"role":"system","content":"You are a terse assistant."},{"role":"user","content":"Capital of France?"},{"role":"assistant","content":"Paris"}]}

That is the whole format for the common case. The pieces that matter:

Multi-turn conversations and the weight field

A conversation can have as many turns as fit the context window. By default every assistant message contributes to the loss. The weight field controls that per message:

{"messages":[
  {"role":"user","content":"Hi"},
  {"role":"assistant","content":"Hello! How can I help?","weight":0},
  {"role":"user","content":"Summarise this contract clause: ..."},
  {"role":"assistant","content":"The clause limits liability to ...","weight":1}
]}

weight: 0 means "this turn is context, do not train on it"; weight: 1 is the default. Use it when earlier assistant turns exist only to set the scene — training on a generic greeting teaches the model to produce generic greetings.

Tool calling

To teach a model when to call a function, include the tool definitions and an assistant turn containing tool_calls rather than content:

{"messages":[
   {"role":"user","content":"Weather in Kyiv?"},
   {"role":"assistant","tool_calls":[{"id":"call_1","type":"function","function":{"name":"get_weather","arguments":"{\"city\":\"Kyiv\"}"}}]},
   {"role":"tool","tool_call_id":"call_1","content":"{\"temp_c\":21}"},
   {"role":"assistant","content":"It is 21 °C in Kyiv."}
 ],
 "tools":[{"type":"function","function":{"name":"get_weather","parameters":{"type":"object","properties":{"city":{"type":"string"}}}}}]}

Two details that trip people up: arguments is a JSON string, not an object — so it is escaped inside the record — and every tool message needs a tool_call_id matching the call it answers. Include the tools array in the record so the model learns the signature alongside the usage.

Rules that get a file rejected

Our OpenAI validator checks all of these locally, before you upload anything.

Not to be confused with the Batch API format

Both are JSONL and both go to OpenAI, and they are entirely different. Fine-tuning lines are training examples. Batch lines are API requests:

// Fine-tuning — a training example
{"messages":[{"role":"user","content":"hi"},{"role":"assistant","content":"hello"}]}

// Batch API — a request to run
{"custom_id":"req-1","method":"POST","url":"/v1/chat/completions","body":{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hi"}]}}

Uploading one where the other is expected produces a confusing error. The tell is the first key: {"messages" for training, {"custom_id" for batch. Validate them with the fine-tune validator and the batch validator respectively.

Differences from other providers

ProviderSystem promptAssistant roleContent shape
OpenAIFirst message in messagesassistantString
AnthropicTop-level system keyassistantString or content blocks
GeminisystemInstructionmodelparts array
ShareGPTfrom: "system" entrygptvalue string
AlpacaOptional systemn/a — outputSingle turn only

The chat format converter moves between all of these, including the system-prompt relocation that is the most common manual mistake. Validate with the provider-specific validator afterwards — the shape being right does not mean the provider's extra rules are satisfied.

Frequently asked questions

How many examples do I need?

The API has a low minimum (historically 10) and the practical floor for a useful result is in the hundreds. Quality matters far more than count: a few hundred carefully-reviewed examples routinely beat thousands of scraped ones.

Should the system prompt be in every example?

If you will send one at inference, yes, and keep it identical. Inconsistency teaches the model the prompt is optional. The system-prompt deduper reports how many distinct system prompts a file contains.

Can I mix single-turn and multi-turn examples?

Yes. Both are valid and mixing them is common. Just make sure every example ends on an assistant turn with real content.

Does the file need a validation split?

It is optional — you can upload a separate validation file. If you do, check for overlap with the leakage detector first, or the reported validation loss will be optimistic.

Is the format stable across models?

The messages shape has been stable for a long time. Specifics — context limits, whether developer is accepted in place of system, tool-calling details — vary by model, so check the current docs for the model you are targeting.

Tools mentioned on this page

— S., [email protected]