Module 01 · Lesson 7 of 13Free
Sampling and temperature: controlling randomness
- Level
- Beginner
- Topic
- AI engineering fundamentals
- Reading time
- 12 min
When you finish this page you can
- Explain why the same prompt gives different wording on each run.
- Recognize the 400 error that temperature, top_p or top_k cause on Claude API version 2026-10-21, and fix the request.
- Get consistent output with a clear prompt and an example, structured outputs, effort and a stored result.
- Decide when a feature needs runs to differ, and set temperature on an open-weight model, where it still exists.
Builds on 2 lessons
Start with lesson 315 minVideo lesson · coming soon
Priya gets a different summary the second time
Deskora is a help desk app. A shop’s support team uses it to answer customer emails from one shared inbox. Each customer problem becomes a ticket: one customer’s whole conversation with the support team, message by message.
Priya is a support agent at a kitchenware shop that uses Deskora. She opens a 20-message ticket from Oskar, whose pressure cooker arrived without its lid. At the top sits a three-line summary. Tomás, the Deskora engineer who builds the AI features, is testing it, and his test page has a Regenerate button. Priya clicks it. The thread hasn’t changed, but the second summary uses different words.
Tomás asks a colleague. “Set temperature to 0,” the colleague says. “Then you get the same answer every time.” Tomás adds it to his request to Claude Haiku 5.5.
Tomás sends the summary request with temperature set to 0, on Claude API version 2026-10-21. What comes back?Show answer
This lesson answers one question: if Tomás can’t turn the randomness down, how does he give Priya summaries she can rely on?
The thread size and costs below are estimates for this lesson’s scenario.
The model draws each word from a spread
A model writes one token at a time; a token is a small piece of text, often part of a word. At each step, the model gives every possible next token a probability. Then one token is drawn from that spread, by chance, in proportion to those probabilities. A likely token wins most of the time, not every time. A summary of 80 tokens is 80 draws, so two runs almost always differ somewhere. Under the hood: how decoding picks each token shows the mechanics.
Here is one step from an example ticket in an earlier lesson:
Diagram not drawn yet: next-token-runs-diverge.svg
A tree from one shared start, “Tried: Daniel offered a”. Three branches leave it. Branch 1, 48%: “ refund” leads to “; Rosa wants size M instead.”, marked correct. Branch 2, 22%: “ full” leads to “ refund, but Rosa asked to exchange it for size M.”, marked correct, different words. Branch 3, 9%: “ replacement” leads to “ jacket in size M.”, marked wrong: Daniel offered a refund, not a replacement.
Most splits change the wording, not the facts. Now and then a pick changes a fact, and Priya can’t tell from one summary which kind she got. So different wording is expected. What remains is which settings shape the draw.
Three settings used to shape the draw
For years, model APIs let you shape the draw with three settings. Other providers’ APIs and the libraries that run open-weight models still have them.
| Setting | What it did |
|---|---|
temperature |
Stretched or flattened the spread before the draw. Below 1 the top token won more often; above 1 unlikely tokens won more often. |
top_k |
Kept only the k most likely tokens and drew from those. A top_k of 1 always took the top token. |
top_p |
Kept the smallest group of top tokens whose probabilities add up to p, such as 0.9. It cut off the unlikely tail, and kept fewer tokens when one was a clear favourite. |
Diagram not drawn yet: temperature-three-settings.svg
Three bar charts in a row for the same five tokens. T = 0.5: shipped 84%, been 11%, left 4%, arrived 0.2%, refund under 0.1%; one tall bar. T = 1: shipped 61%, been 22%, left 14%, arrived 3%, refund 0.4%. T = 2: shipped 42%, been 25%, left 20%, arrived 9%, refund 3%; the bars are much more even. Arrived is labelled wrong for this ticket in all three.
Temperature 0 was the classic advice for steady output. It made the top token win at every step. That gives the same words on every run, but not the right words: if the top token is wrong, it is wrong every time.
On Claude, none of the three is available any more.
On the Claude API, the request fails
On API version 2026-10-21, a request that sends temperature, top_p or top_k returns a 400 error, even at the parameter’s default value 1. The API version is the date in the anthropic-version header of every request. Without the three settings, every request samples with the provider’s defaults, including a temperature of 1.0 1.
Tomás’s Python code fails even earlier. The anthropic Python SDK 2.3.0 sends version 2026-10-21 and no longer has the three parameters 1. Passing one raises a Python TypeError before any request leaves his server.
Staying on the older version, 2023-06-01, doesn’t help him. Claude 4.7 and later models, Haiku 5.5 included, accept only the default values there too 1. The migration guide also says no other setting replaces a lowered temperature 1.
Diagram not drawn yet: sampling-claude-vs-open-model.svg
Two panels. Left panel, Claude API, version 2026-10-21: Deskora’s app server sends model, max_tokens, system and messages to the Claude Messages API. Inside the API, the model scores every token, then a sampling box labelled “provider’s defaults, temperature 1.0” picks one. A red note on the request arrow reads “temperature, top_p or top_k: 400 error”. Right panel, open-weight model on your own server: your code calls the Transformers generate() function, the model scores every token, and the sampling box is labelled “your settings: do_sample, temperature, top_p, top_k”.
Try it yourself
If you have a Claude API key, send the request Tomás sent and watch it fail. Readers without a key can skip this.
curl https://api.anthropic.com/v1/messages \
-s -w "\nstatus %{http_code}\n" \
-H "content-type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2026-10-21" \
-d '{"model": "claude-haiku-5-5", "max_tokens": 200, "temperature": 0, "messages": [{"role": "user", "content": "Summarize in one line: my parcel is late."}]}'An example of what comes back, with the status line that -w adds:
{
"type": "error",
"error": {"type": "invalid_request_error", "message": "..."},
"request_id": "req_..."
}
status 400Look for invalid_request_error and status 400. Now delete "temperature": 0, and send it again. The same call returns a message and status 200. The stays in your shell’s environment, never in the command. With max_tokens at 200, the working call costs at most about 200 × $0.50 per million = $0.0001 on Claude Haiku 5.5, as of October 2026, plus a few input tokens.
So Tomás can’t make the draw less random. He can make the right summary far more likely, and stop drawing again.
What Tomás controls instead
How does Tomás make the summary Priya sees stay the same?
Move summaries to an older model that still accepts a low temperature
It works only on older models that accept other values, and on the old API version. The feature is stuck there, and each run still draws, so repeats are alike but not identical.
Keep the Regenerate button and let agents click until a summary looks right
Every click is another draw and another bill, about $0.0006 for a typical ticket. Priya and a teammate can read two different summaries of the same thread.
Make one summary per change to the ticket, store it, and fix its shape with a clear prompt, an example and a schema
Everyone who opens the ticket reads the same stored words. When the thread changes, a new run keeps the same fields and, with a clear prompt, the same facts.
Why this choice
Priya doesn’t need identical words across runs. She needs the right facts in the same place, and the same summary her teammate sees. Storing one result gives her the second. The prompt and the schema give her the first.
This would change only if the feature moved to an open-weight model, as compared in Hosted and open-weight models. Then temperature exists again, but the stored result is still what makes two people see the same words.
These are the levers he has:
- A clear prompt with an example. The more exactly the prompt says what goes on each line, the more probability lands on the right tokens at each step. One worked example shows the format; Few-shot prompting goes further.
- Structured outputs. They constrain the reply to JSON that matches a schema you send 5, so every summary has the same three fields. Structured outputs teaches them.
- Effort. It sets how much the model thinks before it answers 6. It controls how much work, not how much randomness. Write it out, so a change of default can’t change the feature. Reasoning effort covers the tradeoff.
- Store the result. Make the summary once when the ticket changes, save it, and show the saved copy.
- Check over several runs. Run the same tickets several times and compare the facts, which Evals turns into a habit.
Here is the request without any sampling settings. It adds an example, a schema and an explicit effort to lesson 03’s call:
import json
import anthropic
client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY from the environment
SYSTEM = """You summarize support tickets for the agent who picks them up.
Fill three fields: the problem, what was tried, what the customer is waiting for.
Use only facts from the thread. One short sentence per field.
Example:
problem: Kettle arrived cracked.
tried: Agent sent a replacement on 3 May.
waiting_for: Tracking number for the replacement."""
SCHEMA: dict[str, object] = {
"type": "object",
"properties": {
"problem": {"type": "string"},
"tried": {"type": "string"},
"waiting_for": {"type": "string"},
},
"required": ["problem", "tried", "waiting_for"],
"additionalProperties": False,
}
def summarize(thread: str) -> dict[str, str]:
response = client.messages.create(
model="claude-haiku-5-5",
max_tokens=1024,
system=SYSTEM,
messages=[{"role": "user", "content": thread}],
output_config={
"effort": "medium", # Haiku 5.5's default, written out on purpose
"format": {"type": "json_schema", "schema": SCHEMA},
},
)
text = next(block.text for block in response.content if block.type == "text")
return json.loads(text)Deskora saves the result with the ticket and shows the saved copy. A Regenerate click would cost another call, about $0.0006 for a typical ticket on Claude Haiku 5.5, as of October 2026. That is lesson 03’s estimate: 4,150 input tokens × $0.10 per million + (300 assumed thinking + 80 summary tokens) × $0.50 per million ≈ $0.0006.
Diagram not drawn yet: summary-regenerate-vs-store.svg
Two panels. Left panel, regenerate on every view: Priya opens Oskar’s ticket and the app server calls the model, giving summary A. A teammate opens the same ticket a minute later, a second call gives summary B, with different wording. Two calls, about $0.0012. Right panel, generate once and store: a new message starts a background job, which makes one call and saves the summary in a summaries table, with the row labelled ticket, messages 1 to 20, prompt version. Priya and her teammate both read that row and see the same words. One call, about $0.0006.
Design: a ticket summary feature builds the background job and the stored row.
Variety helps drafts, not labels
Different runs are a problem for some tasks and the point of others.
| Task | Should runs differ? | How to get it |
|---|---|---|
| Ticket summary | No: same facts, same fields | Prompt with an example, schema, stored result |
| Ticket category: delivery, refund or damage | No | Schema with a fixed list of values, stored result |
| Order number from the thread | No | Schema, plus a code check that the number appears in the thread |
| Two alternative replies for Priya | Yes | Ask for two drafts that differ in a named way |
For alternatives, don’t rely on chance. Two calls with the same prompt may give two drafts that say the same thing in other words. Ask in one call for two drafts with a stated difference: one short and direct, one that explains the shop’s policy. A schema with two fields keeps them apart. The difference then comes from the prompt, and Priya always gets a real choice.
For labels, runs that disagree are a signal. If three runs on one ticket say delivery, refund, delivery, the ticket or the prompt is unclear. Tighten the category definitions with examples before picking a winner.
Open-weight models still have temperature
This section is about open-weight models, not Claude.
An open-weight model is one whose weights you download and run yourself. Sampling then runs in your own code, so the three settings are yours. In Hugging Face Transformers, they are arguments to generate() 3. It picks the top token every time unless sampling is turned on 2. Many models ship their own settings: Qwen2.5-0.5B-Instruct turns sampling on, with temperature 0.7, top_p 0.8 and top_k 20 4. Values you pass to generate() override those 3.
This script runs a small open-weight model on your own computer, twice at a low temperature and twice at a high one:
# pip install transformers==5.21.0 torch==2.15.0 (an open-weight model, not Claude)
from transformers import AutoModelForCausalLM, AutoTokenizer
MODEL_ID: str = "Qwen/Qwen2.5-0.5B-Instruct" # downloads about 1 GB the first time
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
model = AutoModelForCausalLM.from_pretrained(MODEL_ID)
messages: list[dict[str, str]] = [
{"role": "user", "content": "Write a one-line apology for a late parcel."}
]
inputs = tokenizer.apply_chat_template(
messages, add_generation_prompt=True, return_tensors="pt"
)
prompt_length: int = inputs["input_ids"].shape[1]
for temperature in (0.2, 1.2):
for run in (1, 2):
output = model.generate(
**inputs,
max_new_tokens=40,
do_sample=True,
temperature=temperature,
top_p=0.9,
top_k=50,
)
reply = tokenizer.decode(output[0][prompt_length:], skip_special_tokens=True)
print(f"temperature {temperature}, run {run}: {reply}")Look at the pairs. At 0.2, the two runs are usually close or identical. At 1.2, they usually differ more. It costs nothing per run beyond your own computer’s time. The same rule holds here: a low temperature makes runs alike, not right, so store results here too.
Where this shows up
- Design: an invoice data extractor: the same fields on every run come from a schema and validation, not from a setting.
- Design: a writing assistant inside an editor: offering several suggestions needs runs that differ, asked for on purpose.
Tomás’s request now carries a system prompt, an example and a 4,000-token thread. Context windows shows how much one call can hold, and what filling it costs.
Common mistakes
- Copying
temperature=0from an older tutorial into Claude code. On API version 2026-10-21 it fails, as a 400 or as aTypeErrorin the SDK. Remove it. - Treating new wording as a bug. Compare the facts and the fields, not the words.
- Calling the model on every view. Each view is a new draw and a new bill. Store the result.
- Expecting two calls to give two useful alternatives. Ask for the difference in the prompt.
Check your understanding
An old code sample sets top_p to 0.9 on a Claude call. Tomás’s project sends API version 2026-10-21 with curl. What happens, and what is the fix?Show answer
Priya and a teammate open the same ticket a minute apart and see differently worded summaries. Which part of the design is wrong?Show answer
Three runs of a category label on one ticket return delivery, refund, delivery. What should Tomás do?Show answer
On an open-weight model at temperature 0.2, a summary gets the same wrong order number on every run. Would raising the temperature to 1.0 fix it?Show answer
Summary
- The model draws each token from a spread of likely tokens, so the same prompt gives new words. Tomás gets steady summaries from a clear prompt with an example, a schema, an explicit effort and one stored result per ticket change.
temperature,top_pandtop_kused to shape the draw. Open-weight models and other providers still have them.- As of October 2026, Claude API version 2026-10-21 rejects all three with a 400, and the anthropic SDK 2.3.0 no longer has them. The provider samples with its defaults.
- Ask for variety in the prompt when you want alternatives. For labels and extraction, use a schema, store the result, and read disagreement between runs as an unclear prompt.
- On an open-weight model, set them in
generate(). A low temperature makes runs alike, not right.