Level 2 · Prompting & Interaction
10 min

Structured Output: JSON, Schemas & Function Calling

When the model needs to talk to your code, plain text isn't enough.

The moment you embed an LLM into a real system, you need its output in a machine-readable format. Parsing free-form English is fragile. There are four production-grade options, in order of robustness.

1. Plain JSON request

The crude version: ask for JSON in the prompt.

Respond with valid JSON only, no surrounding text.
Format: { "intent": "...", "confidence": 0.0 }

This works ~85% of the time. It breaks when the model adds explanatory text, uses Markdown code fences, or hallucinates extra fields. Not production-grade.

2. JSON mode

Most modern APIs (OpenAI, Anthropic, Gemini, OpenRouter, Groq) support a flag that forces the output to be syntactically valid JSON:

response_format: { "type": "json_object" }

The model can no longer emit syntactically broken output. But it can still emit JSON with the wrong schema — missing required keys, extra unexpected keys, wrong types.

3. JSON Schema / structured outputs

The current best practice. You provide a JSON Schema; the API guarantees the output conforms:

{
  "response_format": {
    "type": "json_schema",
    "json_schema": {
      "name": "user_intent",
      "schema": {
        "type": "object",
        "properties": {
          "intent": { "type": "string", "enum": ["buy", "sell", "info"] },
          "confidence": { "type": "number", "minimum": 0, "maximum": 1 }
        },
        "required": ["intent", "confidence"],
        "additionalProperties": false
      }
    }
  }
}

The model is constrained at decode time — invalid tokens are masked out. This is 100% reliable for syntax and schema. Available in OpenAI Structured Outputs, Anthropic tool use, Gemini schemas, and via libraries like Outlines and Instructor.

4. Function calling / tool use

The same machinery, but framed as "the model can call your functions":

{
  "tools": [{
    "type": "function",
    "function": {
      "name": "lookup_order",
      "description": "Look up an order by its ID",
      "parameters": {
        "type": "object",
        "properties": { "order_id": { "type": "string" } },
        "required": ["order_id"]
      }
    }
  }]
}

The model returns a JSON object naming the function and its arguments. Your code executes the function and feeds the result back. This is the foundation of agents (Level 3).

When schemas don't fit

For free-form output (e.g., generating prose), you don't want JSON. But you can still get reliability by asking for delimited sections:

<title>...</title>
<summary>...</summary>
<body>...</body>

Parse with regex or a simple state machine. Less elegant than JSON Schema, but works everywhere.

The takeaway

If your downstream code needs to read the output, always use a schema. Plain "respond with JSON" is a footgun in production. Schema-constrained decoding has approximately zero cost compared to the alternative — invalid output and broken pipelines.

Knowledge Check

Score 70% or higher to mark this chapter complete.

Q1.What does 'JSON mode' guarantee?

Q2.Why are structured outputs / JSON Schema more reliable than 'please respond in JSON'?

Q3.Function calling / tool use is essentially what?

0 / 3 answered

LLMAtlas — The Open Ecosystem Workspace for LLMs