Technical Enough
Next: Give it the right context

Get output you can build on

Chapter 5 of 7 in The Model Layer · 7 min

The fridge feature had worked on every photo we staged. Then we watched the first parent outside the team point a phone at a real fridge, and the screen that should have drawn a shopping list drew a paragraph instead. The request went perfectly: the photo uploaded, and the call returned in seconds with a 200, the success code. The model had replied, "Sure! It looks like you have milk, a dozen eggs, and some spinach in there," which reads well to a person and broke our code, because that code was written to receive a list of items and render each one as a row. Handed a friendly sentence, it had no rows to draw. Nothing was wrong with the model or the photo. We had asked a person's question and tried to use the answer as data.

Code reads structure, not sentences

Our list screen is ordinary frontend code: it parses the response as JSON and renders each entry in items as a row. Here is the reply it was written for, next to the one it got.

What the code expects:

{ "items": ["milk", "eggs", "spinach"] }

What came back:

Sure! It looks like you have milk, a dozen eggs, and some spinach in there.

Both carry the same three groceries, but only one is data. Parsing the first returns an object whose items field holds three strings, and the screen draws three rows. Parsing the second throws an error at the first character, because "S" cannot begin valid JSON, and the screen draws nothing. The tempting workaround, scanning the sentence for the words you expect, dies on the next run, because the model generates a fresh reply every time, and the next one may open with "You've got." Your build already consumes JSON from every other API it calls; a weather endpoint never replies "Sure! Looks like rain."

Free text is for people. When code consumes a model's reply, ask for data in a fixed structure, the same fields and types on every call.

Write the structure down: the schema

You write the structure down as a schema, the list of fields and types your code expects, in the notation your tools and providers use. The fridge schema is one field:

{
  "type": "object",
  "properties": {
    "items": { "type": "array", "items": { "type": "string" } }
  },
  "required": ["items"]
}

Read it as a sentence: the reply is an object, it must contain items, and items is a list of strings. Most features need more than strings; an expense tracker reading receipt photos might require:

{
  "type": "object",
  "properties": {
    "merchant": { "type": "string" },
    "total":    { "type": "number" },
    "currency": { "type": "string", "enum": ["USD", "EUR", "GBP"] },
    "date":     { "type": "string", "description": "YYYY-MM-DD" }
  },
  "required": ["merchant", "total", "currency", "date"]
}

Now total is a number code can add without stripping "$12.50" first, currency must be one of three allowed values (the enum line declares that), and a missing required field makes the reply invalid by definition. Writing a schema is product work: the fields are whatever your feature's code needs, and you listed those the day you sketched the screen.

"Reply with JSON only" is a request, not a guarantee

The obvious objection is that Prompt v1 already has a rules-and-format section, so why not add "Reply with JSON only, no other text" and move on? The line helps, and here is a reply that came back with it in place:

Here is your shopping list in JSON format:

{"items": ["milk", "eggs", "spinach"]}

Let me know if you need anything else!

The JSON is in there, wrapped in prose, and the parse fails on "Here" exactly as it failed on "Sure." Current models follow format instructions well, but a rule followed on 99 calls in 100 still breaks the hundredth, and at a thousand calls that is ten blank screens. Nobody on our team reads each list before a user sees it, so the first person to meet each failure is a parent at their fridge.

A model's output is probabilistic, so output that code consumes needs a check in code: a structure the reply is forced to match, then validation the values must pass before anything acts on them.

Ask for it by name: structured outputs

The forcing half of that check has a name to use in a meeting. All three major providers ship a request option called structured outputs (you will also hear "JSON schema mode") that takes your schema with the call and constrains generation so the reply always parses, generally available on OpenAI, Anthropic, and Google as of mid-2026. The guarantee is mechanical: the model is restricted to output that keeps the document valid against your schema, so a reply starting with "Sure!" cannot be produced. It is one setting on the call, not an architecture change.

