Schema strictness should track consequence.
In the tax-compliance system I built, some model calls are forced through rigid schemas and rejected on any mismatch, while others get light, forgiving validation. A reviewer once flagged this as inconsistency. It’s the opposite: it’s the most deliberate design decision in the pipeline. Validate model output by what it becomes, not by where it came from.
The inconsistency that isn’t
The system uses language models in about a dozen places. On the trial-balance path — where extracted figures will, after human approval, flow toward a filed tax declaration — every call uses forced tool-use and validates against a strict typed model. Extra fields: rejected. Wrong types: rejected. Mismatch: the call fails and the pipeline knows it.
Elsewhere — mapping suggestions a person will review, document summaries a person will read — the same system parses plain JSON with tolerant, hand-rolled validators that shrug off imperfections.
Same codebase, same models, radically different strictness. The organizing principle: the first group produces numbers a human will approve into a filing; the second produces suggestions a human will read. The consequence differs, so the contract differs.
The two failure modes of uniform validation
Teams that miss this distinction end up in one of two bad places.
Uniformly strict systems are brittle. When every model output — including throwaway suggestions — must clear a rigid schema, the pipeline fails constantly on harmless variance. The team then does the natural thing under pressure: they loosen the schemas. All of them. Including the ones that mattered.
Uniformly lenient systems are dangerous. The tolerant parsing that was fine for a summary quietly becomes the only thing standing between a hallucinated value and your database. Nothing fails, which is precisely the problem — the wrong number that looks right travels furthest.
A working taxonomy
Classify every model call site by the blast radius of its output:
That third category is easy to overlook. When a model extracts a figure from a supporting document purely so a control can compare it against a declared value, a failed extraction shouldn’t crash anything — it should record “could not verify” and let the control treat absence as its own signal. The model can tell you a number looks wrong; it must never change the number.
Failure should degrade toward simpler, never toward smarter
Strictness also dictates what happens on failure. The rule I apply: fall back toward something more deterministic, never toward something more speculative. A structured extraction that fails falls back to a simpler extraction path — not to a cleverer prompt with more latitude. A repair pass re-requests exactly the missing rows with pre-composed identifiers the model copies rather than invents. And when the deterministic repair fails too, the call fails loudly. Escalating model creativity to paper over model failure is how systems drift from “wrong occasionally” to “wrong invisibly.”
Classify your own call sites
Take ten minutes and list every place a model’s output enters your system. For each, one question: if this output were subtly wrong, who or what would catch it before it had consequences? If the answer is “a person, reading it” — advisory, relax. If the answer is “nothing” — that call site needs a forced schema, a reconciliation step, or both. If the answer is “it feeds a check” — make sure failure degrades to null and absence is visible.
Most systems I audit have all three categories in the code and one validation policy across them. That mismatch is where the incidents live.
One validation policy for everything?
I audit LLM call sites and match contracts to consequences — usually without touching the prompts at all. mail@adamshawa.com · adamshawa.com