Blog • AI / JSON Schema
Why Does AI Need JSON Schema? Structured Output, Function Calling, and JSON Schema Explained
When you wire ChatGPT, Gemini, or DeepSeek, the docs mentionFunction Calling, Structured Output, andJSON Schema at once. They look like three features. They are three uses of one contract language.
JSON Schema describes what a JSON document must look like. The current mainstream is2020-12. Function Calling uses it for tool arguments; Structured Output uses it for the final answer. AI needs it not because JSON is trendy, but because natural language cannot be consumed reliably by downstream code.
This article covers:
- What each of the three concepts constrains
- Why plain natural-language output is not enough
- How Function Calling uses schema for tool arguments
- How Structured Output uses schema for the final answer
- How to reuse one schema in both places, then choose and validate
Keep this in mind: The model emitting JSON and emitting schema-conformant JSON are two different things. JSON Schema defines the latter. Function Calling covers which tool to call and whether arguments are valid; Structured Output covers what the final answer looks like. Below we walk through why → what → tool args → final answer → how to choose → how to validate.
Three concepts in one table
Put the three terms in one table first. They share JSON Schema as a language, but they constrain different objects.
| Concept | What it constrains | Typical field / standard | What it solves |
|---|---|---|---|
| JSON Schema | JSON fields, types, required keys, enums | JSON Schema 2020-12 | The contract itself: what the data must look like |
| Function Calling | tool_calls.arguments |
tools[].parameters |
Which tool to call and whether arguments are legal |
| Structured Output | The final answer JSON | response_format.json_schema / responseSchema |
Whether the result can be stored or piped downstream |
People mix the three words because docs bundle a feature name with the contract language. Remember the layers: JSON Schema is the language; Function Calling and Structured Output are two uses.
Why AI cannot live on natural language alone
If you only write “please return JSON” in the prompt, these failure modes show up.
| Failure mode | Typical symptom | Root cause |
|---|---|---|
| Syntax errors | Missing quotes, trailing commas, single quotes | The model writes like prose; tokens are unconstrained |
| Field drift | The same prompt yields different field names twice | No schema contract; the model improvises |
| Type errors | Numbers become strings; arrays become objects | The prompt is vague; there is no hard type constraint |
| No regression | The same case cannot be fixture-compared twice | Natural language has no stable field set |
Demos can hide this with retries or a second parse. In production agents, pipelines, and automation, it crashes downstream. JSON Schema is the format contract: declare fields, types, and required keys before generation.
JSON Schema is a contract, not a comment
JSON Schema is the standard for describing JSON structure. In AI integrations it is not a human comment—it is a machine-executable contract:
-
1
Declare field names and types
Each key in properties is an output field; type sets string / integer / array and so on.
-
2
Require keys and limit enums
required lists must-have fields; enum blocks creative values.
-
3
Block extra fields
additionalProperties: false forbids undeclared keys. Strict Schema usually requires this.
{
"type": "object",
"properties": {
"ticket_id": {
"type": "string",
"description": "Support ticket ID, e.g. TCK-1042"
},
"priority": {
"type": "string",
"enum": ["low", "medium", "high"]
},
"summary": {
"type": "string",
"description": "One-sentence problem summary"
}
},
"required": ["ticket_id", "priority", "summary"],
"additionalProperties": false
}
This schema does not care whether it lives in tools or response_format. It only answers: is this JSON legal? The next two sections mount it on Function Calling and Structured Output.
Function Calling: schema for tool arguments
Function Calling lets the model return a structured tool invocation instead of guessing. Each tool’stools[].parameters is JSON Schema:
{
"model": "gpt-4o",
"messages": [
{ "role": "user", "content": "Create a high-priority ticket for checkout timeout." }
],
"tools": [
{
"type": "function",
"function": {
"name": "create_ticket",
"description": "Create a support ticket from the user request.",
"parameters": {
"type": "object",
"properties": {
"ticket_id": { "type": "string" },
"priority": { "type": "string", "enum": ["low", "medium", "high"] },
"summary": { "type": "string" }
},
"required": ["ticket_id", "priority", "summary"],
"additionalProperties": false
}
}
}
]
}
The model returnstool_calls[0].function.arguments as a JSON string. It tries to match parameters, but it is not guaranteed valid every time—missing fields, wrong types, and broken syntax all happen. Parsing and validation are your business layer’s job.
For the Agent loop, MCP inputSchema, and fuller tool writing, seeWhy AI Agents Need JSON Schema.
Structured Output: schema for the final answer
Structured Output constrains the final answer, not tool arguments. OpenAI usesresponse_format; Gemini usesresponseSchema. Underneath, both are JSON Schema:
{
"model": "gpt-4o-2024-08-06",
"messages": [
{ "role": "user", "content": "Extract a support ticket from this email." }
],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "support_ticket",
"strict": true,
"schema": {
"type": "object",
"properties": {
"ticket_id": { "type": "string" },
"priority": { "type": "string", "enum": ["low", "medium", "high"] },
"summary": { "type": "string" }
},
"required": ["ticket_id", "priority", "summary"],
"additionalProperties": false
}
}
}
}
Withstrict: true, the model is guided by the schema at inference time—not generate-then-hope, but generate-only-what-conforms.
For ChatGPT vs Gemini setup and what JSON Mode vs Strict Schema actually guarantee, seethe 2026 AI Structured Output guide.
One schema, two mounts
This is the point of the article: do not write two drifting field sets for tool arguments and the final answer. A ticket is still three fields. Only the mount point changes.
TICKET_SCHEMA = {
"type": "object",
"properties": {
"ticket_id": {"type": "string"},
"priority": {"type": "string", "enum": ["low", "medium", "high"]},
"summary": {"type": "string"},
},
"required": ["ticket_id", "priority", "summary"],
"additionalProperties": False,
}
# 1) Function Calling: constrain tool arguments
tools = [{
"type": "function",
"function": {
"name": "create_ticket",
"description": "Create a support ticket.",
"parameters": TICKET_SCHEMA,
},
}]
# 2) Structured Output: constrain the final answer
response_format = {
"type": "json_schema",
"json_schema": {
"name": "support_ticket",
"strict": True,
"schema": TICKET_SCHEMA,
},
}
One contract, three landings:
-
1
Tool call
The model picks create_ticket; arguments must match the schema.
-
2
Final answer
When no tool is needed, the final JSON is still the same fields and can be stored as-is.
-
3
Business validation
Whether the payload comes from tool_calls or message.content, run the same parse + validate.
How to pick among four paths
In 2026, structured LLM data usually takes one of four paths:
| Path | API config | What it guarantees | When to use it |
|---|---|---|---|
| Prompt only | No special config | No format guarantee | Prototypes, human reading |
| JSON Mode | response_format: json_object |
A valid JSON object | Simple extraction, loose fields |
| Function Calling | tools[].parameters |
arguments try to match parameters | Calling tools / writing a DB / sending HTTP |
| Structured Output | json_schema + strict: true |
Strict conformance to the schema | Final results that must be stored or piped |
How the four Agent layers fit together (LLM, Function Calling, MCP, JSON Schema):2026 AI Agent stack.
Production writing and validation
Success rates for Structured Output and Function Calling track schema quality. The business layer still validates.
-
1
Write a description on every property
The model reads description to decide values. “Support ticket ID, e.g. TCK-1042” beats a bare field name.
-
2
Use enum instead of hinting in prose
“low/medium/high” in a sentence is weaker than
enum. -
3
Set additionalProperties: false
OpenAI Strict usually requires this. Put
additionalProperties: falseon the root object so the model cannot inject undeclared keys. -
4
Do not nest too deep
More than three levels or heavy oneOf raises error rates. Split complex shapes across multiple calls.
import json
from jsonschema import validate, ValidationError
def parse_model_json(raw: str, schema: dict) -> dict:
try:
data = json.loads(raw)
except json.JSONDecodeError as e:
raise ValueError(f"Invalid JSON: {e}") from e
try:
validate(instance=data, schema=schema)
except ValidationError as e:
raise ValueError(f"Schema mismatch: {e.message}") from e
return data
Whether the payload comes from ChatGPT, Gemini, or DeepSeek, run the same parse → validate → business logic. During development, paste schema and output into JSONNote—faster than print debugging.
Debug model JSON in JSONNote
The slow part is rarely writing the API. It is seeing where the model JSON breaks the contract. JSONNote runs locally in the browser:
-
1
Validate schema against output
Paste the API JSON and your schema into theJSON Schema page to see which fields fail the contract.
-
2
Format the API response
UseJSON Formatter to catch syntax and indentation issues.
-
3
Compare two calls
UseJSON Diff to diff two structured outputs or tool arguments for regression checks.
FAQ
What is the difference between Function Calling and Structured Output?
Function Calling constrains tool_calls.arguments (which tool and which args). Structured Output constrains the final JSON answer. Both use JSON Schema at different layers. Agents often combine them: tools for external actions, Structured Output for the final structured result.
Is JSON Mode enough?
No. JSON Mode (response_format.type=json_object) only guarantees a valid JSON object, not fields or types. Use Structured Output (json_schema + strict:true or Gemini responseSchema) for complex production shapes.
Do I still need to validate with Strict Schema enabled?
Yes. API-level constraints do not replace business-layer validate. Empty content, truncated payloads, and edge cases still happen—try/except parse + JSON Schema validate is the production default.
Which JSON Schema version should I use?
In 2026, major LLM APIs and MCP tool contracts standardize on JSON Schema 2020-12. Core keywords include type, properties, required, enum, items, and additionalProperties.
Can one schema serve both tools and the final output?
Yes. The same object definition can go into tools[].parameters and response_format.json_schema.schema. Keep one source of truth so the two contracts cannot drift.
Summary
AI needs JSON Schema because it turns model output from “looks like data” into a machine-checkable contract.
JSON Schema is the language; Function Calling uses it for tool arguments; Structured Output uses it for the final answer.
Write one schema, mount it twice, then validate in the business layer. Format, validate, and diff locally in JSONNote—that is the shortest path from demo to production.