The three options sit on a spectrum from polite request to hard constraint. Knowing which rung you are on tells you what your code has to catch.
JSON mode is the weakest. The provider guarantees the completion parses as JSON, and nothing more. When Vellum walked through a support-escalation prompt that asked for a JSON object with a should_escalate boolean and a reasoning string, the model returned the object wrapped in markdown fences, which broke the parse (Vellum). JSON mode removes the fences. It does not remove field drift, wrong types or invented enum values.
A response schema, sometimes called structured outputs, changes the decoding loop. Before each token is sampled, a constraint engine compiled from your JSON Schema masks every token that would take the output off a valid path. Missing braces, absent required fields, enum violations and markdown wrapping become structurally impossible rather than unlikely (Brenndoerfer). OpenAI's documentation states the guarantee plainly: the model always generates responses that adhere to the supplied JSON Schema, so you do not have to worry about omitted required keys or hallucinated enum values (OpenAI).
A tool call (function calling) is structured output with a decision attached. You define tools as JSON schemas, send them with the prompt, and the model returns a tool name plus arguments that your application executes (Prefect). The model never runs the function; it produces a structured request, and your code does the dispatch. TrueFoundry frames this well: tool calling is structured output with consequence (TrueFoundry).
The practical hierarchy is simple. JSON mode is a syntax guarantee. A response schema is a shape guarantee. A tool call is a shape guarantee plus intent, because the model also tells you which of several contracts it chose.



