Chapter 1 · The scenario
Example: A delivery-slot picker that stopped breaking
A Bengaluru grocery bot used to ask the model for "a friendly message with three slot options" and split the text into buttons. Titles like "Tomorrow morning (7–9 am, express)" ran past 20 characters and the send failed, leaving customers with no reply. The team switched to a schema: a body of up to 1,024 characters, one to three buttons, each id drawn from that day's real slot IDs and each title capped at 20 characters. Button-related send errors stopped, and because the IDs are real, a tap maps straight to the booking API.
Chapter 2 · What is structured output (JSON mode)?
Structured output — often loosely called JSON mode — is a way of making a large language model (LLM) answer in a machine-readable format — almost always JSON — that matches a schema you define, instead of free-form prose. The schema names the fields, their types, which ones are required, the allowed values (enums) and length limits. Your code can then parse the reply straight into a WhatsApp interactive message, the values for each message template variable, or the fields of a CRM record.
It comes in two strengths. JSON mode, the older and looser option, only guarantees that the output parses as JSON; the model can still omit fields, invent keys or use the wrong types. Schema-constrained output, often called structured outputs, restricts generation token by token so the result always conforms to the schema, within the subset of JSON Schema the provider supports. Either way the content can still be wrong: a well-formed object with a made-up delivery date is still made up.
Structured output shapes the model's answer; tool calling (function calling) asks your code to do something. Both use JSON Schemas, and some providers implement one with the other, but the intent differs: a structured reply is the end product ("here are three reply buttons"), while a tool call is a request mid-conversation ("run check_order_status for order 48213 and tell me the result"). Entity extraction is a common use of structured output — pulling an order ID, a date and a PIN code out of a message into typed fields.
Key takeaways
- Structured output constrains an LLM to return JSON that matches your schema instead of free prose.
- Plain JSON mode guarantees valid JSON; schema-constrained output also guarantees the fields and types you asked for.
- Put WhatsApp's limits in the schema: at most 3 reply buttons, each title up to 20 characters.
- Validate every response in code anyway — refusals, truncation and business rules are not caught by the schema.
- Structured output shapes the model's answer; tool calling asks your code to take an action.
Chapter 3 · How it unfolds
How does structured output (JSON mode) work?
Design the schema from the destination
Start from the payload you will send. For reply buttons: a
bodystring and abuttonsarray of at most 3 items, each with anidfrom an enum and atitlewithmaxLength20. Mark fields required and disallow extra properties.Request structured output
Pass the schema with the request using your provider's structured-output or JSON-mode option, and describe each field — in the system prompt or in the schema's
descriptionkeywords.Validate in code
Parse and validate the reply with a schema library even when the provider promises conformance. Then check the business rules a schema cannot express, such as whether a slot is still free.
Handle the edge cases
Treat refusals, truncated output and validation failures as normal paths: retry once with the validation error, then fall back to a plain text reply or a fixed menu.
Map to the WhatsApp payload
Convert the validated object into the Cloud API body — an
interactivemessage, or theparametersfor each template variable — and send it. Log the raw model output next to thewamidfor debugging.
Chapter 4 · In code
Structured output (JSON mode) in code
from typing import Literalfrom pydantic import BaseModel, Fieldclass Button(BaseModel):id: Literal["slot_am", "slot_pm", "slot_eve"] # only real slot IDstitle: str = Field(max_length=20) # WhatsApp reply-button limitclass SlotReply(BaseModel):body: str = Field(max_length=1024)buttons: list[Button] = Field(min_length=1, max_length=3)raw = llm.generate(system="Offer the customer today's delivery slots. Be brief and friendly.",user=customer_text,response_schema=SlotReply.model_json_schema(), # option name varies by provider)reply = SlotReply.model_validate_json(raw) # raises if anything is off
One set of types drives both the schema sent to the model and the validation of its reply.
Chapter 5 · Where it goes wrong
Common mistakes with structured output (JSON mode)
Assuming every schema keyword is enforced
Providers support different subsets of JSON Schema; length limits, patterns or array bounds may be ignored in strict mode. Enforce WhatsApp's limits again in your own validation.
Parsing JSON-mode output without validation
JSON-mode output can parse cleanly and still miss a required field or use a string where you expected an array. Validate against the schema before you build a payload.
Letting the model invent IDs
If button or list-row IDs are free strings, the model will make some up and taps will route nowhere. Constrain IDs to an enum built from the real options for this conversation.
No fallback path
Refusals, timeouts and truncated output happen. Keep a fixed menu or plain-text reply ready so the customer still gets an answer when validation fails.
Newlines in template variables
Template body parameters cannot contain newlines or tabs, and long values push a template past its character limits. Strip, trim and length-check generated values before you send them.
Chapter 6 · The facts
Structured output (JSON mode): key facts and limits
| JSON mode guarantees | Syntactically valid JSON only |
|---|---|
| Schema-constrained output guarantees | JSON matching your schema (supported keywords only) |
| Reply buttons | Max 3; title ≤ 20 characters; ID ≤ 256 characters |
| List messages | Max 10 rows; row title ≤ 24, description ≤ 72 characters |
| Text message body | Max 4,096 characters |
| Template body variables | No newlines, tabs or more than 4 consecutive spaces |
Chapter 7 · Why it matters
Why does structured output (JSON mode) matter?
WhatsApp is strict about message shapes. A reply-button message allows three buttons with titles of up to 20 characters; a list message allows ten rows with 24-character titles; a template body variable cannot contain a newline. Free-text LLM output breaks those rules unpredictably, and the send fails with an API error, usually while a customer is waiting. Structured output turns the model into a component that returns exactly the object your send code expects, so failures become rare, typed and catchable.
Chapter 8 · What good teams do
Best practices for structured output (JSON mode)
- Generate the schema from the same types your send code uses, so the two never drift apart.
- Keep schemas small and flat — deep nesting raises error rates and latency.
- Add a
descriptionto every field; models follow field descriptions closely. - Log validation failures with the raw output — they are the best guide to fixing the prompt.
Chapter 9 · Structured Output (JSON Mode) FAQs