Taming the Dice
Every large language model is, underneath the marketing, a very expensive pair of loaded dice. It rolls to pick each word. That is not an insult and it is not a scandal, it is just how the machine works. This is the full field guide to that machine: how it actually works from zero, why it hands you a different answer to the same question, and the real, tested playbook for making it behave when you need it to.
Written so it is useful whether you have never touched an API or you ship LLMs to production for a living. No hand-waving, no hype, real vendor docs cited, and a diagram every time words are the slower way to explain something.
↓ scroll to read · click a section on the left to jump · press P to print
1000 identical requests. 80 different answers.
In September 2025, researchers at Thinking Machines Lab did something almost nobody bothers to do: they sent the exact same prompt to an LLM one thousand times, with the randomness dial turned all the way to zero, on the same hardware, changing nothing. In a perfect world you would get the same answer a thousand times.
They got 80 different answers. The most common one showed up 78 times out of 1000. And the very first word where the runs started to disagree was the 103rd token in. Same prompt. Same settings. Same machine. Eighty different completions. [1]
If you have ever asked a chatbot the same thing twice and gotten two different answers and quietly wondered whether you were losing it: you were not. This is the default behaviour of the technology, and it is stranger and more fixable than the usual "well, it is random" shrug suggests. By the end of this you will know exactly why it happens, at every layer, and what you can actually do about each one.
A real mental model of how an LLM produces a word (no maths degree required), a precise vocabulary for the problem, the three knobs everyone misuses (temperature, top_p, top_k) defined with worked numbers, what seed and system_fingerprint really buy you, how to write prompts that survive a model swap, how to force a fixed output shape, how to test any of it, and a tour of every other way these systems bite you in production. Cited, with pictures.
- 01How it works
- 02The dice: sampling
- 03Why it varies
- 04The knobs: seed & friends
- 05Prompting that travels
- 06Forcing structure
- 07How to test it
- 08The bestiary of pain
- 09The playbook
- 10Opinion
It is autocomplete. Enormous, uncanny, expensive autocomplete.
Strip away everything and a language model does exactly one thing: it reads the text so far and predicts the next chunk of text. That is the entire job. It does not "look up" an answer, it does not have a database of facts it queries, it does not decide what it thinks and then phrase it. It guesses the next piece, adds that piece to what it has read, and guesses again. Over and over, one piece at a time, until it decides to stop.
The phone keyboard that suggests "morning" after you type "good" is doing a tiny, dumb version of the same thing. Scale that up by a few hundred billion knobs, train it on a large fraction of everything humans have written, and the guessing gets good enough to write code, explain calculus, and pass the bar exam. But it is still, mechanically, guessing the next piece. Hold onto that. It explains almost everything weird that follows.
The five words you need: token, embedding, transformer, attention, softmax
- Token. Models do not see letters or even whole words. Text is chopped into tokens, sub-word chunks. "Tokenization" might become token + ization. Each token is just a number to the model.
- Embedding. Each token number is turned into a long list of numbers, a vector, that places it in a giant space where "king" and "queen" or "Paris" and "France" sit in geometrically meaningful spots. This is the model's sense of meaning, learned, not programmed.
- Transformer. The engine. A stack of identical layers introduced in the 2017 paper literally titled Attention Is All You Need, which threw out the older step-by-step approach so every word could be processed at once and any word could directly influence any other. [2]
- Attention. The trick that makes it work. At each layer, every token asks a question and every token offers an answer, and the ones that match get to influence each other (more on this in a second).
- Softmax. The final step that turns the model's raw scores into probabilities that add up to 100%, so one token can be picked. This little function is the secret star of this entire article.
Attention, in one honest sentence
Picture every word in a sentence at a meeting. Each one raises a hand with a question ("who here is relevant to me?"), wears a name-tag key ("here is what I am about"), and holds some content to share. Each word compares its question against everyone's name-tag, and blends in the content of whoever matches best. In "the animal did not cross the street because it was too tired," attention is the mechanism that lets "it" figure out it means the animal and not the street. That soft, learned lookup, repeated at every layer for every token, is the whole magic trick. [3]
Here is the mental image to keep for the rest of this article. At step 4, the model does not hand you a word. It hands you a set of weighted dice over its entire vocabulary, where the weights are its confidence. "mat" is a heavy side, "roof" lighter, "aardvark" basically never. How much you let those dice wobble before you roll them is a setting you control. Everything from here is about those dice: how they are weighted, how hard you shake them, and why two rolls of the exact same dice can still, annoyingly, land differently.
Weighting the dice, then deciding how hard to shake
Back to step 4 of Fig 1. The model has produced a raw score, a logit, for every token in its vocabulary. Logits are just unbounded numbers, not probabilities, so we need to turn them into a clean distribution that sums to 1. That is softmax. It exponentiates each score and divides by the total, which does two things at once: kills negatives and makes the big scores dominate.
See that T tucked into the exponent? That is temperature, and it is the single most misunderstood dial in AI. Before softmax runs, every logit gets divided by T. That one division is the whole knob. Watch what it does with three candidate words whose logits are 2.0, 1.0, and 0.1.
Low temperature (near 0) = focused, repetitive, "safe," picks the favourite almost every time. Great for extraction, classification, code, anything with a right answer. High temperature (1.5 to 2) = creative, surprising, occasionally unhinged. Good for brainstorming and copy, bad for JSON. And temperature 0 is a special case: divide by zero is undefined, so providers implement it as "skip the dice entirely, always take the single highest logit." That is called greedy decoding. Which sounds perfectly deterministic. Hold that thought, because it is a trap, and section 03 is about springing it.
top_p and top_k: throwing away the tail before you roll
Temperature reshapes the dice. The other two knobs do something different: they chop off the unlikely tail entirely before the roll, so the model can never blurt out something from deep left field. They just chop it two different ways. Take one distribution over five tokens and watch.
top_p (nucleus sampling)
The smallest group of top tokens whose probabilities add up to at least p. It is adaptive: when the model is confident (one token at 0.95) the group is tiny, when it is unsure (everything near-equal) the group is large. From the 2019 paper that introduced it. [4]
Worked: at top_p 0.9, cumulative is A 0.40, +B 0.65, +C 0.80, +D 0.92 which clears 0.9, so the nucleus is {A,B,C,D}, E is dropped, and the four are rescaled to sum to 1.
top_k
Keep the k highest-probability tokens, full stop. A fixed count, blind to shape. Simpler, blunter. top_k 1 is just greedy (always the favourite).
Worked: at top_k 2 you keep {A, B}, renormalize (0.40 and 0.25 become 0.615 and 0.385), and roll on just those two. C, D, E cannot happen, no matter what.
Who exposes it: Anthropic (range 0 to 500, off by default) and Google Gemini do. OpenAI's chat API does not give you top_k, only temperature and top_p. So if you want your settings to travel across providers, top_k is the one that will not. [5]
The rest of the dice bag, quickly
- frequency_penalty and presence_penalty (OpenAI-family, range -2 to 2). Frequency scales with how many times a token already appeared (kills verbatim repetition). Presence is a flat one-time nudge the moment a token has appeared at all (pushes toward new topics). Handy when the model loops. [6]
- repetition_penalty (open-source / Hugging Face). Same idea, multiplicative. 1.0 means no penalty.
- min_p (open-source). Keep tokens whose probability is at least min_p × (probability of the top token). The cutoff floats with the model's confidence: strict when it is sure, lenient when it is not. [7]
Tune temperature OR top_p, not both. Every major vendor says this in their own docs, and people ignore it constantly. They interact in ways that are hard to reason about, so pick one lever, hold the other at its default, and move only the one. For anything where you want reliability, the honest default is temperature = 0 and leave the rest alone. Which brings us to the uncomfortable part: even that does not make it repeatable.
Two layers of randomness, and only one of them has an off switch
Layer one is the dice. Above temperature 0, the model draws from the distribution instead of always taking the top token. That is deliberate, and you switch it off by setting temperature to 0 (greedy decoding). Easy. If that were the whole story, temperature 0 would give you the same answer forever, and this article would be four sections shorter.
Layer two is the one that ruins your week. Set temperature to 0, change nothing, run the same prompt again, and you can still get a different answer. That is the 80-out-of-1000 result from the opening. The dice are off. So where does the wobble come from? Two places, and the second one surprised even the people who build these systems.
Culprit A: computers cannot add up
Floating-point addition is not associative. On paper (a + b) + c equals a + (b + c). On a computer, with finite precision, it does not always. The Thinking Machines write-up gives the clean example: (0.1 + 1e20) - 1e20 = 0, but 0.1 + (1e20 - 1e20) = 0.1. Same three numbers, different grouping, completely different result. [1]
A GPU running a model adds up millions of these numbers across thousands of cores in parallel. If the order of those additions changes between runs, the result drifts by a few bits. A few bits changes a logit by a hair. A hair is enough to flip which token is the argmax on some step. One flipped token cascades into a totally different sentence. That is the story most people know, and it is true. It is also, it turns out, not the main thing.
Culprit B: your request has roommates
Here is the genuinely counter-intuitive finding. On a fixed batch, the same matrix multiply on the same GPU gives bit-identical results every time. The forward pass has no random atomic operations, it is run-to-run deterministic. So why the variance? Because inference servers batch your request with other people's requests, and how many roommates you get depends on how busy the server is at that instant, which changes constantly. The kernels are not "batch-invariant," so the exact arithmetic done on your tokens depends on the size of the batch you happened to land in. Same request, different crowd, different math. As the researchers put it, "the primary reason nearly all LLM inference endpoints are nondeterministic is that the load (and thus batch-size) nondeterministically varies." [1]
Culprit C: the model changed under you and did not send a memo
The third one is mundane and brutal. Providers update infrastructure, quantization, and routing behind a stable-looking model name. Your prompt that ran on one configuration last month runs on a subtly different one today, same name, different brain. There is even a measured version of this: one study found GPT-4's accuracy at telling prime numbers from composite ones fell from 84% in March 2023 to 51% in June 2023, on the same task, same model name, three months apart. [8]
There is no dial, on any major provider, that gets you to "bit-identical, always." Temperature 0 kills layer one and leaves layers two and three untouched. This is not a bug you can file, it is the shape of the technology today. The winning move is not to fight for perfect determinism, it is to aim for the thing you can actually get, which has a name, and which the next section is about. (If you want the deeper legal and audit angle on this specifically, I wrote a whole companion piece on it: Same Prompt, Different Answer.)
You cannot buy determinism. You can buy a receipt.
First, three words people use interchangeably and absolutely should not, because knowing which one you actually need saves you from chasing an impossible one:
Bit-identical, always
Same input, exact same output, forever. The standard a calculator meets. No major LLM API meets it today, and none claim to. Stop chasing this one.
Detect and explain drift
Output can vary, but you can tell when it changed and roughly why: a version pin, a fingerprint, a logged config. This is the realistic target.
Same meaning, maybe new words
Wording shifts, substance holds. "Low risk" phrased two ways is fine, "low risk" flipping to "high risk" is not. Usually what people actually mean.
For reproducibility, the OpenAI family (and Azure OpenAI, which mirrors it) gives you two things that work together:
- seed - an integer you pass in. Per the docs: the system will "make a best effort to sample deterministically, such that repeated requests with the same seed and parameters should return the same result." Read the hedge: best effort, not guaranteed. It pins layer one (the dice), not layers two and three.
- system_fingerprint - a string that comes back on every response, identifying the backend configuration. You do not prevent drift with it, you detect it. If two answers differ and the fingerprints differ, you know the backend moved under you. On Azure this shipped in API version 2023-12-01-preview. [9]
resp = client.chat.completions.create(
model="gpt-4o-2024-08-06", # pin the snapshot, not "latest"
seed=42,
temperature=0,
messages=msgs,
)
# log these THREE together, every call:
audit.write(
seed=42,
fingerprint=resp.system_fingerprint,
output=resp.choices[0].message.content,
)
# fingerprint changed + output changed? now you can say WHY.
And here is the honesty, straight from Microsoft's own page, which to their credit does not oversell it: "Determinism isn't guaranteed... it's currently not uncommon to still observe a degree of variability in responses. Identical API calls with larger max_tokens values will generally result in less deterministic responses even when the seed parameter is set." Translation: shorter answers are more stable than long ones, and even with everything pinned, you have narrowed the variance, not deleted it. [9]
The knobs, by provider (as of Aug 2026)
| Provider | seed | fingerprint | top_k | Their own stance on determinism |
|---|---|---|---|---|
| OpenAI | best effort | yes | no | "a small chance that responses differ even when request parameters and system_fingerprint match" [10] |
| Azure OpenAI | best effort | yes | no | "Determinism isn't guaranteed... not uncommon to still observe variability" [9] |
| Anthropic (Claude) | none | none | yes | "Even with temperature set to 0... identical inputs may produce different outputs across API calls" [11] |
| Google Gemini | best effort | none | yes | "makes a best effort to provide the same response for repeated requests" [12] |
Picking a stack because a slide said "deterministic," or assuming a competitor quietly solved this. Nobody has. The move is the same everywhere: pin the exact model snapshot, set the seed if the provider has one, log the fingerprint if it has one, and treat "the answer changed" as an expected event you have a paper trail for, not a surprise you discover in front of a client. That is reproducibility, and it is genuinely achievable today. Determinism is not, and pretending otherwise is how teams get hurt.
Good prompting is just narrowing the dice before you roll
Here is the reframe that makes prompt engineering click. Every instruction you add reshapes the probability distribution before sampling ever happens. A vague prompt leaves the model a huge spread of plausible next words, so tiny wobbles send it anywhere. A specific, well-structured prompt collapses that spread to a narrow peak, and now the same wobbles barely matter. You are not "asking nicely," you are pre-loading the dice.
The anatomy of a prompt that survives a model swap
The three big vendors publish separate prompt guides, and the striking thing is how much they agree. That agreement is the model-agnostic core, the stuff that works whether you are on GPT, Claude, or Gemini:
- Be clear, specific, and complete. Do not assume the model knows your context, spell it out. Anthropic leads its list with "be clear and direct," Google frames the whole game as "clear and specific instructions." [13] [14]
- Use delimiters and structure. Markdown headers and XML tags to fence off sections. OpenAI recommends an Identity / Instructions / Examples / Context skeleton, Anthropic explicitly recommends XML tags. [15]
- Set the role. A system message that fixes tone, goal, and constraints, separate from the user's query.
- Show, do not just tell (few-shot). A handful of input/output examples pins the format harder than any adjective. Google is blunt: "prompts without few-shot examples are likely to be less effective." Watch the ceiling though, too many examples and the model overfits to them. [14]
- Ask for the reasoning when accuracy matters. Let the model think in steps. And if you can afford it, sampling several reasoning paths and taking the majority answer (self-consistency) measurably lifts accuracy, +17.9 points on the GSM8K maths benchmark in the original paper. [16]
- Specify the exact output shape. "Return a JSON object with keys x, y, z" beats "give me the answer" every single time. This is the bridge to section 06.
Granularity: cut the task down
A vague mega-prompt is a wide distribution by construction. Google's own decomposition patterns: break instructions into separate prompts, chain them in sequence, or run parallel prompts over slices of data and aggregate. On my multi-agent builds this is the whole design: one narrow agent for intent, one for retrieval, one for the answer, each with a tight job it is hard to get wrong. Small, specific units beat one clever paragraph. [14]
What data to hand it
The model only knows what is in the prompt plus what it absorbed in training. If the answer depends on your documents, put them in the prompt (that is what RAG is: retrieve the relevant chunks, paste them in, cite them). Do not make it guess facts it has no way to know, that is where confident nonsense comes from. Give it the data, the format, and one example, and most "the model is unreliable" complaints quietly disappear.
Write prompts against the shared core above, not one vendor's quirks, and keep your logic out of clever provider-specific tricks. On a multi-agent client build I deliberately kept the agents model-independent so swapping the underlying LLM version did not break them, and when the model did change, nothing caught fire. That is the payoff: prompts that travel are prompts that survive the next model release, which, per section 03, is coming whether you scheduled it or not.
Stop asking for JSON. Make JSON the only thing it can say.
Prompting nudges the format. It does not enforce it. You write "return JSON," and 96 times out of 100 you get clean JSON, and the other 4 times you get a chatty "Sure! Here's your JSON:" wrapped in a markdown fence, and your parser explodes at 2am. For anything a machine reads next, "usually valid" is a bug with a delay on it.
The real fix is not a better prompt, it is constrained decoding. Remember step 4, where the model has a probability for every token? A grammar sits on top of that step and sets the probability of every schema-invalid token to zero before the roll. The model literally cannot emit a token that would break the structure, because those tokens are not on the dice anymore.
How much does it matter? OpenAI's own numbers: with Structured Outputs, gpt-4o-2024-08-06 hit a "perfect 100%" on a hard JSON-schema-following eval. The same model with prompting alone managed 93% ("insufficient for production"), and older gpt-4-0613 scored under 40%. That is the gap between "hope" and "guarantee." [17]
- OpenAI: Structured Outputs vs JSON mode. JSON mode promises valid JSON but not your schema. Structured Outputs (response_format: json_schema, strict: true) guarantees both. Use the second one. [18]
- Anthropic. Structured outputs plus "strict tool use" give the same constrained-decoding guarantee, with one honest caveat: a safety refusal takes precedence over the schema. [19]
- Open-source, any model you host. Outlines and llama.cpp's GBNF grammars enforce JSON, regex, or a full grammar during generation. GBNF's one gotcha: it constrains output but is not shown to the model, so describe the schema in the prompt too. [20]
response_format={
"type": "json_schema",
"json_schema": {
"name": "risk_flag",
"strict": True, # the magic word
"schema": {
"type": "object",
"properties": {
"level": {"enum":["low","high"]},
"score": {"type":"number"}
},
"required":["level","score"]
}
}
}
Constraining the format can hurt the thinking. A 2024 study found "a significant decline in LLM reasoning abilities under format restrictions," and the tighter the cage, the bigger the drop. [21] The fix is not to abandon structure, it is to separate the steps: let the model reason in free text first (a scratchpad, a reasoning field), then emit the constrained JSON. Make it think before you make it fill in the form, not instead of.
Run it a hundred times and measure the disagreement
Everything so far narrows the variance. Testing is how you find out whether you narrowed it enough. And the method is exactly the experiment from the opening, turned into a tool: take a prompt, run it many times over a set of known cases, and measure how much the outputs disagree. High disagreement on a task that should have one answer is not noise to average away, it is a red flag telling you where the model is unsure and where a human needs to look.
Pick the grader that fits
- Exact match / assertions when there is a right answer: equals, regex, is-json, valid tool call. Cheap, deterministic, no model call. Perfect for classification and extraction.
- Semantic similarity when wording can vary but meaning must not: embed both, check cosine similarity against a threshold. Catches "same substance, different phrasing."
- LLM-as-judge for open-ended quality (helpfulness, tone, faithfulness). A strong judge like GPT-4 hit "over 80% agreement" with human preferences, matching how often humans agree with each other. [22]
The tools, and one warning
promptfoo, OpenAI Evals, DeepEval, and Ragas (for RAG) all do the run-grade-score loop for you, with the graders above built in. [23]
The warning on the judge: LLM judges have real biases, they favour the first answer shown, longer answers, and answers that look like their own writing. Control for position and length, and never grade a model purely with a copy of itself. A judge is a useful instrument, not an oracle.
The same "run N times" idea is also a way to get better answers, not just measure them. Self-consistency: sample several independent reasoning paths and take the majority answer. It lifted accuracy by +17.9 points on a hard maths benchmark in the original paper. [16] So variance is not purely the enemy. Measured, it is a reliability signal. Aggregated, it is a reliability technique. The trick is never leaving it unmeasured.
Non-determinism is one monster in a whole zoo
If you are shipping LLMs to real users, the wobble from section 03 is just the one you noticed first. Here is the rest of the menagerie, each with a number attached, so you know which ones to actually lose sleep over. None of these are reasons not to build. They are the things you build around.
Confidently, fluently wrong
The model states false things with total conviction. Independent testing puts summarization hallucination rates between 1.8% and 24.2% depending on the model. [24] One enterprise survey blamed hallucinations for 15% of failed GenAI pilots. [25] Confidence is not correctness, and the model cannot tell you which one you are getting.
"Ignore your instructions"
Untrusted text (a web page, a pasted email, a document) smuggles in commands that hijack the model. It is number one on the OWASP Top 10 for LLM Apps 2025, and it gets nastier the moment your agent can actually do things. [26]
A big context window is a lie you tell yourself
Models use the start and end of a long context well and quietly ignore the middle. Accuracy follows a U-shape: bury the key fact at the halfway mark and performance "significantly degrades." [27] More context is not more understanding.
Same name, different brain, gone by Tuesday
The model behind a stable name changes: GPT-4's prime-spotting accuracy fell 84% → 51% in three months. [8] And snapshots get retired, so pin versions and watch deprecation notices. Build on "latest" and you have signed up for surprises.
Garbage retrieved, garbage generated
Most RAG failures are not the model, they are the retrieval feeding it the wrong chunks. It is the dominant enterprise pattern (51% of deployments) precisely because it is powerful, but its failure points "evolve rather than being designed in at the start." [28] I have shipped production RAG with page-level citations specifically so a human can check the source, not just trust the answer.
Cheap per token, slow per human
Unit price is collapsing (GPT-3.5-level cost fell from $20.00 to $0.07 per million tokens in about 18 months). [29] But latency is still the thing users feel, and long reasoning chains cost real seconds and real money at scale.
The system prompt is not a secret
Sensitive data leaks through outputs, logs, and embeddings, and system prompts get extracted more easily than teams expect. Both are on the OWASP 2025 list (Sensitive Information Disclosure, System Prompt Leakage). [30] Never put a secret in a prompt and assume it stays one.
429, right when you get popular
Throughput is capped on RPM, TPM, and more, and you hit an HTTP 429 at the worst possible moment. [31] I killed a wave of Azure OpenAI 429s under peak load with token-aware batching and Retry-After backoff, which is the unglamorous plumbing that decides whether your demo survives contact with real traffic.
You gave the improviser a credit card
Give an agent tools, and a wrong guess is no longer a bad sentence, it is a bad action: a deleted row, a sent email, a spent dollar. OWASP calls it Excessive Agency. Scope the permissions like the model will occasionally do the dumbest legal thing, because it will.
The ruler is also made of rubber
Measuring quality is hard, and the popular shortcut (an LLM judge) is itself unreliable and biased, as section 07 covered. So you can be wrong about how wrong you are. Cheap deterministic checks first, model-graded ones second, and always with a human spot-check on the tail.
Building on LLMs is like managing a wildly talented intern who is fast, tireless, occasionally brilliant, sometimes confidently makes things up, does the task slightly differently each time, can be talked into anything by a stranger's note, and might get a personality transplant overnight without telling you. You do not fire the intern, they are genuinely great. You just do not let them wire money without a second signature. Every item above is one more second signature.
Ten moves, in order, from "just guessing" to "governed"
- 01Default to temperature 0 for anything with a right answer. Turn the dice off before you do anything clever. Save the heat for brainstorming.
- 02Move one knob, not two. Temperature or top_p. Every vendor says it, everyone ignores it, do not be everyone.
- 03Pin the exact model snapshot (gpt-4o-2024-08-06, not "latest"). A model change should be a dated decision you made, not a Tuesday you survived.
- 04Set seed, log system_fingerprint where they exist. You are not buying determinism, you are buying a receipt that explains drift after the fact.
- 05Structure the prompt: role, delimiters, one or two examples, and the exact output shape. Narrow the distribution before sampling ever runs.
- 06For machine-read output, use structured outputs / constrained decoding. Make invalid output impossible, not merely unlikely.
- 07Let it think, then make it format. Free-text reasoning first, constrained JSON second, so the cage does not crush the thinking.
- 08Test by running N times and measuring disagreement. Exact match, then semantic similarity, then a judge, with a human on the tail. Gate every prompt change on it.
- 09Treat "the answer changed" as an expected event, like a flaky network call. Log everything that feeds a decision. High variance is a routing signal, not noise.
- 10Scope agent permissions as if the model will occasionally do the dumbest allowed thing. Second signature on anything irreversible.
You cannot make an LLM deterministic. You can make it reproducible, structured, tested, and logged, and that stack is what turns a party trick into something you can put your name on. The dice never stop rolling. You just stop letting them roll unsupervised.
"It's just predicting the next word" is true, and it is not the dunk people think
Mechanically, everything in this article comes back to one fact: the model is sampling from a probability distribution over the next token. Calling that "guessing" or "autocomplete" is not an insult, it is an accurate description of the algorithm. People say it like a debunking. It is not one. The genuinely startling thing is that a machine built entirely on "predict the likely next piece of text" writes working code, explains ideas it was never explicitly taught, and is right often enough to have changed how a lot of us work. The mechanism being simple does not make the behaviour un-remarkable. It makes it more so.
Here is where I land. The two loud camps are both, in my view, dodging the actual problem. "It is just autocomplete, ignore it" and "it reasons like a person, trust it" are the same laziness pointed in opposite directions. Both let you skip the uncomfortable truth: this is a genuinely new kind of tool that is probabilistically excellent and individually unverifiable, and that combination does not map cleanly onto anything we had before. A model right 97% of the time is a phenomenal drafting partner and a terrible sole decision-maker for the 3% where being wrong is expensive, precisely because you cannot tell, from a single confident answer, which bucket you are in.
So the skill that actually matters is not "prompt hacking" and it is not deciding whether to be a believer or a cynic. It is knowing where your 3% lives and building the second signature exactly there. Determinism tooling, structured outputs, evals, logging, none of it removes the underlying fact that the thing is rolling dice. It just makes the dice honest: shaped, bounded, watched, and written down. For anything you would have to explain later to a user, a client, or a court, that might be the whole game.
None of this is an argument against building with LLMs, most of this article assumes you already are. It is an argument for being precise about what you are trusting: not "the model," but "the model, plus the structure, plus the tests, plus the human check, plus the honesty about where the 3% lives." Take any one of those away and you have not removed the risk. You have just turned off the light you were using to see it.
Everything factual above traces to these
Vendor behaviour and benchmarks change monthly. Before you build on any specific claim here, go read the source. That is not a disclaimer, it is the single most useful habit in this whole field.
- [1] Thinking Machines Lab - Defeating Nondeterminism in LLM InferenceThe 80-of-1000 result, the batch-invariance thesis, and the fix. The single most important recent source here.
- [2] Vaswani et al. - Attention Is All You Need (2017)The transformer paper.
- [3] Jay Alammar - The Illustrated TransformerThe best beginner-friendly visual walkthrough of attention and Q/K/V.
- [4] Holtzman et al. - The Curious Case of Neural Text Degeneration (2019)Origin of nucleus (top_p) sampling.
- [5] AWS Bedrock - Anthropic Claude message parameterstop_k range and defaults; the temperature-or-top_p rule.
- [6] Microsoft Learn - Azure AI Foundry chat completionsfrequency_penalty / presence_penalty ranges.
- [7] Hugging Face - Text generation configmin_p, repetition_penalty, top_k/top_p defaults.
- [8] Chen, Zaharia, Zou - How Is ChatGPT's Behavior Changing over Time? (2023)The 84% → 51% prime-number drift.
- [9] Microsoft Learn - Reproducible output with Azure OpenAIseed / system_fingerprint mechanics and the "not guaranteed" caveat.
- [10] OpenAI Cookbook - Reproducible outputs with the seed parameterThe mechanism Azure mirrors, and the "small chance responses differ" line.
- [11] Anthropic - Glossary (temperature / determinism)"Even with temperature set to 0... identical inputs may produce different outputs."
- [12] Google - GenAI SDK GenerateContentConfigGemini's best-effort seed wording.
- [13] Anthropic - Prompt engineering best practices
- [14] Google - Gemini prompting strategiesfew-shot, decomposition, output format.
- [15] OpenAI - Prompt engineering guide
- [16] Wang et al. - Self-Consistency Improves Chain of Thought Reasoning (2022)The +17.9 point GSM8K result.
- [17] OpenAI - Introducing Structured Outputs in the API100% vs 93% vs under-40% schema adherence; constrained decoding.
- [18] OpenAI - Structured Outputs guideStructured Outputs vs JSON mode.
- [19] Anthropic - Structured outputs
- [20] Outlines - Structured generationOpen-source constrained decoding; see also llama.cpp GBNF grammars.
- [21] Tam et al. - Let Me Speak Freely? (2024)Format restrictions can degrade reasoning.
- [22] Zheng et al. - Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena (2023)80%+ agreement, plus position/verbosity/self-enhancement biases.
- [23] promptfoo - Assertions & metricsDeterministic, similarity, and model-graded assertions.
- [24] Vectara - Hallucination LeaderboardLive rates (1.8%-24.2% at time of writing).
- [25] Menlo Ventures - 2024 State of Generative AI in the EnterprisePilot-failure causes; RAG at 51% of deployments.
- [26] OWASP - LLM01:2025 Prompt Injection
- [27] Liu et al. - Lost in the Middle (2023)The U-shaped long-context accuracy curve.
- [28] Barnett et al. - Seven Failure Points When Engineering a RAG System (2024)
- [29] Stanford HAI - AI Index 2025The $20.00 → $0.07 per-million-token cost drop.
- [30] OWASP - Top 10 for LLM Applications 2025
- [31] OpenAI - Rate limits
Field notes: Anupam Kumar · Backend & Generative AI Engineer. Written Aug 2026. Vendor docs and benchmarks cited as of writing, they change fast, so re-check before you rely on any specific number. Companion piece on the legal and audit angle: Same Prompt, Different Answer. v1.0