Structured outputs and function calling for reliable LLM automation

What structured outputs and function calling guarantee, where the guarantee stops, and how to design schemas and checks so an LLM can safely feed an automation.

AI Automation7 min read

A model that writes a lovely paragraph is no use to a script that expects an invoice number, a date and a total. The first time you wire an LLM into a real workflow, you find this out fast. One reply in fifty comes back with a stray sentence before the JSON, a field renamed, or a total written out in words. The parser throws, the job stalls, and someone ends up checking the queue by hand.

Structured outputs and function calling are the main fixes the model providers offer for this. They're worth using. They also get oversold, so this piece covers what they actually guarantee, where the guarantee stops, and how to design around the gap.

Prompting, JSON mode and schemas#

There are roughly three ways to ask a model for machine-readable output, and they promise very different things.

The weakest is asking nicely in the prompt: "respond only with JSON in this shape". Most of the time it works. When it doesn't, you get the failures Anthropic's documentation lists for unconstrained output: malformed JSON, missing required fields, inconsistent data types and schema violations that need error handling and retries.

Next is JSON mode. It makes sure what comes back parses as JSON, but nothing more. OpenAI's guide puts the difference plainly: both produce valid JSON, but only Structured Outputs ensure schema adherence. Valid JSON with the wrong keys still breaks your pipeline.

The strongest is schema-constrained output, which the major APIs now call structured outputs. You hand over a JSON Schema and the model's output is forced to match it. The gap between the second and third levels is large. When OpenAI launched the feature, its announcement reported that on its evals of complex JSON schema following, gpt-4o-2024-08-06 with Structured Outputs scored 100%, while gpt-4-0613 scored under 40%. That's a vendor's own benchmark, but the direction matches what anyone who has parsed free-form JSON at volume will recognise.

How constrained decoding works#

The guarantee doesn't come from the model trying harder. It comes from the sampler. OpenAI's announcement describes converting the supplied JSON Schema into a context-free grammar, then working out which tokens are valid after each token is generated. Tokens that would break the schema simply can't be picked. Anthropic's docs describe the same idea as constrained decoding.

Two practical consequences follow. First, there's a one-off cost when you use a new schema. OpenAI said typical schemas take under 10 seconds to process on the first request. Anthropic says compiled grammars are cached for 24 hours from last use, and that changing the schema structure (or the set of tools) invalidates the cache, while editing only names or descriptions doesn't. If you generate schemas dynamically per request, you'll pay that compile cost over and over.

Second, only a subset of JSON Schema is supported, because not everything can be turned into a grammar. Anthropic's current list of unsupported features includes recursive schemas, numerical constraints like minimum and maximum, string length limits, and additionalProperties set to anything other than false. Send one of those and you get a 400 error. So the schema that constrains generation often can't express all your business rules. A quantity field can be forced to be an integer, but not forced to be positive.

The same approach works locally. Ollama accepts either "format": "json" or a full JSON schema in the format field. Its docs also suggest passing the schema as text in the prompt to ground the response, and lowering the temperature (for example to 0) for more deterministic output.

Function calling vs a structured response#

Function calling (also called tool use) is the same machinery pointed at a different job. You describe functions with names and parameter schemas, and the model replies with a call: which function, and with what arguments. Your code runs it.

OpenAI's rule of thumb is simple. If you're connecting the model to tools, functions or data in your system, use function calling. If you want to shape the reply the model gives the user, use a structured response format. Anthropic exposes both as separate switches: output_config.format for JSON outputs, and strict: true on a tool definition to guarantee schema validation on tool names and inputs. You can use them together.

The important word there is "strict". Without it, a tool call is still the model's best guess at matching your parameter schema. With it, the arguments are constrained like any other structured output. If your automation does anything with side effects (creating a ticket, sending an email, updating a CRM record), turn strict mode on.

What the guarantee doesn't cover#

A schema guarantees shape, not truth. OpenAI's announcement says it outright: Structured Outputs doesn't prevent all kinds of model mistakes, and the model may still make mistakes within the values of the JSON object. A perfectly valid {"invoice_total": 1250.00} can still be the wrong number, read from the subtotal line.

