Skip to content

05. Output schema validation — the answer also needs a passport before it leaves

~13 min read. A correct thought in the wrong shape is still a production failure.

Built on the ELI5 in 00-eli5.md. The passport desk — the checkpoint that checks document shape — must inspect outputs too, not only inputs.


Good text is not enough when machines depend on it

A human can forgive messy prose. A downstream service usually cannot. If your assistant feeds a workflow, then structure is part of correctness. That is why output validation belongs near arrival customs.

Look at the common failure. The prompt asks for JSON. The model returns almost-JSON. One missing quote. One trailing comma. One extra explanatory sentence. The human reader shrugs. The parser crashes.

model completion
┌──────────────────┐
│ passport desk    │  validate exact output shape
└───────┬──────────┘
        ├── pass ──→ parse and execute
        └── fail ──→ retry, repair, or refuse

Now what is the deeper issue? Syntax is only the first layer. Even valid JSON may still be semantically unusable. A field may be missing. An enum may be invented. A number may be out of range. The assistant may swap customer_id and order_id. So output schema validation must check both shape and constraints.

The airport analogy helps. The model has reached arrival customs. But customs still asks, "Is this passport valid, complete, and consistent with the trip you declared?" Same idea.

Structured outputs make the model less slippery

Free-form prose is flexible. It is also dangerous for automated systems. If a model is expected to produce actions, routes, SQL fragments, UI blocks, or tool arguments, then constrain the format.

Common ways to do this include JSON Schema, typed tool definitions, Pydantic models, XML templates with validators, and vendor-native structured output features. The important part is not the brand. The important part is deterministic validation after generation.

A practical rule is simple. Treat every model output as untrusted until the passport desk accepts it. That sentence alone prevents many fragile pipelines.

See a concrete structure.

expected object
├── action: enum ["refund_check", "escalate", "reply_only"]
├── confidence: number 0..1
├── customer_id: string pattern CUS-[0-9]{5}
└── explanation: string max 200 chars

If the model emits anything outside that, it has not finished the job. The job includes passing validation.

Worked example: malformed yet plausible JSON

Suppose the system expects this result from the assistant.

{
  "action": "refund_check",
  "confidence": 0.82,
  "customer_id": "CUS-48291",
  "explanation": "Customer reports duplicate renewal charge."
}

Instead, the model returns this.

Sure, here is the JSON:
{
  action: refund_check,
  confidence: "high",
  customer_id: "48291",
  explanation: "Looks refundable",
}

Looks close. Still wrong. The opening sentence breaks strict parsing. The enum lacks quotes. confidence is a string, not a number. customer_id misses the CUS- prefix. The trailing comma may break some parsers.

Now pass it through validation.

syntax check        → fail
schema fields       → fail
type check          → fail
pattern check       → fail
range check         → not reached

So what to do? First, strip only clearly allowed wrappers if your policy permits. Second, ask the model to regenerate under the same schema. Third, if retries keep failing, route to the no-fly desk or a human fallback. Do not improvise values silently.

A retry message can be narrow and effective.

"Return only JSON. Keep confidence as a number from 0 to 1. Use customer_id format CUS-12345. Do not add commentary."

That works because the validator is external. The validator tells the model exactly what failed.

Repair loops should be bounded and observable

Many teams build a repair loop. Good. But bound it. A loop without limits becomes latency and cost leakage.

Use a pattern like this.

candidate output
validate
 ├── pass ──→ continue
 ├── fail 1 ──→ targeted retry with error summary
 ├── fail 2 ──→ constrained regeneration
 └── fail 3 ──→ refuse or human handoff

Simple, no? Three tries are usually enough for structure. Beyond that, you probably have a prompt mismatch, model mismatch, or an inherently ambiguous task.

The control tower should track validation failure rate by prompt version, model version, and task type. If one release suddenly increases malformed outputs, that is an operational regression. Output validation is not only a runtime gate. It is also an early warning sensor.

