AI-Assisted Engineering

How to Make LLM Numeric Judgments Safe with Model Consensus

A single model can produce numbers that look right and are wrong. Cross-check two independent models and recompute the result in code before you trust it.

A language model that summarizes a paragraph can be a little wrong and still useful. A model that computes a threshold, validates an amount, or checks that two figures reconcile does not get that grace. A number is either correct or it is not, and a plausible wrong number is worse than an obvious one, because it passes review. This post describes a pattern that treats a model's numeric output as a claim to be checked, not a source of truth: run the same judgment through two independent models, accept the number only when they agree exactly, and recompute the result in plain code before it reaches anything that matters.

Language is forgiving, numbers are not

Most of what LLMs do well lives in a tolerant space. If a summary drops a nuance or a rewrite picks a slightly odd word, a reader still gets the meaning, and nothing downstream breaks. Natural language has redundancy built in, so small errors wash out.

Numeric judgment has none of that slack. When a model decides that a transfer of 1,050,000 is within a limit of 1,000,000, or that a discounted price rounds to one value when it rounds to another, there is no partial credit. The output feeds a gate, a ledger, or a control flow branch, and a single wrong digit changes the outcome completely.

The failure modes that make this dangerous are specific and repeatable:

  • Hallucinated confidence. The model states a figure with the same fluent certainty whether it derived it correctly or invented it. Nothing in the tone tells you which one happened.
  • Digit and magnitude errors. A model can drop a zero, transpose two digits, or land an answer one order of magnitude off while the surrounding sentence reads perfectly.
  • Unit and currency confusion. Percentages read as fractions, minutes read as seconds, one currency treated as another. The arithmetic is internally consistent and still wrong because the unit was wrong.
  • Boundary mistakes. Off-by-one behavior at a threshold, or an inclusive limit treated as exclusive, so a value that should be rejected slips through.

These are exactly the errors a fluent single answer hides best. The sentence is well formed, the reasoning sounds right, and the number is wrong.

Treat the model output as a claim, not an answer

The core shift is to stop asking a model for the answer and start asking it for a claim you will then test. Four rules turn a probabilistic generator into a safe gate.

  1. Cross-check two independent models and accept only when they agree.
  2. Recompute the number in deterministic code and reject any mismatch.
  3. Force structured output so the answers can be compared exactly.
  4. Isolate the model to a pure judgment, with no tools and no external state.

None of these trusts the model on its own. Each one assumes the output might be wrong and builds a check around that assumption. The rest of this post takes them one at a time.

The consensus gate: agreement or stop

The first rule is a unanimous gate, not a vote. Send the identical judgment to two independent models, with no shared context between them, and compare their answers. If both produce the same number and the same unit, treat that as a provisional accept. If they disagree by any amount, stop and hold the case for a human or a fallback path.

The word unanimous matters. This is not majority rule where the most common answer wins. With numeric safety, a disagreement is the most valuable output of the whole process. It means at least one model is wrong here, and you cannot yet tell which. Resolving that split in favor of the majority throws away the exact signal you paid for. The gate should fail closed: when in doubt, do not pass the number through.

Independence is what gives the gate its power. Two models from the same family, or the same model queried twice, tend to make the same mistake in the same place, so agreement between them proves little. Two genuinely different models trained on different data have different blind spots, so for both to produce the identical wrong number by chance is far less likely. You are not counting votes, you are checking whether two separate estimators land on the same point.

def consensus_gate(task, model_a, model_b):
    a = model_a.judge(task)   # structured: {"value": ..., "unit": ...}
    b = model_b.judge(task)   # same task, no shared context

    if a is None or b is None:
        return HOLD           # a model failed to answer, fail closed

    if a.value != b.value or a.unit != b.unit:
        return HOLD           # disagreement is a risk signal, escalate

    return Accept(a.value, a.unit)   # both agree, provisional accept

Notice that agreement here is only a provisional accept, not a final one. Two models can share a blind spot and be wrong together. The gate lowers that probability, it does not remove it, which is why the next rule exists.

Recompute the number in deterministic code

Consensus filters out most single-model errors, but it still leaves you trusting a number that a model produced. The second rule removes that trust: whenever the answer can be derived by ordinary computation, derive it, and use the model only for the parts that genuinely need judgment.

A useful way to think about it is a division of labor. The model is good at the fuzzy front of the problem: reading a messy input, deciding which rows are relevant, classifying an ambiguous field. It is weak at guaranteeing that a total is exact. So let the model do the reading and the selecting, then compute the arithmetic yourself and check the model's claim against your own result.

The deterministic layer should enforce three kinds of check. Parse the value into an exact type, never a float for money. Range-check it against known bounds. And assert the invariants that must hold regardless of what any model said, such as parts summing to a whole.

