✦ Insights

Structured Output Changed How We Build With LLMs

Moving from regexes over free text to schemas changed how we treat models: as typed components we can test, validate and replace. Here is what it fixed and what it does not.

AIAltisly Engineering19 December 20255 min read
All posts

The regex era

Early in building with language models, a lot of our code looked like this: ask the model for a result, receive a friendly paragraph, then write a parser to dig the facts out of it. The parser used regular expressions. The regular expressions were cursed.

Some days the model wrote "Amount: 5,000". Other days "The total is NGN 5000.00". Once it wrote the number in words. Each variation produced a bug, and each bug produced another pattern, until the parsing code was longer than the feature it served.

Structured output ended that. We now think it is one of the most practical changes in how software uses language models, and it deserves less mystique than it gets.

What it is

Instead of asking the model to write prose and hoping, you give it a schema: the fields you want, their types, which are required, which values are allowed. The model's response is constrained to match. You receive an object, not a paragraph.

Different providers implement this differently. Some constrain generation directly so that invalid output cannot be produced. Others rely on tool calling, where the schema describes a function the model "calls". The details vary and change over time, so check what your provider guarantees and what it merely encourages. The idea is the same in each case.

A typical schema, written in TypeScript style, might look like this:

type InvoiceExtraction = {
  supplier: string
  currency: "NGN" | "GHS" | "GBP" | "USD" | "other"
  amountMinor: number
  dueDate: string | null
  needsReview: boolean
  reviewReason: string | null
}

Notice the choices. Money is an integer in minor units, so there is no decimal ambiguity. Currency is an enumeration, so "Naira" and "NGN" cannot both appear. A missing due date is an explicit null, not an invented one.

What it changed in practice

The model became a component. Before, the model was a collaborator whose answers needed interpretation. Now it is a function with a typed return value. Other code can depend on it, test it and replace it.

Validation moved to the edge. We still validate the object, because a well formed answer can be a wrong one. But the validator now checks meaning (does the amount make sense, is the date in the future) and not shape. The two kinds of failure are handled differently, and that clarity helps.

Failures became legible. When output is free text, a bad result is a mystery. When it is an object, you can see which field was wrong, log it, and count how often. Patterns show up that prose hid.

Prompts got shorter. A great deal of old prompt text existed to coax a format: "respond only in JSON, do not add commentary". The schema does that job better, and the prompt can go back to describing the task.

The habits we have kept

Give the model a way out. Fields like needsReview and reviewReason, or nullable values, let it say "I am not sure" without inventing something to fill the gap. A schema that forces an answer will get one.

Keep schemas small and specific. One schema per task, with fields that mean one thing each. Large, loose schemas bring the old ambiguity back with better formatting.

Describe the fields. A short description on each property is read by the model and acts as part of the instructions. It is the cheapest accuracy improvement we know.

Version your schemas. Downstream code depends on them, so changing a field is a breaking change, and should be treated as one.

What it does not fix

A valid object is not a true one. The model can still misread an invoice and return a perfectly shaped, perfectly wrong amount. Structure removes formatting errors, not reasoning errors. Anything that matters, such as money or access, still needs checks that do not trust the model, and often a person for the doubtful cases.

Constrained output can also, in some setups, nudge quality in subtle ways, particularly where the schema forces the model to commit to an answer before it has reasoned. If your task needs thinking first, give the schema a field for reasoning that comes ahead of the answer, and measure the result on your own examples instead of assuming.

Testing a structured pipeline

Once the output is an object, testing gets ordinary, which is the best thing about it. We keep a set of real inputs with the correct objects beside them, run the pipeline against the set whenever a prompt, schema or model changes, and compare field by field. A change that improves supplier names and quietly worsens due dates is now visible in a single run, where before it would have been a feeling.

Two details help. First, test the awkward inputs on purpose: the blurry scan, the invoice in two currencies, the one with no date. Second, record which cases the model flagged for review, and check that the flagged ones are the ones it really got wrong. A model that is unsure about the right things is more useful than one that is slightly more accurate and confident about everything.

We also keep the schema next to the code that consumes it, so a change to one forces a look at the other. It sounds trivial. It prevents the kind of drift where the prompt promises one shape and the database expects another.

Cost and speed

Structured output has small costs worth knowing. Schemas add tokens to each request. Strict constraints on generation may add a little latency on the first call with a new schema in some providers. For most workloads this is negligible next to the engineering time saved, but if you run at very high volume, measure it.

Where to begin

Find the place in your codebase where you parse model output with string handling. Replace that one place with a schema. Keep the old path behind a flag for a week, compare the results, and look at how much code you can delete. In our experience it is more than you expect.

LLMsStructured OutputEngineering
A

Altisly Engineering

Writing about systems architecture, operations software and what it takes to keep a build coherent, at Altisly.

Have a problem worth solving?

Tell us what is not working, what you are trying to build, or where you want to go next.

What happens next
01You describe the operation.
02We reply within one business day.
03If there is a fit, we tell you what the next step looks like.