Your LLM Isn't Bad At Math. It Was Never Doing Math In The First Place.
In a tutorial, an LLM call looks like a function: pass in text, get back an answer, move on. It's easy to start treating the model like it's evaluating your business logic the same way a function would, deterministically, the same input always producing the same output.
In production, that assumption breaks in a specific, predictable way, and it's worth naming precisely why.
Traditional software runs on deterministic logic: if input A meets condition B, output C happens, every time, with a proof you could write on a whiteboard. An LLM runs on statistical probability: it predicts the most likely next token given the patterns in its training data. Those are two different kinds of "correct." The friction shows up exactly where a team asks the second kind to do the first kind's job.
Below are the places that friction actually shows up in a live system. These are illustrative scenarios based on the shape this failure takes, not one specific incident.
Asking a model to do arithmetic is asking it to guess what arithmetic usually looks like
Say a pipeline extracts line items from a batch of invoices, then asks the same model call to also compute the subtotal, apply tax, and return a total. On short, simple invoices, it's right almost every time, because short simple math is heavily represented in training data and easy to pattern-match. On a 40-line invoice with mixed tax rates and a rounding rule, it starts being right most of the time, which is a different and much worse thing than right. Nothing throws an error. The number is just plausible instead of correct, and "plausible instead of correct" is invisible until someone reconciles the books.
A statistically-applied business rule is not the same rule as a logically-applied one
Say the business rule is "refunds are allowed within 30 days of purchase." Put that rule in a prompt and ask the model to decide eligibility case by case, and it will get the easy cases right: a purchase from six months ago, obviously no; a purchase from yesterday, obviously yes. Where it gets interesting is the boundary: day 29, day 30, day 31, across time zones, with a purchase timestamp in one format and today's date passed in another. A deterministic date comparison gets this right every single time by construction. A model is producing its best guess at what "a refund decision near the boundary" looks like, based on how those decisions were phrased in its training data, and that's a meaningfully different operation even when it happens to output the correct answer nine times out of ten.
The output still looks like a normal response, which is exactly the problem
This is what makes the failure mode dangerous rather than just annoying: a wrong statistical answer doesn't look different from a right one. It's the same JSON shape, the same confident tone, the same absence of an exception. There's no signal in the response itself that tells you this was a guess rather than a computation. You find out later, from a customer service escalation or a finance reconciliation, not from anything in your logs.
Where the line actually goes
None of this means don't use the model near your business logic. It means be precise about which half of the job you're handing it.
What the model is good at: turning unstructured input, an email, a contract clause, a support ticket, a scanned form, into structured data. That's genuinely ambiguous work: language is fuzzy, humans phrase the same request ten different ways, and a statistical model that's seen millions of phrasings is the right tool for mapping "hey can I send this back, it's been like a month" into structured fields like intent and stated days since purchase.
What the model should never be the last word on: the validation, the calculation, and the rule enforcement that runs on that structured data once you have it. That's deterministic code's job, on purpose, with a strict schema at the boundary so a malformed or out-of-range extraction fails loudly instead of quietly flowing downstream as a confident-looking guess.
We lean on this split in how Cyclopt's automated checks work: the model interprets unstructured signals in a codebase or a pull request, but the actual rule enforcement, the pass or fail line, runs through deterministic logic against a defined schema, not through the model re-deciding the rule each time. The interpretation layer changes. The rule layer doesn't get to.
The reframe
Statistical vs. logical correctness isn't a model-quality problem that gets solved by a better model. It's an architecture decision, and better models make it easier to ignore, not less necessary to make. Let the model handle the ambiguity. Let your code handle the rules.
Where do you actually draw that line in a system you've shipped? What's the one thing you learned the hard way should never have been the model's call to make?
Top comments (17)
The distinction between interpretation and execution is really useful here. I’d add that the same principle applies to financial AI systems: an LLM can interpret a transaction request or extract intent from messy user input, but balances, fees, limits, and transaction validation should still be handled deterministically. The model can help translate ambiguity into structured data, but it shouldn’t be the source of truth for the numbers.
That is a great point and it really extends the analogy. It makes sense to treat the model as the translator for messy human intent while keeping the actual ledger logic strictly deterministic. I like how that highlights that the model shouldn't be the source of truth for the numbers even when it is helping to process them.
"Right most of the time" is the line that should scare people, because nine-in-ten sits underneath the detection threshold of any spot-check you'd realistically design - pull twenty invoices for QA and they all reconcile. The only thing that surfaces it is computing the number a second, independent way and diffing, and once you've written that path you've written the deterministic version anyway. On the schema handoff: do you actually gate on the confidence score, or does it just sit there for the post-mortem?
That nine in ten statistic is exactly why the failure is so quiet and dangerous since spot checks will almost always pass and you only catch it during reconciliation when the error has already caused real damage. We don't actually gate on confidence scores because they are notoriously unreliable indicators of factual accuracy so instead we treat the model output as a first draft that must pass through strict deterministic validation before it ever touches a production system. This means the schema handoff acts as a hard filter where any value outside expected ranges or logical constraints triggers an immediate rejection rather than a silent pass so we avoid the trap of trusting a plausible looking guess over a computed truth.
Schema validation catches the impossible value. The nine-in-ten case is the plausible one - a date that exists, an amount inside every range you set, just not the one on the invoice. What catches that is the source span jo-do raised above: check that the quoted text actually contains the value you parsed out of it. Rejecting out-of-range is a type check; comparing against provenance is the only part that tests the extraction itself.
The refund boundary is the cleanest example of this because it's so ordinary. Day 30 in one timezone and day 31 in another isn't a model quality problem at all — it's a rule that was never specified precisely enough to be computed. Asking a model to apply it doesn't degrade the model, it just reveals that the business rule was written as prose.
Where I'd push one step further: the extraction half (ticket text into
intentandstated_days) really is the model's job, but the moment I've tried that in anger the failure shifted to the schema — a customer writing "it's been like a month" maps to 30, 31 or 28 depending on the day they sent it, and the model has no way to know which one your rule means. So we ended up making the model emit a range plus the raw phrase, and letting the deterministic layer decide what to do with an ambiguous span. Did you land on a single scalar for that field, or does your structured output carry the uncertainty forward?Totally agree that a fuzzy phrase like "about a month" breaks a rigid scalar field, which is why we ended up having the model pass through the raw quote alongside the parsed value so downstream code can handle the ambiguity instead of the model guessing.
This is a great example of what happens when model makes wrong assumptions and how ambiguity can create subtle bugs. The problem is they don't ask clarifying questions like humans.
I think the issue is less about asking clarifying questions and more about the fundamental difference in how they process information. Humans naturally pause to ask for clarification when faced with ambiguity, but LLMs are designed to predict the most likely continuation. This can lead to confident but incorrect answers when the input is truly ambiguous. The key is to structure our prompts and systems to minimize ambiguity, not to expect the model to act like a human.
This shifts the burden to the users. So it becomes another hurdle for using it the right way. I feel like the simplicity is lost. This exposes the complexity. Maybe for power users, it is acceptable?
You are spot on, and that is exactly why the burden has to fall on the developers building the application rather than the end users. If we expect everyday users to write perfect, unambiguous prompts just to get a reliable result, the simplicity is completely lost. The goal of good system design is to build those deterministic guardrails and validations into the software itself, wrapping the model so it feels simple and safe for the user while the code handles the complexity behind the scenes.
It is a good idea to split the end user from developers. So it is easy for users, developers job is to tackle the guardrails.
Exactly, that is the sweet spot. When developers take on the heavy lifting of building those guardrails, the end user gets to experience the AI as pure magic without needing to understand the underlying complexity. It turns a temperamental statistical engine into a reliable product, allowing users to just focus on what they want to achieve while the software quietly keeps things on the tracks.
The best boundary I have found is exactly this: let the model extract the operands and intent, then let code compute and enforce. The structured handoff should keep the source span and confidence for every extracted field, though. A perfect date comparison still gives the wrong refund decision if the model silently extracted the wrong purchase date.
That is a crucial point because even a bulletproof deterministic rule will still fail if the model silently hallucinated the purchase date during extraction, making source spans and confidence scores essential parts of that schema handoff.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.