Schema design should match business authority

Do not ask for more freedom than you really want. The schema should reflect allowed authority.

If only three actions are legal, use a three-value enum. If the assistant must not send arbitrary email bodies, do not allow a free-form 10,000-character string. If the workflow needs a yes-no decision plus evidence, request exactly that.

This is where passport desk design becomes product design. Loose schemas invite creative mistakes. Tight schemas reduce expressiveness but improve reliability. The right balance depends on whether the assistant is drafting, deciding, or acting.

One strong pattern is split outputs. Ask the model for a small structured control object plus a separate human-readable explanation. Validate the control object strictly. Treat the explanation as display text and still pass it through arrival customs for content filtering.

See the separation.

assistant result
├── control object  → strict schema, used by machines
└── explanation     → free text, used by humans

That one design trick prevents many brittle pipelines.

Output validation is part of defense in depth

Now what is the final lesson? Output schema validation is not only a developer convenience. It is a safety control.

It limits prompt injection damage by constraining what can leave the model. It limits hallucinated tool arguments. It reduces hidden parser bugs. It supports refusals when the model cannot meet contract. It makes incident analysis easier because failures become explicit pass-fail events.

The passport desk and arrival customs work together. One checks shape. The other checks safety. Keep both. A toxic but perfectly valid JSON object is still unsafe. A harmless but malformed JSON object is still unusable.

Look. Production AI succeeds when we separate these questions clearly.


Where this lives in the wild

  • OpenAI structured outputs — application engineer: defines JSON schemas so assistants return machine-usable objects instead of best-effort prose.
  • Anthropic tool-use workflows — backend engineer: validate tool arguments and model-produced JSON before executing actions.
  • Zapier AI actions — automation architect: rely on strict argument structure because malformed fields can trigger the wrong external app step.
  • Glean enterprise agents — platform engineer: need typed action payloads when assistants query or modify internal systems.
  • LangChain Pydantic parsers — production ML engineer: convert completions into validated objects with retries on malformed generations.

Pause and recall

  • Why is valid JSON still weaker than full output schema validation?
  • What should happen after the first, second, and third validation failure?
  • Why should schema design reflect the assistant's allowed business authority?
  • How does separating control objects from explanations improve safety?

Interview Q&A

Q: Why validate output externally instead of trusting vendor-native JSON mode alone? A: Because generation controls reduce errors but do not replace an independent pass-fail check aligned to your product's exact schema and policy. Common wrong answer to avoid: "Because JSON mode always returns invalid syntax anyway."

Q: Why cap repair retries instead of letting the model keep trying until it succeeds? A: Because repeated failures often indicate a deeper mismatch, and unbounded retries convert a schema problem into latency, cost, and abuse exposure. Common wrong answer to avoid: "Because models cannot improve after one failed attempt."

Q: Why prefer enums and bounded fields over broad free-form strings for action outputs? A: Because narrow schemas reduce unintended authority and make incorrect generations fail safely instead of executing creatively. Common wrong answer to avoid: "Because free text cannot be stored in JSON."

Q: Why separate structured control from human explanation rather than validating one combined blob? A: Because machines need deterministic control fields, while humans benefit from natural language, and combining them makes both parsing and safety checks weaker. Common wrong answer to avoid: "Because explanations are irrelevant once an action runs."


Apply now (5 min)

Exercise. Define one output object for your assistant with four fields. Add one enum, one numeric range, one ID pattern, and one length cap. Then write one almost-correct model output that should fail the passport desk.

Sketch from memory. Draw the loop. Model emits candidate JSON. The passport desk validates it. A pass continues. A fail triggers retry, then refusal. Add arrival customs next to the explanation text.


Bridge. Structure is now controlled. But a perfectly structured answer can still be toxic, sexual, violent, or off-policy. So next we build the customs officers for content itself. → 06-content-filtering.md