Determinism First: Use an LLM Only Where Determinism Runs Out

August 9, 2026

5 min read

Classify the input first: parse the structured slice, model only what's left

Take the plainest extraction task there is: pull a date out of a scanned invoice, converted to text. The date is sitting right there in the header, in the format it always is, 2026-08-14, next to the word "Issued."

The job is to get that date into a field, and the instinct is to hand the whole page to a model and ask it, nicely, for the date back as JSON.

That instinct is the trap now. You go straight at the thing that looks like the problem and miss that the actual work is somewhere plainer. A date in a known format is a regex. One line, no API call, no token budget, and it will never once hand you back a hallucinated Tuesday.

The mature move with AI is to shrink the part of the system that behaves non-deterministically until only the genuinely unstructured slice is left.

The Reflex, and Why It's Wrong By Default

There is real pressure right now to put a model in every feature. It reads as modern. It demos well. And for a task that is already deterministic, it's over-engineering with a monthly bill attached.

Think about what you pay to send that invoice date through a model. Latency, measured in hundreds of milliseconds to seconds, instead of microseconds. A per-token cost on every call instead of zero marginal cost on a compiled pattern. And a non-zero chance the model just gets it wrong, confidently, in a way a regex structurally cannot. A model that gives you a different answer to the same input on a bad day is the most unreliable function in your pipeline, and you've volunteered it for a job a deterministic function already does perfectly.

The rule I keep coming back to: you don't need AI for everything. Use the model where judgment is actually required. Everything else runs as code.

Where a Parser Does the Whole Job

Take a realistic extraction task. You've got a pile of messy documents (invoices, receipts, forms, log lines, scraped emails) and you need structured fields out of them: dates, amounts, email addresses, phone numbers, order IDs, the cells of a table.

Walk that list. Almost none of it needs a model.

  • Dates come in a handful of formats. You enumerate them, or you lean on a date library. Deterministic.
  • Amounts are a currency symbol, digits, separators. A pattern.
  • Emails and phone numbers are the textbook regex example. They have been for thirty years.
  • IDs follow a scheme you control or can read off two examples.
  • Table cells, once you have the table's structure, are position, not prose.

These fields are structured. Someone already imposed a format on them, upstream, and that format is exactly the thing a grammar or a regex is built to exploit. Reaching past it for a language model throws away the one property that makes the field cheap to extract.

This is where a tool like Stepbook earns its place in the argument. It records a typed pipeline and replays every step from cache, which means I can put the deterministic parser and the model on the exact same set of fields and actually watch them. On the structured fields the parser matches the model output field for field, at no cost, with no variance between runs. Once you've seen that side by side, the model call for those fields is just harder to justify.

Where the Model Actually Earns It

Now look at the field the parser can't touch: the free-text line where a human wrote whatever they wanted.

The line item description. The note in the memo field. The paragraph of context someone typed into a form that was supposed to take three words. There is no format there to exploit, because no format was ever imposed. "2 boxes letter-size, rush, leave with front desk" is not a pattern. Pulling { quantity, item, urgency, delivery_instruction } out of that is genuinely a language problem, and that is exactly the slice where a model is the right tool and a regex would be a tarpit of special cases.

That's the whole test. Was a format imposed on this field, or not? If yes, parse it. If no, and only then, model it.

When I do reach for the model on that slice, the output still gets pinned down. I encode a Zod schema into the prompt so the model knows the exact shape it owes me, then validate the response against that same schema on the way out. A model that returns malformed or off-schema output fails a real check instead of quietly poisoning the next step. The non-deterministic part stays boxed.

The Hybrid: Pre-Parse to Shrink the Model's Job

Here's the move that ties it together. Deterministic parsing and the model are a sequence: the parser runs first, and its job is to make the model's job smaller.

Before anything touches the model, I strip out every field I can pull deterministically. The date, the amounts, the IDs, the addresses, all gone, extracted, done. What reaches the model is only the residue: the free-text slice, and only that.

That does two things at once. It cuts the token count, because you're not paying to send the model a page of structured data it didn't need to see. And it cuts the surface area for error, because the model can't hallucinate a field it was never asked to produce. Most of the practical write-ups on this land in the same place: let deterministic rules carry the overwhelming majority of the predictable cases and reserve the model for the genuine edge. Same principle, applied one document at a time.

Classify the Input First

So the rule is small enough to keep in your head. Before you decide what extracts a field, classify the field.

Is it structured (a format was imposed on it) or unstructured (a human wrote it free-hand)? Structured goes to a parser. Unstructured, and only unstructured, goes to the model, behind a schema, on the smallest input you can hand it. Do not model what you can parse.

This is really the same discipline I use everywhere else: ask what work the model is actually doing, and give everything else to code. Most of the time, on most of the fields, the honest answer is: nothing a pattern couldn't. Treating the model as one untrusted step in a deterministic pipeline, rather than the engine of the whole thing, is what keeps the system testable.

If you do decide the field earns a model, two questions come next, and I've left them to companion pieces: which model to reach for, and how to feed it once you've picked one. This post is only about the first fork, whether to reach for one at all.

The skill worth building is drawing the smallest non-deterministic surface that still solves the problem, and then defending that boundary. The best extraction pipeline I've built is mostly boring code with a model doing one narrow, well-fenced thing in the middle. That's not a limitation I'm working around. It's the goal.