There are also documented cases where the output won't match the schema at all:

  • The response hits the token limit. OpenAI says the model can fail to follow the schema if generation reaches max_tokens or another stop condition first. Anthropic flags this case with stop_reason: "max_tokens" and warns the output may be incomplete.
  • The model refuses. Anthropic returns stop_reason: "refusal" with a normal 200 status, and the refusal text takes precedence over the schema. OpenAI says refusals are programmatically detectable via a refusal field.
  • Small drift inside the rules. Anthropic's docs warn that an enum value can come back differing from the schema only in capitalisation, with no error and no special stop reason.
  • Feature conflicts. OpenAI's launch post noted Structured Outputs wasn't compatible with parallel function calls, and suggested setting parallel_tool_calls: false.

So your code still needs to check the stop reason before it trusts the payload, and still needs a path for "this didn't work".

Strict formats can make reasoning worse#

There's a less obvious cost. In Let Me Speak Freely?, researchers from Appier and National Taiwan University compared free-text answers with format-restricted ones. They report a significant decline in reasoning ability under format restrictions, with stricter constraints generally causing bigger drops on reasoning tasks. One cause is mundane: on one of their tasks, 100% of GPT-3.5 Turbo's JSON-mode responses put the "answer" key before the "reason" key, so the model committed to an answer before working anything out.

Classification went the other way. The authors found JSON mode competitive with, and sometimes better than, the looser approaches, probably because a constrained answer space cuts down on picking-the-wrong-label errors.

The paper's suggestion is a two-step approach: let the model answer in natural language first, then convert that answer into the target format. In practice you can get most of the benefit with schema design alone:

  1. Put a reasoning or notes field before the decision fields, so field order lets the model think before it commits.
  2. Keep each call to one job. Extraction, classification and judgement are easier to test separately.
  3. Use enums for anything your code branches on ("approve" | "reject" | "needs_review"), not free text.

Note that the paper tested 2024-era models. Newer models may suffer less, but the field-ordering trick costs nothing, so it's worth doing anyway.

Designing schemas for automation#

A few habits make structured output far more useful downstream.

Model uncertainty explicitly. If an invoice has no due date, you want null, not an invented date. Make fields nullable where the information may be missing, and add a status enum like "complete" | "partial" | "unreadable" so the model has a legitimate way to say it couldn't do the job.

Keep schemas stable. Because grammars are compiled and cached, a fixed schema per task is faster and easier to test than one built on the fly. Version your schemas like any other interface.

Describe fields properly. Names and descriptions are part of the prompt. total_incl_gst with a one-line description beats amount.

Put business rules in code. Positive quantities, totals that equal the sum of line items, dates not in the future, customer IDs that exist in your system: these belong in a validation step after parsing, partly because the schema subset can't express them, and partly because they're the checks that catch wrong values in a well-formed object. Libraries like Pydantic or Zod, which Ollama's docs recommend for defining schemas, can do the parsing and the business validation in one place.

Treat the output as untrusted input#

Even a perfectly shaped tool call is still text chosen by a model that may have read a malicious email or web page. OWASP's entry on improper output handling recommends treating the model as any other user, with a zero-trust approach, applying input validation to responses before they reach backend functions, and using parameterised queries for any database work involving LLM output. Structured outputs make that validation easier to write. They don't replace it.

At TheAILAB we treat every LLM step in an automation the same way: constrain the shape with a strict schema, check the stop reason, validate the values in code, and route anything that fails (or comes back needs_review) to a person instead of retrying blindly.

If you take one thing from this, make it the order of operations. Turn on strict structured output so parsing stops being your problem. Then spend the time you saved on the validation layer, because that's where the errors that actually cost money get caught.

Sources

  1. Introducing Structured Outputs in the API — OpenAI (6 August 2024) (checked 2026-10-12)
  2. Structured model outputs — OpenAI API documentation (checked 2026-10-12)
  3. Structured outputs — Anthropic, Claude Platform Docs (checked 2026-10-12)
  4. Structured Outputs — Ollama documentation (checked 2026-10-12)
  5. Let Me Speak Freely? A Study on the Impact of Format Restrictions on Performance of Large Language Models — Tam, Wu, Tsai, Lin, Lee and Chen, Appier AI Research and National Taiwan University (arXiv, August 2024) (checked 2026-10-12)
  6. LLM05:2025 Improper Output Handling — OWASP Gen AI Security Project (checked 2026-10-12)

Dealing with this in your own business?

Tell us what you're working on and we'll say whether it's worth automating.

Get in touch