Set Temperature
Important: current status (November 2026)
The
temperatureparameter, along withtop_pandtop_k, has been removed in Claude API version2026-10-21, which is the version used by Anthropic Python SDK 2.0.0 and later. Requests that send these parameters are rejected, even at their default values. This lesson explains what temperature is, why you will still see it in older code and tutorials, what happens if you use it today, and how to achieve the same goals with current techniques.
What Temperature Is
When a language model writes a response, it produces text one token at a time. At each step it calculates a probability for every possible next token, and then samples one of them.
Temperature is a setting that changes how that sampling behaves:
| Temperature | Effect on sampling | Typical result |
|---|---|---|
| Low (for example, 0.0–0.3) | Strongly favors the most likely tokens | Focused, conservative, more repeatable wording |
| Default (1.0) | Samples according to the model's probabilities | Natural, varied responses |
| Higher | Gives less likely tokens more chance | More varied, sometimes less coherent |
In the Claude API, temperature accepted values from 0.0 to 1.0, with a default of 1.0.
Two related parameters controlled sampling in other ways:
| Parameter | What it did |
|---|---|
top_p (nucleus sampling) | Sampled only from the smallest set of tokens whose combined probability reached p |
top_k | Sampled only from the k most likely tokens |
All three were sampling parameters: they adjusted randomness in token selection, not the model's knowledge or reasoning.
A Common Misconception
Even when it was supported, temperature=0 did not guarantee identical output on every run. It made responses much more consistent, but not perfectly deterministic. Code that relied on exact repeatability was always fragile.
Current Status
Starting with API version 2026-10-21:
- A Messages API request (or Message Batch request) that includes
temperature,top_p, ortop_kreturns a400error, even if the value equals the default. - The SDK's 2.x releases no longer have these parameters in
messages.create(). - Requests are sampled with the defaults, including a temperature of 1.0. That is the same behavior as an older request that simply left the parameters out.
| Model generation | Effect of the change |
|---|---|
| Claude 4.7 and later, including all current models | No behavior change. These models already accepted only the default values. |
| Older models that accepted other values (for example, Claude Sonnet 4.6) | Requests that lowered temperature for more consistent output lose that effect. No other setting replaces it. |
If a workload truly depends on non-default sampling on an older model, the documented option is to keep that request on the previous API version (2023-06-01) by staying on SDK 1.x for it, as described in Lesson 5. For all current models, the parameters have no role.
What Happens If You Use It Today
With SDK 2.x
from anthropic import Anthropic
client = Anthropic()
message = client.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
temperature=0.2, # This parameter no longer exists in SDK 2.x
messages=[{"role": "user", "content": "Summarize the benefits of unit testing."}],
)
The method has no temperature parameter, so the call fails before any request is sent. Python raises a TypeError about an unexpected keyword argument, and type checkers flag the line in your editor.
Forcing It Through extra_body
The SDK lets you add raw fields to a request with extra_body:
message = client.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Summarize the benefits of unit testing."}],
extra_body={"temperature": 0.2},
)
The request reaches the API, which rejects it with a 400 error (anthropic.BadRequestError). The parameter is removed in the API itself, not just hidden by the SDK.
Recognizing Legacy Code
You will still find code like this in older articles, repositories, and Stack Overflow answers:
# Legacy pattern: SDK 1.x / API version 2023-06-01
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
temperature=0.0,
messages=[...],
)
When you see temperature, top_p, or top_k in Claude code, recognize it as written for the older API version. Remove these parameters when moving the code to SDK 2.x and current models.
Achieving the Same Goals Today
Developers used temperature for two opposite goals: more consistency and more variety. Both can be achieved with current techniques, which are often more reliable than temperature ever was.
Goal 1: Consistent, Predictable Output
| Technique | How it helps | Lessons |
|---|---|---|
| Precise instructions | Leaves less room for variation in content, length, and wording | 31, 38 |
| A fixed output format | Constrains what the answer can look like | 36, 37 |
| Examples in the prompt | Shows exactly the style and structure you expect | 35 |
| Structured outputs | Makes responses follow a defined schema | 101–110 |
| Validation with retry | Accepts only answers that meet your rules | This lesson |
| Caching results | Returns the stored answer for identical inputs, which gives truly identical output | This lesson |
The last technique deserves attention. If your application needs the same answer every time for the same input, for example a classification shown to users, store the first result and reuse it. That provides exact repeatability, which no sampling setting could guarantee.
Goal 2: Variety and Creativity
| Technique | How it helps |
|---|---|
| Ask for several distinct options in one request | "Give 5 different titles, each with a different tone." Claude deliberately varies them, which is more controlled than random sampling. |
| Explicit style direction | "Playful", "formal", "use a metaphor from cooking"… Directed variety is more useful than random variety. |
| Varying the prompt | Change the angle, audience, or constraint between requests to explore different directions. |
Default sampling already produces natural variation between runs. For brainstorming, explicitly asking for diverse options usually gives better results than relying on randomness alone.
What effort Does and Does Not Do
The effort setting (Lessons 13 and 151–153) controls how much the model thinks before answering. It affects depth, latency, and cost. It is not a randomness control and does not replace temperature.
Practical Example: Consistency Without Temperature
A classic use of temperature=0 was classification, where the same input should always get the same label. This example achieves consistency with current techniques: a precise instruction, validation of the answer, and caching of results.
"""consistent_classifier.py: stable classification without sampling parameters."""
from functools import lru_cache
from anthropic import Anthropic
from anthropic.types import Message
client = Anthropic()
LABELS = ("bug", "feature_request", "question", "billing")
SYSTEM_PROMPT = f"""You classify customer support tickets.
Reply with exactly one label from this list and nothing else: {", ".join(LABELS)}.
Rules:
- bug: something is broken or behaves incorrectly.
- feature_request: asks for new functionality.
- question: asks how to do something that already exists.
- billing: payments, invoices, refunds, or plans."""
def get_text(message: Message) -> str:
return "".join(block.text for block in message.content if block.type == "text")
def request_label(ticket: str) -> str:
message = client.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
output_config={"effort": "low"},
system=SYSTEM_PROMPT,
messages=[{"role": "user", "content": f"<ticket>\n{ticket}\n</ticket>"}],
)
return get_text(message).strip().lower()
@lru_cache(maxsize=10_000)
def classify(ticket: str, attempts: int = 2) -> str:
"""Return a validated label; identical tickets always get the same answer."""
for _ in range(attempts):
label = request_label(ticket)
if label in LABELS:
return label
return "needs_review"
if __name__ == "__main__":
tickets = [
"The export button crashes the app on Android.",
"Could you add dark mode?",
"How do I change my profile picture?",
"I was charged twice this month.",
"The export button crashes the app on Android.", # Repeated input
]
for ticket in tickets:
print(f"{classify(ticket):<16} {ticket}")
print(classify.cache_info())
Example output:
bug The export button crashes the app on Android.
feature_request Could you add dark mode?
question How do I change my profile picture?
billing I was charged twice this month.
bug The export button crashes the app on Android.
CacheInfo(hits=1, misses=4, maxsize=10000, currsize=4)
How It Works
- The system prompt defines a closed set of labels with a rule for each. Clear definitions are what make the classification consistent: Claude is not choosing words, it is applying rules.
<ticket>tags separate the ticket text from instructions (Lesson 18).output_config={"effort": "low"}reduces thinking for this simple task, saving latency and tokens. It does not affect randomness.- Validation:
if label in LABELSaccepts only exact, known labels. An unexpected answer, such as extra words, triggers one retry. If that also fails, the ticket is routed to"needs_review"instead of storing an invalid label. @lru_cachestores results by ticket text. The repeated ticket is answered from the cache, with no second API call. It is guaranteed to receive the same label, and costs nothing.cache_info()confirms one hit.
Note:
lru_cachelives in memory and is cleared when the program stops. In production, store results in a database or a cache such as Redis, keyed by a hash of the input. For even stricter output shapes, use structured outputs (Lessons 101–110).
Practical Example: Variety Without Temperature
For creative tasks, ask for distinct options directly:
prompt = """Suggest 5 titles for a beginner tutorial about Python virtual environments.
Make each title use a different style:
1. straightforward, 2. question, 3. playful, 4. benefit-focused, 5. step-count ("in N steps").
Return only the numbered list."""
message = client.messages.create(
model="claude-opus-5-5",
max_tokens=2048,
messages=[{"role": "user", "content": prompt}],
)
titles = [
line.split(".", 1)[1].strip()
for line in get_text(message).splitlines()
if line.strip()[:1].isdigit() and "." in line
]
print(titles)
Example output:
['Python Virtual Environments Explained', 'Why Does Every Python Project Need Its Own Environment?', 'Stop Fighting Your Dependencies: A Friendly Guide to venv', 'Keep Projects Clean and Conflict-Free with Virtual Environments', 'Set Up a Python Virtual Environment in 4 Steps']
How the parsing works: splitlines() splits the answer into lines. Lines that start with a digit and contain a period are list items. line.split(".", 1)[1] keeps everything after the first period, which removes the 1., 2., and so on. One request produces five deliberately different results, with more control over the kind of variety than random sampling could offer.
Common Mistakes
Copying temperature from Older Code
What happens: TypeError with SDK 2.x, or a 400 BadRequestError when sent through extra_body or raw HTTP with API version 2026-10-21.
Fix: remove temperature, top_p, and top_k. The scanning script in Lesson 5 finds them across a project.
Expecting Any Setting to Make Output Fully Deterministic
What happens: tests that compare exact output strings fail randomly.
Fix: test properties instead of exact text: the label is valid, the JSON parses, required fields exist, the length is within limits. For truly identical answers, cache the results.
Using effort as a Replacement for Temperature
What happens: expecting low effort to make answers less random.
Fix: effort controls thinking depth, cost, and latency. Use instructions, examples, structured outputs, and validation for consistency.
Relying on Randomness for Variety
What happens: repeated requests return similar ideas.
Fix: ask for multiple distinct options with explicit style directions in one request.
Best Practices
- Do not use sampling parameters with SDK 2.x or current models. Treat them as legacy.
- Get consistency from clear instructions, examples, fixed formats, and structured outputs.
- Validate model output and retry or escalate when it does not meet your rules.
- Cache results when the same input must always produce the same answer.
- Get variety by asking for it explicitly and specifying how the options should differ.
- Write tests that check properties, not exact wording.
- Check the migration guide when you find legacy parameters in code you maintain (Lesson 5).
Exercise
- Try calling
messages.create()withtemperature=0.5using SDK 2.x. What error do you get, and at what point does it occur? - Run
consistent_classifier.pyand add three new tickets of your own, including one that is ambiguous. How is the ambiguous one labeled? - Change
classify()so that, when both attempts return an invalid label, it logs the invalid answers before returning"needs_review". - Write a prompt that produces 3 different product descriptions for a reusable water bottle: one for athletes, one for students, and one for office workers.
Step 1: Python raises a TypeError for the unexpected keyword argument temperature when the method is called, before any network request is made. SDK 2.x has no such parameter.
Step 3:
import logging
logger = logging.getLogger(__name__)
@lru_cache(maxsize=10_000)
def classify(ticket: str, attempts: int = 2) -> str:
invalid_answers = []
for _ in range(attempts):
label = request_label(ticket)
if label in LABELS:
return label
invalid_answers.append(label)
logger.warning("Unclassifiable ticket %r; model answers: %r", ticket[:80], invalid_answers)
return "needs_review"
ticket[:80] limits how much of the ticket is written to the log, which keeps logs readable and avoids storing long, possibly sensitive text. %r shows the exact answers, including stray whitespace or punctuation, which helps you improve the prompt.