Free text versus structured outputThe model can return the same answer two ways. As free text, it reads well to a person but your code has to guess where each item is, which is brittle. As JSON matched to a schema, your code reads the list of items directly, which is reliable. Caption: free text is for people, structured output is for your code.TWO WAYS TO GET THE ANSWER BACKTHEMODELFREE TEXT“Sure! You’ve got milk, adozen eggs, and some spinach.”YOUR CODEhas to guess whereeach item isJSON, TO A SCHEMA{ "items": ["milk", "eggs", "spinach"] }YOUR CODEreads the listdirectlyFree text is for people; structured output is for your code.

Once you have the name, the conversation is short. If engineers build your product, the request is one sentence, "turn on structured outputs with this schema," and you hand over the schema. If you build it yourself, ask your tool to pass the schema as the provider's structured output option; the diff is a few lines. Either way the schema stays yours; its fields are your feature's requirements. Our fridge call now sends the one-field schema, plus a prompt line to return only items visible in the photo, and the screen has drawn rows, never paragraphs, ever since.

Check the values after the parse

Structured outputs end the parse failures and leave the harder problem standing, because a guaranteed structure is not a guaranteed value. {"items": ["caviar"]} matches the fridge schema perfectly and is wrong if no caviar appears in the photo; the model produces a well-formed wrong answer with the same fluency as a right one. Validation is the second half, and it is small. Ours is three lines in the route that makes the call:

const items = JSON.parse(reply).items;
if (items.length === 0) return askForRetake();
if (items.length > 60) return askForRetake();

An empty list means the model got nothing usable from the photo, so the feature asks for a retake instead of guessing, and a list past sixty items is suspect enough to re-ask rather than render. The receipt schema's checks are the same size: total is positive and under a sanity cap, date is a real calendar date, currency is already held to three values by the enum. Write one check per field that could hurt you, and run it after the parse, before anything acts on the value.

When prose is the right output

None of this applies when the reply goes to a person. A drafted reply from Gmail's Help me write, or a meeting recap from Zoom's AI Companion, goes straight to a human who reads, edits, or discards it, and a schema would only add a decoding step. The test takes a second: if code touches the reply, use a schema plus checks; if only a person reads it, let it be prose. Some features are both, a recap a person reads plus action items code files into a task list; each output gets its own treatment.

Try it now

Twenty minutes; the first path spends nothing, the second a fraction of a cent. You arrive with Prompt v1 from Prompting is engineering, not wording. You leave with your schema, the fields your feature's code needs with a type on each, plus one validated output, and both ride into the drills ahead.

No setup: Write your feature's schema on one page: every field your code needs, a type beside each, allowed values wherever a wrong one would hurt, using the receipt schema as your template. Then be the parser: try to extract each of your fields from the "Sure!" reply above by rule rather than by reading, and mark how few survive prose. Finish with one plain-English value check per field, like "total is positive and under 500."

With your tools: Hand Claude Code your schema and Prompt v1, and ask it to pass the schema to your feature's model call as the provider's structured outputs option, with your value checks after the parse and a retry or retake when one fails. Run one real input and keep the reply that passes; that is your validated output. If your tools are not set up yet, the Setup Clinic gets you there in one sitting. In Codex or Cursor the move is the same: paste the schema, ask for the structured output setting plus checks, run one input.

Chapter Summary

  • A model reply that reads well to a person can be useless to your code: our fridge feature drew a blank screen because code written for a list of items received a friendly sentence.
  • When code consumes the output, ask for data against a schema, the written list of field names and types your code expects on every call.
  • Prompt instructions like "reply with JSON only" raise the odds of clean output without guaranteeing it, and the misses land on real users.
  • The fix has a name to ask for: structured outputs, also called JSON schema mode, one setting on the call at OpenAI, Anthropic, and Google, which constrains generation so the reply always parses.
  • A guaranteed structure is not a guaranteed value; schema-perfect JSON can still carry a wrong answer.
  • Validation is a few lines of code after the parse, numbers in range, real dates, allowed options, with empty or absurd results routed to a retry or a retake.
  • When only a person reads the reply, prose is the right output; the schema is for replies that code consumes.
  • Carry your schema and your validated output forward; next, Give the model the facts it wasn't trained on works on making the values right, not just well-formed.

Sources

  • OpenAI, Anthropic, and Google Gemini structured output documentation (last verified July 2026).
  • JSON Schema specification, json-schema.org.