from decimal import Decimal

def verify(value, unit, source_rows):
    # 1. parse into an exact type, never a float for money
    amount = Decimal(value)

    # 2. range check against known bounds
    if not (MIN_AMOUNT <= amount <= MAX_AMOUNT):
        raise Reject("value is outside the allowed range")

    # 3. invariant: the parts must sum to the whole
    computed = sum(Decimal(r.amount) for r in source_rows)
    if amount != computed:
        raise Reject(f"sum mismatch: model said {amount}, code computed {computed}")

    # 4. unit must match what the pipeline expects
    if unit != EXPECTED_UNIT:
        raise Reject(f"unexpected unit {unit}")

    return amount

The mismatch branch is the important one. When the recomputed total disagrees with the model's number, the code does not average them or prefer one, it rejects. A discrepancy means either the model is wrong or the input the model read is not the input the code read, and both cases need a human before any number ships. This is the layer that catches a magnitude slip that both models happened to share.

Force structured output so the numbers can be compared

You cannot compare two answers, or recompute one, if the model buries the number inside a sentence. The third rule is to require structured output with a fixed schema, and to forbid free prose in the fields that carry values.

A schema does three things at once. It makes the comparison in the consensus gate an exact match on typed fields rather than a fragile parse of English. It gives the deterministic layer a clean value to check instead of a phrase it has to interpret. And it narrows the surface where a model can be creative, which is precisely where numeric drift sneaks in.

Ask for output shaped like this, and reject anything that does not fit:

{
  "value": "1050000",
  "unit": "KRW",
  "basis": "sum_of_line_items",
  "confidence": "high"
}

Keep the numeric value as a string in transit and parse it into an exact decimal type on arrival, so no floating point rounding creeps in between the model and your check. Treat a response that is missing a field, adds commentary, or wraps the number in explanation as a failed response, the same as a disagreement. A model that will not answer in the required shape has not answered.

Isolate the model from tools and state

The last rule is about what the model is allowed to touch. For a numeric judgment, give it nothing but the input and the question. No tools, no ability to call out to other systems, no shared memory with the other reviewer, no write access to anything.

Isolation buys two things. It keeps the two models genuinely independent, since neither can see the other's reasoning or anchor on a shared intermediate result, which is what makes their agreement meaningful. And it keeps the blast radius at zero: a model that can only return a structured claim cannot act on a wrong number, it can only propose one, and every proposal passes through the gate and the recheck before anything happens. The model is a sensor, not an actuator. It reports a reading, and code decides what to do with it.

Single model versus the consensus gate

The pattern adds moving parts, so it helps to see plainly what those parts buy against a single call.

Dimension Single model Two-model consensus gate
Silent wrong number ships downstream blocked unless both agree and the recheck passes
Confident hallucination often accepted caught unless both share the exact error
Cost per judgment one model call two or more model calls
Latency one model the slower of the two
On disagreement nothing to notice flagged and held for a human
Best fit reversible, low-stakes text money, thresholds, reconciliation

The single-model row is not wrong for every job. It is wrong for the jobs where a plausible bad number does real damage.

What it costs, and when to skip it

The gate is not free. You pay for two or more model calls per judgment instead of one, and you wait for the slowest of them, since they run in parallel but the last one home sets the pace. There is engineering cost too: a schema to maintain, a deterministic checker to write, and a hold path for disagreements that a human or a fallback has to handle. For a high-volume, low-value stream of judgments, that overhead can outweigh the benefit.

So match the pattern to the stakes. The full gate earns its cost when a wrong number is expensive and hard to walk back:

  • Money paths. Amounts, limits, refunds, anything that moves value or gates a transaction.
  • Thresholds and controls. A computed cutoff that decides whether something is allowed.
  • Reconciliation. Checking that two independent figures agree, where a false pass hides a real break.
  • One-way outputs. A number that gets written to a ledger or sent to a partner and cannot be quietly corrected later.

For the opposite case, skip it. A model that suggests a rough estimate for a human to eyeball, a summary of a report, a draft that a person will edit anyway, none of these need two models and a decimal checker. Running the full gate on a throwaway estimate is wasted latency and spend.

The honest limit is the shared blind spot. Consensus lowers the chance of a wrong number, it does not drive it to zero, because two models can be trained on the same flawed pattern and repeat it together. That is why the deterministic recheck sits behind the gate and why a human stays on the disagreement path. The model is strong at generating a candidate and weak at guaranteeing it is exact. Consensus, deterministic recomputation, and fail-closed handling turn that weak guarantee into a gate you can put in front of a number that has to be right.

Related posts