Set Thinking Budgets

Su Suriya Ravichandran Updated 10 Nov 2026
7 min read ·Lesson 152 of 200

Controlling How Hard Claude Works

Lesson 151 showed how to turn thinking on. This lesson covers the dial that controls how much effort goes into a response. On current models, that dial is the effort parameter. On older models, and in the legacy configuration, the dial was a token budget for reasoning. Understanding both matters, because existing code and older examples still use budgets, and the migration from one to the other changes the behavior of every request.

The main idea is simple. More effort or a larger budget usually produces more careful reasoning, at a higher cost and with more latency. Less effort is faster and cheaper, and it can be the right choice for simple tasks. The skill is matching the setting to the task, and checking that the match holds with measurements rather than assumptions.


The Effort Parameter

Effort is set in output_config, not inside the thinking object:

response = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=4096,
    thinking={"type": "adaptive"},
    output_config={"effort": "high"},
    messages=[...],
)

The documentation describes effort levels that range from the lowest, which minimizes thinking and skips it for simple tasks, through to higher levels that allow deeper reasoning. Medium is the default on Claude Opus 5.5 and Claude Haiku 5.5. Not every level is accepted by every model, and the top levels are limited to specific models, so the effort documentation's support table is the authority for any given model.

Effort is more than a thinking setting. The documentation notes that it applies to all output tokens: text, tool calls, and thinking. A low effort therefore produces terser text, and fewer and simpler tool calls, not only less reasoning. That breadth is useful, and it is also a reason to test the effect on the whole response rather than on its reasoning alone.


Effort Works Through Adaptive Thinking

The effort level is a steering signal, not a hard limit. Claude decides how much to think for each request, and the level shifts that decision. A simple question at high effort may still receive a short answer, and a hard question at low effort may still receive careful reasoning when the model judges it necessary. The level sets the default disposition, and the model adjusts within it.

This is different from a budget, which is a ceiling on reasoning tokens. A budget says "reason up to this many tokens". Effort says "work this hard, and decide how much reasoning that needs".


The Legacy Budget

On models that still accept manual extended thinking, the configuration sets an explicit reasoning budget:

thinking={"type": "enabled", "budget_tokens": 10000}

budget_tokens sets the target for reasoning tokens, and it must be at least 1024 and less than max_tokens, unless interleaved thinking is in use. It is deprecated on some models and not accepted on others (Lesson 151 lists which). The budget is a maximum that the model aims to use, not a guarantee that it will use all of it.

Budgets have one practical advantage: they are predictable. A budget of 10,000 bounds the reasoning cost of a request. Adaptive thinking is less predictable in cost, because the model decides how much to think, which is why cost budgets belong in monitoring (Lessons 125 and 126) rather than in the configuration alone.


Migrating from a Budget to an Effort

The documentation gives a direct mapping for migration. Remove budget_tokens, set thinking to adaptive, and choose an effort level that approximates the old depth. The example below turns a legacy budget into a proposed effort level, using a simple band table as a starting point:

"""thinking_budgets.py: move from manual budgets to effort levels, and pick effort per task."""

BUDGET_BANDS = [          # (upper bound on the old budget in tokens, proposed effort)
    (2048, "low"),
    (8192, "medium"),
    (16000, "high"),
    (float("inf"), "max"),
]

TASK_EFFORT = {
    "classify": "low",
    "extract": "low",
    "summarize": "medium",
    "draft": "medium",
    "analyze": "high",
    "debug": "high",
    "plan": "high",
}


def budget_to_effort(budget: int) -> str:
    """A starting point for migration only. Confirm each choice by measuring quality and cost."""
    for upper, effort in BUDGET_BANDS:
        if budget <= upper:
            return effort
    return "max"


def effort_for_task(task: str) -> str:
    return TASK_EFFORT.get(task, "medium")


if __name__ == "__main__":
    for budget in [1024, 4096, 10000, 32000]:
        print(f"budget {budget} -> effort {budget_to_effort(budget)}")
    for task in ["classify", "summarize", "debug", "poem"]:
        print(f"task {task} -> effort {effort_for_task(task)}")

Output:

budget 1024 -> effort low
budget 4096 -> effort medium
budget 10000 -> effort high
budget 32000 -> effort max
task classify -> effort low
task summarize -> effort medium
task debug -> effort high
task poem -> effort medium

How It Works

  1. The bands turn a number into a category. A budget of 10,000 falls in the band up to 16,000, so it maps to high. The documentation's own migration example uses high for a legacy budget of 10,000, which agrees with this table.
  2. The task table sets effort by purpose. Classification and extraction are routine, so they run at low effort. Analysis and debugging benefit from more reasoning, so they run at high.
  3. Unknown tasks fall back to medium, which is the default on the current flagship model, so a new task is not silently set to an extreme.
  4. The mapping is a starting point, not a conversion. A budget and an effort level are not equivalent measures, so the table must be checked against real results.

Measuring the Effect

Choosing an effort level is an empirical question. The right test runs the same set of representative tasks at two or three levels, and compares three things: the quality of the answers, the cost of the tokens, and the latency. The quality measure depends on the task, and it may be a rubric, a set of expected labels (Lesson 110), or a human review of a sample.

A measurement harness can be as small as a loop that records the usage fields for each run, so cost comparisons come from data. The harness below records usage for one prompt at each effort level, using a function that stands in for the API call so the structure can be shown offline:

"""effort_trials.py: record cost for the same prompt at several effort levels."""

from dataclasses import dataclass


@dataclass
class Trial:
    effort: str
    input_tokens: int
    output_tokens: int


def run_trials(call, prompt: str, levels: list[str]) -> list[Trial]:
    trials = []
    for level in levels:
        usage = call(prompt, level)          # Returns the response's usage fields
        trials.append(Trial(level, usage["input_tokens"], usage["output_tokens"]))
    return trials


def fake_call(prompt: str, level: str) -> dict:
    output = {"low": 180, "medium": 520, "high": 1400}[level]
    return {"input_tokens": 90, "output_tokens": output}


if __name__ == "__main__":
    for trial in run_trials(fake_call, "Plan a three-day study schedule.", ["low", "medium", "high"]):
        print(f"{trial.effort:<6} input={trial.input_tokens} output={trial.output_tokens}")

Output:

low    input=90 output=180
medium input=90 output=520
high   input=90 output=1400

How It Works

  1. run_trials() runs one prompt at several levels, and keeps the usage from each run. The same prompt makes the comparison fair.
  2. The usage fields are the cost record. Output tokens rise sharply from low to high in this example, which is typical, and the input tokens stay the same because the prompt is the same.
  3. The fake call stands in for the API. The real call would pass the level as output_config={"effort": level} and read response.usage.
  4. Quality is measured separately, on the same outputs. A harness that records only cost can make a cheap setting look good when its answers are poor.

Choosing the Level

A workable policy starts with the lowest level that meets the quality bar on the representative tasks, and raises it only when a measured gap appears. Routine work such as classification, extraction, and formatting rarely needs more. Work where a wrong answer is expensive, such as analysis, code review, or planning, often justifies a higher level, because the extra reasoning reduces errors that are costly to fix later.

The cost of a higher level is paid on every request, so the decision should be revisited as prices and models change. Lesson 126 described how to estimate the break-even for caching, and the same discipline applies to effort: a setting that adds output tokens on every call needs to earn its place.


Summary

Effort is the current control for how hard Claude works, and it applies to text, tool calls, and thinking alike. A budget is the legacy control, a ceiling on reasoning that is predictable but deprecated on newer models. Migration maps budgets to effort levels, with the mapping treated as a starting point to be measured. Choosing a level is an empirical decision, made by comparing quality and cost across representative tasks. The next lesson turns to the thinking blocks themselves, which carry the reasoning these settings produce.

0 Comments

Reviewed before they appear

No comments yet.

