Zenaique

OpenAI's hosted FT for tool calling models: what does a JSONL row need beyond text SFT?

Short answer·Medium·4.0 · 0·~3 min·Asked atNykaaOpenAISap·Relevant atMicrosoft
Attempt it

A team is moving from text only SFT to OpenAI's hosted fine-tuning API for a tool calling model (e.g. gpt-5.5 class). They have working multi-turn JSONL but the API rejects half their rows once they add tool calls. Specify the extra fields a tool aware row needs, both on the assistant turn that issues the call and on the tool response that follows, and explain which field links them. What breaks if the linkage is wrong or missing?

Free · 2 AI evals / day
TL;DR

The assistant turn carries a tool_calls array with unique id, type, name, and JSON-string arguments. The next turn uses role tool with a matching tool_call_id, the linkage that survives parallel calls.

Memory aid
Sign in to see the mnemonic that makes this stick.
Easy to grasp

Picture a busy kitchen where the head chef shouts orders to the prep station. Every order gets its own ticket number stuck to it: order forty-one, two onions diced; order forty-two, one carrot grated. When the prep returns, they staple the chopped food to a matching ticket so the head chef knows what came back from where. If you forget the ticket, or two tickets share a number, the chef ends up putting onions where carrots should go. The hosted fine-tuning API works the same way. Each tool call carries its own ticket, the response carries the same ticket back, and the system uses tickets to keep everything paired even when several orders fly at once.

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.

Moving from text-only SFT to OpenAI's hosted fine-tuning for a tool-calling model is one of those migrations that looks small on paper and breaks half your rows in practice. The reason is that the chat-completions schema changes shape in three places once tool calls appear, and the schema is strict. Rows that worked fine for plain conversational SFT get rejected the moment they include a function call, because two new fields become mandatory, one existing field changes type, and a brand-new role appears in the conversation.

The API is strict for good reasons. The shape it enforces at training time is the same shape the model emits and consumes at inference time. Any mismatch between training and inference shape would create a distribution gap the model would feel, so the API refuses to let you train on rows that diverge from the runtime contract.

This deep dive walks every required change in the row shape, explains what each field does, and covers the failure modes you will hit if you skip any of them. There are two distinct failure classes worth distinguishing: hard failures that the API catches at upload time and you fix in minutes, and soft failures that train cleanly but corrupt the model's grounding behaviour at inference, where you discover them only after production starts mismatching tool results.

The assistant turn shape changes

In text-only SFT the assistant turn carries a single content field with the free-text reply. In tool-calling SFT the assistant turn that issues a call abandons free-text content entirely and instead carries a tool_calls array.

Each entry in tool_calls is an object with three required keys. The id is a unique string for this call, conventionally call_ followed by a UUID. The type is the literal string function (the API reserves the type field for future extensibility, but function is the only supported value today). The function object holds two fields: name, the function being called, and arguments, the serialised parameters.

The single detail that catches most teams on the first migration is that arguments is a JSON string, not a nested JSON object. The API is strict here. A row that passes arguments as a JSON object, even if the object is well-formed, will be rejected at upload time. The string requirement exists because the OpenAI inference API emits arguments as a string for the client SDK to parse, and the training and inference shapes have to match.

The call turn should carry an empty (or null) content alongside the tool_calls array. The convention is that a call turn does one thing, it commits to a tool invocation. The natural-language reply belongs on a separate assistant turn that comes after the tool result. Some validators reject rows that mix tool_calls with non-empty content; all OpenAI internal tooling expects the split.

The new tool role and the tool_call_id linkage
Hard failures the API catches at upload
Soft failures that corrupt grounding at inference
Scaling to parallel calls and fleets of tools
Sign in to unlock the full deep dive.

Situations where this technique stops working.

Sign in to see when this approach fails.

2–4 min · Everything important, quickly.

Sign in to see the quick scan of the deep dive.

Real products, models, and research that use this idea.

  • OpenAI's hosted fine-tuning for gpt-5.5 family enforces this shape exactly and rejects rows at upload when arguments is nested or tool_call_id is missing.
  • OpenAI's Python SDK ChatCompletion examples emit ids as call_<uuid> and pass arguments as a JSON string, the same convention training data must follow.
Sign in to see more production examples.

What an interviewer would ask next. Try answering before peeking at the approach.

QWhy does the OpenAI spec keep arguments as a JSON string rather than a nested object?
A

Talk about training and inference symmetry. At inference the model emits arguments as a string for the SDK to parse. Encoding the same shape during training removes a distribution gap between fine-tune data and runtime output, which would otherwise show up as malformed argument structures.

2 more follow-ups an interviewer would ask next. Sign in to reveal them.

Red flags & common mistakes

The phrases that signal junior thinking. Click to expand.

Most common mistake

Passing arguments as a nested JSON object instead of a JSON string. The OpenAI spec is strict here, arguments must be a string, and rows that nest the object are rejected at upload time.

Sign in to see all red flags and common mistakes.

60 second bullets to scan on the way to the call.

  • Three required keys on each tool_calls entry and why each is mandatory

  • Why arguments is a JSON string and not a nested object

Sign in to unlock the revision sheet.

Primary sources. Browse if you want the original framing.

Similar questions

Same topic, related formats. Practice these next.

4 curated
Next question
What is RLHF, and why is it used after pretraining?
MCQ·Easy