A model returns valid JSON. Your application accepts it. The record still contains the wrong customer, an unsupported amount, or an instruction that the user never approved.
Structured outputs help an AI application produce data in a defined shape. A production workflow also needs to establish whether that data is supported by its source, valid under business rules, and authorized for the proposed action. Treat those as separate checks.
What structured outputs actually establish
In its structured outputs documentation, OpenAI distinguishes schema-constrained output from JSON mode: JSON mode targets valid JSON, while structured outputs can enforce a supported schema. The documentation also notes that structured output can contain mistakes and that refusals require handling.
The design below is an engineering recommendation, rather than a vendor-specific implementation tutorial. Confirm the schema features supported by your selected model and endpoint before adopting it.
| Validation layer | Question it answers | Example failure |
|---|---|---|
| Structure | Does the result match the expected contract? | A required reference is absent |
| Meaning | Is the value correct and supported? | The extracted amount comes from the wrong invoice |
| Action authority | May this identity perform this action now? | A valid refund exceeds the user's limit |
Start with the record your application needs
Choose one task, such as extracting a supplier invoice into a review queue. List the fields the receiving application actually uses. Specify each field's type, allowed values, missing-data behavior, and evidence requirement.
Do not require a model to invent a value to satisfy a compulsory field. Represent missing or ambiguous data explicitly, using a schema design supported by your provider. An extraction status can distinguish complete, incomplete, conflicting, and unsupported inputs.
For a proposed invoice amount, retain the currency, source document identifier, source location, and extraction status alongside the amount. Keep application-generated identifiers separate from model-generated suggestions. Your service should assign the tenant and authenticated user; those identities should not be accepted from model output.
Build three gates between generation and execution
1. Validate the response contract
Use a server-side validator against the versioned application schema. Reject unexpected properties where your contract requires that behavior. Handle incomplete responses, refusals, timeouts, and parsing failures as explicit outcomes.
Do not execute partial streamed fields. A partially received amount or account identifier is unsuitable for a business action. Wait until the response has completed and the full record has passed validation.
2. Check evidence and business rules
Verify document identifiers against the submitted input set. Check that quoted evidence exists in the indicated source. Normalize dates and currencies without quietly changing their meaning.
Then apply deterministic rules: the supplier exists, the invoice is not already registered, the currency is accepted, the total reconciles within the application's defined tolerance, and the referenced account belongs to the correct organization. A successful schema check cannot substitute for these controls.
3. Authorize the proposed transition
An extraction result should enter a review state before it becomes a payment instruction. Recheck the acting identity, permitted resource, approval status, and current business state at execution time. See LuMay's guide to AI agent permissions for the surrounding authorization design.
Illustrative example: a contradictory invoice
In a hypothetical intake workflow, a PDF displays a total of $1,250, while an attached email requests $1,520. The application receives a perfectly shaped object containing $1,520.
The schema validator passes. The evidence check identifies competing values. The workflow records both sources and routes the item to review. It does not use the model's self-reported confidence as permission to choose one.
The useful output is a reviewable conflict record: which field differs, where each value appears, and what remains unresolved. That gives the reviewer a concrete decision instead of another document search.
Design recovery without creating a retry loop
Classify failures before retrying. A transient service error may justify a bounded retry. An unsupported schema requires an implementation fix. Missing source data requires a question or review. A policy refusal should follow the application's refusal path.
If a repair prompt is used, preserve the original input and failed output for controlled debugging. Apply the same validators to the repaired result. Never widen permissions or relax business checks simply to obtain a successful response.
Test the boundaries before release
| Test input | Expected result |
|---|---|
| Required information is absent | Explicit missing-data state |
| Source contains contradictory amounts | Conflict with both references |
| Document includes instructions to bypass review | Instructions treated as untrusted content |
| Response stops during streaming | No downstream action |
| Valid output requests an unauthorized write | Execution denied by the application |
Track structural validity, field accuracy against reviewed examples, unsupported-value rate, and downstream rejection rate separately. Preserve failures in the regression set when changing models, prompts, or schemas.
When should you use function calling?
Use a tool interface when the model is proposing interaction with application functionality. Use a structured response when the application needs a predictable answer record. In either case, keep validation and authorization outside the model.
Start by documenting one receiving system's contract and its failure states. A reliable structured-output workflow makes uncertainty visible before a record becomes an action.