Anthropic SDK Using Python
Anthropic SDK Intro Claude API Intro SDK Requirements Install Anthropic SDK Upgrade the SDK Create an API Key Set API Keys Use Environment Variables Import Anthropic Create a Client Send Your First Message Choose a Claude Model Set Max Tokens Read a Response Print Response Text Understand Message Blocks Use System Prompts Send User Messages Set Temperature Handle Stop Reasons Messages API Intro Create Messages Use Message Roles Send Conversation History Continue a Conversation Build a Chat Loop Reset Chat History Use Multiple Turns Inspect Message IDs Read Usage Data Write Clear Prompts Set a System Prompt Add Context to Prompts Use Prompt Templates Add Examples to Prompts Request JSON Output Use XML Tags Control Response Length Improve Prompt Quality Avoid Prompt Injection Read Text Blocks Read Content Blocks Check Stop Reasons Count Input Tokens Count Output Tokens Count Tokens Before Sending Limit Output Tokens Use Stop Sequences Read Request IDs Parse API Responses Streaming Intro Stream Text Output Use Stream Events Use Stream Helpers Get Final Messages Stream Async Responses Handle Stream Errors Build a Live Chat Stream Long Responses Close Streams Safely Build a Chatbot Keep Chat History Manage Chat Sessions Add Chat Memory Build a CLI Chatbot Build a Web Chatbot Use Flask with Claude Use FastAPI with Claude Return JSON from an API Add Chat Error Messages Async SDK Intro Create an Async Client Send Async Messages Use asyncio Run Parallel Requests Stream Async Text Handle Async Errors Set Async Timeouts Close Async Clients Choose Sync or Async Tool Use Intro Define a Tool Create Tool Schemas Send Tools to Claude Read Tool Calls Run Python Tools Return Tool Results Continue After Tool Use Control Tool Choice Handle Tool Errors Use the beta_tool Decorator Create a Tool Runner Add Tool Docstrings Validate Tool Inputs Use Multiple Tools Limit Tool Calls Log Tool Activity Build a Weather Tool Build a Search Tool Build a Calculator Tool Structured Output Intro Request JSON Data Validate JSON Output Use Pydantic Models Handle Invalid JSON Extract Fields from Text Generate Typed Data Use Image URLs Send Base64 Images Read PDFs with Claude Use Output Schemas Build Data Extraction Test Structured Responses Send Image Inputs Upload Files Use File IDs Process Text Files Summarize Documents Extract Table Data Handle File Errors Read Cache Usage Reduce Repeated Input Costs Debug Cache Misses Prompt Caching Intro Enable Prompt Caching Cache System Prompts Cache Long Context Test Cache Performance Use Cache Breakpoints Cache Best Practices Message Batches Intro Create a Message Batch Set Batch IDs Check Batch Status List Message Batches Get Batch Results Process Batch Outputs Handle Failed Batch Items Use Batches for Bulk Tasks Compare Batches and Requests Handle API Errors Handle Rate Limits Retry Failed Requests Set Request Timeouts Handle Network Errors Use Exponential Backoff Build Reliable API Calls Use Extended Thinking Log API Errors Track Request IDs Paginate API Results Set Thinking Budgets Read Thinking Blocks Use Tool Search Use Web Search Tools Use Computer Tools Use Citations Use Token Counting Read Raw Responses Set Custom Headers Use Claude on AWS Bedrock Use Claude on Vertex AI Use Claude on Microsoft Foundry Configure Cloud Credentials Choose a Cloud Provider Compare API Integrations Configure Provider Clients Handle Cloud Errors Use Cloud SDK Extras Deploy Cloud Apps Protect API Keys Use a .env File Ignore Secrets in Git Validate User Input Reduce Prompt Injection Limit Tool Permissions Avoid Unsafe Tool Calls Sanitize Tool Results Set Usage Limits Review Security Logs Test SDK Functions Mock API Responses Test Error Handling Test Streaming Code Measure Token Usage Track API Costs Configure Logging Deploy with Docker Deploy a FastAPI App Monitor Production Apps Build a Claude Chatbot Build a Text Summarizer Build a Code Explainer Build a JSON Extractor Build a PDF Assistant Build a Streaming Chat App Build a Tool-Calling Agent Build a Batch Processor Build a Research Assistant Build a Claude API Dashboard
Ask about this post
AI Ask about this post

Ask questions about Set Thinking Budgets and get answers drawn from it.

Signed-in readers only.