Request parameters, in full
A precise reference to every parameter of client.messages.create: what each does, its sane default, when you'd actually change it, and the gotchas — don't set both temperature and top_p, max_tokens must fit the model, forcing a tool changes stop_reason, extended thinking runs at temperature=1. Defaults are good; change them deliberately.
Learning objectives
- Name every parameter of
client.messages.createand say what each one does. - Recite the three required parameters and give sane defaults for the rest.
- Shape
messagesandsystemcorrectly — roles, content blocks, cache_control. - Tune sampling deliberately:
temperaturevstop_p(never both),top_k,stop_sequences. - Force, allow, or forbid tools with
tool_choice, and control parallel calls. - Turn on extended thinking correctly and know the gotchas (thinking needs
temperature=1). - Recognise the operational knobs —
stream,metadata,service_tier,betas— and when to touch them.
1 · One endpoint, many knobs essential
Almost everything you do with Claude goes through a single call: client.messages.create(...). Tools, vision, streaming, thinking, structured output — none of them are separate APIs. They are all parameters on this one call. Learn the parameters once and the whole surface opens up.
This lesson is a reference. Earlier lessons showed you the parameters you needed in the moment (a model here, a max_tokens there); this one lays every parameter out in a single place, with its default, what it actually changes, and when — if ever — you should touch it. Read it once end to end, then come back to the big table whenever you're unsure.
There are only three required parameters — model, max_tokens, and messages. Everything else has a sensible default, and the single most common mistake is over-configuring: setting knobs you don't understand and making the model worse. The mental model to carry through this lesson: the defaults are good — change them deliberately, one at a time, with a reason.
2 · The parameter groups essential
The parameters fall into five natural groups. Hold this shape in your head and every specific parameter has a home:
This picture is the whole parameter surface of messages.create on one line. Every specific parameter in the lesson lives in exactly one of these five boxes — so when a parameter confuses you, first ask which box it's in.
- Required —
model,max_tokens,messages. The minimum any call needs; there are no defaults for these three. - Prompt —
systemand the content blocks insidemessages. This is where your actual instructions and the conversation live. - Sampling —
temperature,top_p,top_k,stop_sequences. These tune how the model picks each next token, not what you ask. - Tools —
toolsandtool_choice. Present only when you want the model to call functions. - Operational —
stream,thinking,metadata,service_tier,betas. These govern delivery, reasoning, and billing rather than the words of the answer.
In short: The big table in §3 is organised by these same five groups. Read left to right — most calls set the first two boxes, occasionally reach into sampling or tools, and leave the operational box to a shared house wrapper.
Required is the minimum any call needs. Prompt shaping is where your actual instructions and conversation live. Sampling tunes how the model picks the next token. Tools let the model call functions. Operational knobs govern delivery, reasoning, and billing rather than the content of the answer. The reference table in §3 uses these same groups.
3 · The full reference table essential
Every parameter of messages.create, its default, what it does, and when you'd actually reach for it. Bookmark this section.
| Parameter | Default | What it does | When to touch it |
|---|---|---|---|
model (required) | — | Which Claude answers (e.g. claude-opus-4-8). | Always. Pick by task: Opus for hard work, Haiku for cheap/fast. |
max_tokens (required) | — | Hard cap on output tokens. The model stops here even mid-sentence. | Always set it. Big enough to finish; must fit the model's output limit. |
messages (required) | — | The conversation: a list of {role, content}. Roles alternate user/assistant. | Always. This is the prompt (plus any history you carry). |
system | None | Top-level instructions / persona. A string, or a list of text blocks (for cache_control). | Almost always — it's where role, rules, and tools context go. |
temperature | 1.0 | Randomness of token choice. 0 ≈ deterministic-ish, 1 = full range. | Lower (0–0.3) for extraction/classification; leave at 1 for creative work. |
top_p | 1.0 | Nucleus sampling: consider only the top tokens summing to p probability. | Rarely. An alternative to temperature — never set both. |
top_k | unset | Only sample from the k most likely tokens. | Rarely — an advanced sampling lever; most workloads never need it. |
stop_sequences | [] | Strings that force the model to stop when generated (the string is not included). | When you need a hard delimiter — end of a section, a custom terminator. |
stream | False | Return tokens incrementally over SSE instead of one final message. | Interactive UIs, and any large-max_tokens call (avoids timeouts). |
tools | None | List of tool definitions (name, description, JSON-schema input) the model may call. | When the model needs to act — call functions, search, run code. |
tool_choice | {"type":"auto"} | Whether the model may / must / must-not use a tool. | To force a specific tool, force some tool, or forbid tools for one turn. |
thinking | off | Extended (adaptive) reasoning before the answer: {"type":"adaptive"}. | Hard multi-step problems. Pair with output_config.effort. |
metadata | None | Opaque request metadata, notably {"user_id": ...} for abuse tracking. | In production, to attach a stable per-end-user id (hashed, not PII). |
service_tier | provider default | Requests a priority/standard/batch service level (verify current values in docs). | Rarely — when you have a priority-tier arrangement and want to steer to it. |
betas / extra_headers | none | Opt into beta features via client.beta.messages + a beta flag string. | Only when a specific beta feature needs it (files, caching betas, etc.). |
output_config | None | Structured-output format and the effort dial for thinking depth. | For guaranteed JSON (format) or to tune reasoning cost (effort). |
4 · Required + prompt shaping — model, max_tokens, messages, system essential
These are the parameters you set on essentially every call. model and max_tokens are scalars; the interesting shape is in messages and system.
messages is a list of turns. Each turn has a role ("user" or "assistant") and content. Content can be a plain string, or — when you need images, documents, or tool results — a list of content blocks ({"type": "text", ...}, {"type": "image", ...}, and so on). system is separate from messages: it's top-level instructions, given as a string, or as a list of text blocks when you want to attach cache_control to cache a large stable prompt.
required.py# needs: pip install anthropic + ANTHROPIC_API_KEY; makes a real API call
import anthropic
client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY from the environment
resp = client.messages.create(
model="claude-opus-4-8", # REQUIRED: which model answers
max_tokens=1024, # REQUIRED: hard cap on output tokens
system=[ # top-level instructions (list form → cacheable)
{
"type": "text",
"text": "You are a terse DevOps assistant. Answer in one sentence.",
"cache_control": {"type": "ephemeral"}, # cache this stable prefix
}
],
messages=[ # REQUIRED: the conversation
{"role": "user", "content": "What does 'kubectl get pods' show?"},
# content can also be a LIST of blocks, e.g. text + an image:
# {"role": "user", "content": [
# {"type": "text", "text": "What's in this screenshot?"},
# {"type": "image", "source": {"type": "url", "url": "https://.../shot.png"}},
# ]},
],
)
# content is a LIST of blocks — read only the text ones
print(next((b.text for b in resp.content if b.type == "text"), ""))
This is the smallest useful call — the three required parameters plus a system prompt. It needs pip install anthropic and an ANTHROPIC_API_KEY, and it makes a live request. Everything else in the lesson is an addition to this shape.
client = anthropic.Anthropic()builds the client and reads your key from the environment, so the key never appears in the code.modelandmax_tokensare the two required scalars: which Claude answers, and the hard ceiling on how many tokens it may produce.systemis given here as a list of text blocks (not a plain string) so acache_controlmarker can be attached — that caches the stable prompt prefix. A plain string works too when you don't need caching.messagesis the conversation. Each turn is{role, content}; content is a string, or (see the commented lines) a list of blocks when you add an image or document.
What the output means: The one-sentence answer prints. Because resp.content is a list of blocks, we pull the first text block rather than assuming a string.
Try this: Swap the system list for a plain string (system="...") and re-run — it still works; you just lose the ability to cache that prefix.
max_tokens is the output ceiling, and it can't exceed the model's max output (e.g. 128K on Opus 4.8, 64K on Sonnet/Haiku). Set it too low and the reply is truncated mid-thought with stop_reason == "max_tokens"; set it very high on a non-streaming call and the SDK may refuse it to avoid an HTTP timeout — stream instead.5 · Recipe — temperature vs top_p (and stop_sequences) intermediate
Sampling parameters change how the model chooses each next token. The two you'll meet most are temperature and top_p, and the golden rule is: set at most one of them. They're two different ways to control the same thing (randomness), and steering with both at once is confusing and unnecessary — Anthropic recommends adjusting temperature and leaving top_p alone.
temperature ranges 0 to 1. Near 0 the model almost always picks the most likely token — good for extraction, classification, and anything you want reproducible-ish. At 1 (the default) it samples across the full distribution — good for brainstorming and prose. stop_sequences is unrelated to randomness: it's a list of strings that, when the model generates one, cut the response off immediately (the stop string itself is not returned). When that happens, stop_reason is "stop_sequence" and stop_sequence tells you which one fired.
sampling.py# needs: pip install anthropic + ANTHROPIC_API_KEY; makes a real API call
import anthropic
client = anthropic.Anthropic()
resp = client.messages.create(
model="claude-opus-4-8",
max_tokens=256,
temperature=0, # near-deterministic: extraction / classification
# top_p=0.9, # DON'T also set this — pick ONE knob, not both
stop_sequences=["\n\nEND"], # stop the moment the model writes this marker
system="Extract the error code. Output only the code, then write \n\nEND.",
messages=[{"role": "user",
"content": "Log: FATAL 2026-09-06 db timeout code=E5521 retry=3"}],
)
text = next((b.text for b in resp.content if b.type == "text"), "")
print("text:", repr(text))
print("stop_reason:", resp.stop_reason) # "stop_sequence" if the marker fired
print("stop_sequence:", resp.stop_sequence) # which sequence caused the stop
This call is tuned for a deterministic extraction task — pull one error code out of a log line. It shows the two sampling ideas you'll use most: a low temperature, and a stop_sequence that ends the reply at a marker.
temperature=0makes the model almost always pick the single most likely token — exactly what you want for extraction, where creativity is a bug, not a feature.- The commented
top_p=0.9line is a trap on purpose:temperatureandtop_pare two controls for the same thing, so you set one, never both. stop_sequences=["\n\nEND"]tells the model to stop the instant it writes that marker. The marker itself is not included in the returned text.- After the call,
resp.stop_reasonreports why it stopped andresp.stop_sequencesays which marker fired — that's how you confirm the stop sequence did the work rather than a natural end or the token cap.
What the output means: The extracted code prints, then stop_reason ("stop_sequence" if the marker fired) and the specific stop_sequence that caused it.
Try this: Delete the stop_sequences argument and re-run — the model finishes on its own and stop_reason becomes "end_turn" instead.
temperature for almost everything; reach for top_p only if you have a specific reason, and then drop temperature.stop_sequences is an input (strings you supply that end generation). stop_reason is an output field telling you why the model stopped: "end_turn" (finished naturally), "max_tokens" (hit your cap), "stop_sequence" (hit one of yours), "tool_use", or "refusal". Always read stop_reason before trusting the content.6 · Recipe — tools & tool_choice (force / allow / forbid) intermediate
tools is a list of tool definitions (each a name, a description, and a JSON-schema for its input). tool_choice controls whether and which tool the model uses. It has four modes:
| tool_choice | Meaning |
|---|---|
{"type": "auto"} | Default. Model decides whether to use a tool at all. |
{"type": "any"} | Model must use one of the tools (its choice which). |
{"type": "tool", "name": "X"} | Model must use tool X — forced. |
{"type": "none"} | Model may not use any tool this turn. |
Any of these can also carry "disable_parallel_tool_use": true to force at most one tool call per turn (by default the model may request several at once). Here is a forced tool call — useful when you want structured extraction and you know the model should always fill in this one tool:
tool_choice.py# needs: pip install anthropic + ANTHROPIC_API_KEY; makes a real API call
import anthropic
client = anthropic.Anthropic()
record_incident = {
"name": "record_incident",
"description": "Record a parsed incident from an alert line.",
"input_schema": {
"type": "object",
"properties": {
"service": {"type": "string"},
"severity": {"type": "string", "enum": ["low", "high", "critical"]},
},
"required": ["service", "severity"],
},
}
resp = client.messages.create(
model="claude-opus-4-8",
max_tokens=512,
tools=[record_incident],
# FORCE this exact tool + at most one call this turn:
tool_choice={"type": "tool", "name": "record_incident",
"disable_parallel_tool_use": True},
messages=[{"role": "user",
"content": "CRITICAL checkout-api is down, 500s everywhere"}],
)
# a forced tool means stop_reason == "tool_use" and a tool_use block with the args
tool_use = next((b for b in resp.content if b.type == "tool_use"), None)
print(resp.stop_reason) # "tool_use"
print(tool_use.name, tool_use.input) # record_incident {'service': 'checkout-api', ...}
This forces the model to call one specific tool — a clean way to get guaranteed structured data out of free text. The magic is entirely in tool_choice; the tool definition itself is an ordinary name + description + JSON schema.
tools=[record_incident]makes one tool available. Itsinput_schemadeclares the fields (service,severitywith anenum) the model must fill in.tool_choice={"type": "tool", "name": "record_incident"}forces that exact tool — the model won't answer in prose, it will emit atool_useblock with the parsed arguments."disable_parallel_tool_use": Truecaps it at a single tool call this turn (without it, the model may request several at once).- Because a tool was forced,
resp.stop_reasonis"tool_use"and you read the answer from thetool_useblock's.input— not from a text block.
What the output means: stop_reason is "tool_use", and the tool's .input is a dict like {'service': 'checkout-api', 'severity': 'critical'} — structured, not prose.
Try this: Change tool_choice to {"type": "auto"} and re-run. Now the model decides whether to call the tool — on a clear incident it usually still does, but it may answer in text instead.
{"type":"tool"} or {"type":"any"} the model will emit a tool_use block and stop_reason becomes "tool_use" — it won't answer in prose. Don't force a tool and then look for a text answer; read the tool_use block instead. Forcing is great for guaranteed structured extraction, wrong for open-ended chat.7 · Recipe — extended thinking advanced
Extended thinking lets the model reason privately before it answers — a real lever on hard, multi-step problems (planning, tricky debugging, math). On current models you turn it on with adaptive thinking, where the model itself decides how much to think, and you tune the overall depth with output_config.effort.
Two accuracy points that trip people up. First, on Opus 4.8 / 4.7 the old {"type": "enabled", "budget_tokens": N} form is gone — it returns a 400. Use {"type": "adaptive"}. Second, thinking runs at temperature=1 — you cannot combine extended thinking with a lowered temperature (or with top_p/top_k); those sampling params are incompatible with thinking. So the recipe is: turn thinking on, leave sampling alone.
thinking.py# needs: pip install anthropic + ANTHROPIC_API_KEY; makes a real API call
import anthropic
client = anthropic.Anthropic()
resp = client.messages.create(
model="claude-opus-4-8",
max_tokens=8000,
thinking={"type": "adaptive", "display": "summarized"}, # model decides how much
output_config={"effort": "high"}, # low | medium | high | max — the depth dial
# NOTE: do NOT also set temperature / top_p / top_k here — thinking runs at
# temperature=1 and rejects those sampling params.
messages=[{"role": "user",
"content": "Plan a zero-downtime Postgres major-version upgrade."}],
)
for block in resp.content:
if block.type == "thinking":
print("[thinking]", block.thinking[:80], "...")
elif block.type == "text":
print("[answer]", block.text)
This turns on extended thinking — private reasoning before the answer — for a genuinely hard planning task. Two things are load-bearing here: the adaptive type, and the absence of any sampling parameter.
thinking={"type": "adaptive"}is the current way to enable thinking on Opus 4.8 / 4.7 — the model decides how much to think. The old{"type": "enabled", "budget_tokens": N}form is gone and returns a 400.output_config={"effort": "high"}is the depth dial —lowthroughmax. This is how you control reasoning cost, not temperature.- There is deliberately no
temperature/top_p/top_khere: extended thinking always runs attemperature=1, and combining it with a custom sampling value is a 400. - The reply comes back as blocks — a
thinkingblock (the summarised reasoning) followed by thetextanswer — so the loop checksblock.typefor each.
What the output means: A truncated summary of the model's reasoning prints first (the thinking block), then the actual upgrade plan (the text block).
Try this: Lower effort to "low" and compare the depth of the plan against the token count in resp.usage — that's the quality-vs-cost trade thinking gives you.
output_config.effort, not with temperature. Extended thinking always runs at temperature=1; passing a custom temperature (or top_p/top_k) alongside it is a 400. Reach for thinking on genuinely hard problems — it costs more tokens and latency, so it's overkill for a lookup or a classification.8 · Validate before you send (this one runs offline) professional
Most parameter mistakes are catchable before you spend a request: both temperature and top_p set, a max_tokens that exceeds the model's output cap, or extended thinking combined with a non-default temperature. The helper below is pure stdlib — no API key, no network — and runs with a plain python file.py. It inspects a params dict and returns a list of warnings, so you can lint your call sites in a test.
validate_params.py# Catch the common messages.create mistakes locally, before spending a request.
# Model output caps as of this writing (verify current values in the docs):
MODEL_MAX_OUTPUT = {
"claude-opus-4-8": 128_000,
"claude-sonnet-4-6": 64_000,
"claude-haiku-4-5": 64_000,
}
def validate_params(params):
"""Return a list of human-readable warnings for a messages.create params dict.
Pure stdlib; does not call the API."""
warnings = []
model = params.get("model")
if not model:
warnings.append("model is REQUIRED and missing")
if "max_tokens" not in params:
warnings.append("max_tokens is REQUIRED and missing")
if not params.get("messages"):
warnings.append("messages is REQUIRED and empty/missing")
# don't set both temperature and top_p
if "temperature" in params and "top_p" in params:
warnings.append("both temperature and top_p set — pick ONE, not both")
# max_tokens must fit the model's output cap
cap = MODEL_MAX_OUTPUT.get(model)
mt = params.get("max_tokens")
if cap is not None and isinstance(mt, int) and mt > cap:
warnings.append(f"max_tokens={mt} exceeds {model} output cap of {cap}")
# extended thinking requires temperature == 1 (i.e. leave it unset)
thinking = params.get("thinking")
thinking_on = isinstance(thinking, dict) and thinking.get("type") in ("adaptive", "enabled")
if thinking_on and params.get("temperature", 1) != 1:
warnings.append("thinking is on but temperature != 1 — thinking runs at temperature=1")
if thinking_on and ("top_p" in params or "top_k" in params):
warnings.append("thinking is on but top_p/top_k set — incompatible with thinking")
return warnings
bad = {
"model": "claude-opus-4-8",
"max_tokens": 200_000, # over the 128K cap
"messages": [{"role": "user", "content": "hi"}],
"temperature": 0.2, # both temperature ...
"top_p": 0.9, # ... and top_p
"thinking": {"type": "adaptive"}, # thinking on, but temperature != 1
}
for w in validate_params(bad):
print("WARN:", w)
print("ok:", validate_params({
"model": "claude-opus-4-8", "max_tokens": 1024,
"messages": [{"role": "user", "content": "hi"}],
}))
WARN: both temperature and top_p set — pick ONE, not both
WARN: max_tokens=200000 exceeds claude-opus-4-8 output cap of 128000
WARN: thinking is on but temperature != 1 — thinking runs at temperature=1
WARN: thinking is on but top_p/top_k set — incompatible with thinking
ok: []
Unlike the four blocks above, this one is pure Python — no key, no network — so it runs with a plain python validate_params.py. It's a pre-flight linter: hand it a params dict and it lists the mistakes that would otherwise cost you a wasted (or 400'd) request.
- It first checks the three required parameters are present —
model,max_tokens,messages. - Then the classic mutual-exclusion bug: both
temperatureandtop_pset. Pick one. MODEL_MAX_OUTPUTholds each model's output cap, so it can flag amax_tokensthat exceeds what the model can actually produce.- Finally the thinking rules: if thinking is on,
temperaturemust be 1 andtop_p/top_kmust be unset — otherwise the real API 400s. The deliberately-brokenbaddict trips all of these at once.
What the output means: Four WARN: lines for the broken dict (both-sampling, over-cap, thinking+temp, thinking+top_p), then ok: [] for the clean one — an empty warning list means the params are safe to send.
Try this: Drop this into a unit test over your real call sites. Catching a bad params dict offline costs nothing; catching it as a 400 in production costs a request and a page.
validate_params into a unit test catches the whole class of "why did my request 400?" bugs without ever hitting the network. It's the cheapest possible check — no key, no tokens, no latency.9 · Tech-lead — operational knobs & a house policy tech-lead
The last group of parameters doesn't change what the model says — it changes how the request is delivered, tracked, and billed. A lead sets a house policy for these so every call site is consistent:
Operational parameter policy
streamfor anything interactive or large. Streaming isn't just UX — it's how you avoid HTTP timeouts on bigmax_tokenscalls. Policy: stream any user-facing response and any call withmax_tokensabove ~16K; use.get_final_message()to still get the assembled reply and usage.metadata.user_idon every production call. Attach a stable, hashed per-end-user identifier (never raw PII) so abuse and rate-limit issues can be traced to a tenant. It costs nothing and it's the field support will ask you for.thinking+effortby route, not globally. Turn thinking on for the hard routes (planning, debugging) and leave it off for lookups and classification. Standardise theeffortlevel per route so cost is predictable.service_tieronly if you have an arrangement. It requests a priority / standard / batch service level; the exact accepted values and behaviour evolve, so treat it as opt-in and verify the current options in the docs before wiring it in.betasdeliberately, never by default. Beta features live onclient.beta.messageswith an explicit flag string. Pin the flag, document why you need it, and plan to remove it when the feature goes GA — don't leave stale beta headers scattered across the codebase.- Centralise the defaults. Wrap
messages.createin one thin house function that fillsmodel, sanemax_tokens,metadata, and the streaming decision. Thenvalidate_paramsfrom §8 runs in that one place.
The through-line: the content parameters (messages, system, tools) are per-request and belong to the feature; the operational parameters (stream, metadata, service_tier, betas) are cross-cutting and belong to the platform. Put the second set behind one wrapper and every engineer inherits the house policy for free.
service_tier's accepted values and betas' flag strings change as features ship and graduate. This lesson deliberately doesn't hard-code them: when you reach for either, check the current Anthropic docs for the exact string rather than copying one that may have rotated.Exercise AP8.1 — Read the table, build the call
Context: Choosing parameters is a design decision, not a default-copy. A classification task wants a cheap model, a tiny output, and near-zero randomness — and every extra knob you add should earn its place.
Your task: Without looking at the recipes, write a messages.create call for a classification task (label a support ticket): pick the right model, a small max_tokens, the right temperature, and decide whether you need thinking. Justify each non-required parameter in one sentence and delete any you can't justify.
Requirements:
- Pick a fast, cheap model appropriate to a simple labeling task
- Set a small
max_tokensthat fits just the label - Choose a low/zero
temperaturefor stable, repeatable labels - Decide (and justify) that
thinkingis unnecessary for classification - Write a one-sentence justification per non-required parameter — and drop any that has none
💡 Hint: Start from the three required params and add a knob only when you can name the behavior it buys; classification rarely needs more than a low temperature.
Exercise AP8.2 — Extend the linter
Context: A linter is only as good as its coverage. Two more checks catch config mistakes that silently waste requests: a forced tool with no tools defined, and a huge output with streaming off.
Your task: Extend validate_params with two checks: (1) warn if tool_choice is {"type":"tool"} but tools is empty or missing, and (2) warn if max_tokens is very large (say > 16000) but stream is not True. Run it on a params dict that trips both.
Requirements:
- Add a check for a forced
tool_choicewith no usabletoolslist - Add a check for a large
max_tokens(> 16000) withoutstream=True - Preserve the linter's existing warnings — extend, don't replace
- Both new warnings are appended as clear strings to the returned list
- Demonstrate on a dict that trips both and confirm both warnings print
💡 Hint: Read tool_choice, tools, and stream defensively so a missing key is treated as absent, not an exception.
🪜 Practice ladder beginner → industry
Six graded exercises, easy to real-world. Try each before opening its solution.
Context: Every Claude call rests on three required fields; everything else is a knob you opt into. Knowing the minimum keeps you from cargo-culting parameters you don't understand.
Your task: Write a messages.create() call using only the three required parameters — model, max_tokens, and messages — and explain in a comment why system is optional.
Requirements:
- Pass exactly
model,max_tokens, andmessages— nothing else max_tokensis understood as a hard cap on the output lengthmessagesis a list of role/content dicts with a single user turn- A comment states that without
systemthe model falls back to its defaults - Print the reply's text from the first content block
💡 Hint: Reach for a current model id such as claude-sonnet-4-6; resist adding temperature or anything else until a task demands it.
Show solution
system is optional; the three required fields are the minimum to get a reply.
import anthropic
client = anthropic.Anthropic()
resp = client.messages.create(
model="claude-sonnet-4-6", # required
max_tokens=256, # required: hard cap on output
messages=[{"role": "user", "content": "Define idempotency."}], # required
)
# system is optional: with no role/rules given, the model uses its defaults.
print(resp.content[0].text)
Context: temperature and top_p are two different ways to shape randomness, and Anthropic's guidance is to tune only one. For deterministic extraction you want the temperature floor.
Your task: Show one call that lowers temperature for a deterministic extraction, plus a comment stating the rule about setting temperature and top_p together.
Requirements:
- Set
temperature=0for focused, repeatable output - A comment states you should not also set
top_p— pick one knob - Use
stop_sequencesto cut the output at a natural boundary - The task is a small, single extraction (one fact out of one sentence)
- Print both the text and the response's
stop_reason
💡 Hint: Tuning both sampling knobs at once fights itself — lower temperature alone, and let a stop_sequences entry end the reply cleanly.
Show solution
Tune one sampling knob at a time. Setting both temperature and top_p is discouraged; pick one.
import anthropic
client = anthropic.Anthropic()
resp = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=64,
temperature=0, # focused, repeatable; do NOT also set top_p
stop_sequences=["\n\n"], # stop at first blank line
messages=[{"role": "user", "content": "Extract the year: Founded in 1994."}],
)
print(resp.content[0].text, resp.stop_reason)
Context: When you force a tool, Claude's answer is no longer prose — the structured arguments arrive in a tool_use block and stop_reason flips to tool_use. Reading content[0].text then gets you nothing.
Your task: Define one tool and force it with tool_choice, then explain in a comment why you must read the tool_use block rather than content[0].text.
Requirements:
- Declare a tool with a
name,description, and aninput_schema - Force it via
tool_choice={"type": "tool", "name": ...} - Recognize that forcing a tool drives
stop_reasontotool_use - Scan
resp.contentfor the block whosetypeistool_useand read itsinput - A comment explains the arguments live in that block, not in a text block
💡 Hint: Never index content[0] blindly — iterate the blocks and branch on block.type so you pick up the arguments wherever they land.
Show solution
Forcing a tool sets stop_reason to "tool_use"; the arguments live in a tool_use block, not in text.
import anthropic
client = anthropic.Anthropic()
tools = [{
"name": "record_temp",
"description": "Record a temperature reading.",
"input_schema": {"type": "object",
"properties": {"celsius": {"type": "number"}},
"required": ["celsius"]},
}]
resp = client.messages.create(
model="claude-sonnet-4-6", max_tokens=256, tools=tools,
tool_choice={"type": "tool", "name": "record_temp"},
messages=[{"role": "user", "content": "It is 21 degrees C."}],
)
# stop_reason == 'tool_use' -> the args are in a tool_use block:
for block in resp.content:
if block.type == "tool_use":
print(block.input) # {'celsius': 21}
Context: Extended thinking lets Claude reason before answering, but it changes the rules: thinking runs at temperature 1 and rejects the sampling knobs. Depth is dialed with output_config.effort, not temperature.
Your task: Enable extended thinking and set the reasoning depth via output_config, and note the two constraints thinking imposes on sampling parameters.
Requirements:
- Turn thinking on with an adaptive
thinkingsetting ({"type": "adaptive"}) - Set reasoning depth through
output_configwith aneffortoflow|medium|high|max - Do not pass
temperature,top_p, ortop_k— a comment notes thinking runs at temperature 1 and rejects them (a 400) - Give
max_tokensreal headroom for a reasoning task - Print only the final
textblocks from the response
💡 Hint: Treat output_config.effort as the single dial for how hard the model thinks, and leave every sampling parameter unset while thinking is on.
Show solution
Thinking uses {"type": "adaptive"}; it runs at temperature 1 and rejects temperature/top_p/top_k. Depth is tuned with output_config.effort.
import anthropic
client = anthropic.Anthropic()
resp = client.messages.create(
model="claude-opus-4-8",
max_tokens=2048,
thinking={"type": "adaptive", "display": "summarized"},
output_config={"effort": "high"}, # low | medium | high | max
# NOTE: with thinking on, do NOT pass temperature/top_p/top_k -> 400.
messages=[{"role": "user", "content": "Prove sqrt(2) is irrational."}],
)
for b in resp.content:
if b.type == "text":
print(b.text)
Context: A bad parameter combination burns a request (and money) before you learn it was invalid. A small offline linter catches the classic mistakes at your boundary instead of at the API.
Your task: Implement validate_params(p, model_cap) that returns warnings for setting both temperature and top_p, for max_tokens above the model cap, and for thinking enabled together with a non-default temperature.
Requirements:
- Warn when both
temperatureandtop_pare present - Warn when
max_tokensexceeds the suppliedmodel_cap - Warn when
thinkingis on buttemperatureis not the required default of 1 - Also flag any missing required param (
model,max_tokens,messages) - Return a list of warning strings; run fully offline against a sample dict
💡 Hint: Read the params dict defensively with .get() so a missing key is just another warning rather than a KeyError.
Show solution
This is the lesson's linter: catch config mistakes before spending a request.
def validate_params(p, model_cap):
warns = []
if "temperature" in p and "top_p" in p:
warns.append("set temperature OR top_p, not both")
if p.get("max_tokens", 0) > model_cap:
warns.append(f"max_tokens {p['max_tokens']} > cap {model_cap}")
if p.get("thinking") and p.get("temperature", 1) != 1:
warns.append("thinking requires temperature=1 (drop it)")
for k in ("model", "max_tokens", "messages"):
if k not in p:
warns.append(f"missing required param: {k}")
return warns
bad = {"model": "claude-opus-4-8", "max_tokens": 200000,
"messages": [], "temperature": 0.3, "top_p": 0.9}
print(validate_params(bad, model_cap=128000))
Context: When a platform fronts every Claude call, per-team discipline doesn't scale — policy has to be enforced in one sanitizer that rewrites requests before they leave the building.
Your task: Enforce a house policy on incoming params: default max_tokens if unset, cap it at the model's limit, strip top_p when temperature is present, and require a metadata.user_id for abuse tracking, returning the sanitized params.
Requirements:
- Look the cap up per-model and clamp
max_tokensto it (defaulting it first when absent) - Drop
top_pwhenevertemperatureis set, so only one sampling knob survives - Reject the request (raise) if
metadata.user_idis missing - Return a copy — do not mutate the caller's dict
- Runs offline; demonstrate it on a params dict that trips every rule
💡 Hint: Copy the dict up front, then apply each rule as an independent transform so adding the next policy later is just one more step.
Show solution
A single sanitizer keeps every team inside the guardrails without hand-review.
CAPS = {"claude-opus-4-8": 128000, "claude-sonnet-4-6": 64000,
"claude-haiku-4-5": 64000}
def house_policy(p):
p = dict(p)
cap = CAPS.get(p["model"], 4096)
p.setdefault("max_tokens", 1024)
p["max_tokens"] = min(p["max_tokens"], cap) # never exceed the cap
if "temperature" in p:
p.pop("top_p", None) # one sampling knob
md = p.setdefault("metadata", {})
if "user_id" not in md:
raise ValueError("metadata.user_id required for abuse tracking")
return p
out = house_policy({"model": "claude-sonnet-4-6", "max_tokens": 999999,
"temperature": 0.2, "top_p": 0.8,
"metadata": {"user_id": "u-42"}})
print(out["max_tokens"], "top_p" in out) # 64000 False
✓ Checkpoint — you can move on when you can…
- Name the three required parameters and give a sane default for every other one.
- Shape
messages(roles + content blocks) andsystem(string or cacheable list) correctly. - Explain why you never set both
temperatureandtop_p. - Distinguish
stop_sequences(input) fromstop_reason(output). - Force, allow, and forbid tools with
tool_choice, and disable parallel calls. - Turn on adaptive thinking correctly and state why you can't also lower the temperature.
- Name the operational knobs (
stream,metadata,service_tier,betas) and a house policy for each.
Knowledge check check yourself
Why should you never set both temperature and top_p, and what do they each control?
Show answer
top_p does nucleus sampling over the top tokens summing to probability p), so setting both is contradictory and is a common source of 400 errors. Pick one sampling lever.What happens if max_tokens is set too low, and why can setting it very high on a non-streaming call also fail?
Show answer
stop_reason == "max_tokens". Set very high on a non-streaming call and the SDK may refuse to avoid an HTTP timeout on a long generation; the fix is to stream (stream=True) so tokens arrive incrementally.