Limit Output Tokens

Su Suriya Ravichandran Updated 10 Nov 2026
9 min read ·Lesson 47 of 200

Why Limit Output?

Output tokens are the most expensive part of a request (5× the input price on current models) and the main driver of latency (Lesson 45). Limiting output is therefore one of the most effective ways to control cost, speed, and predictability.

Lesson 13 introduced max_tokens, the hard ceiling on generated tokens. This lesson treats output limiting as a strategy with several tools, each controlling a different aspect:

ToolControlsMechanism
Prompt instructionsLength of the visible answerClaude plans a shorter (or longer) answer
effortAmount of thinkingFewer thinking tokens for simple tasks
Structured outputsShape of the outputOnly the fields you define are produced
Stop sequencesWhere generation endsStops at a marker you choose (Lesson 48)
max_tokensAbsolute maximumHard cutoff, as a safety net
Streaming cancellationWhen you stop readingYour code ends the stream early
Budgets in your applicationTotal spend per user or featureYour code refuses or downgrades requests

The key principle: shape the output with the first four tools, and use max_tokens as a safety net, not as the main control.


Layer 1: Ask for Less

The cheapest output is output Claude never plans to write. Specific length instructions (Lesson 38) reduce the visible answer:

messages = [{"role": "user", "content": "In 2 sentences, what is a Python virtual environment?"}]

Also remove requests for content you do not need. "Explain, give examples, list alternatives, and summarize" produces far more output than "Explain in 2 sentences."


Layer 2: Reduce Thinking with effort

Thinking tokens are often a large share of output (Lesson 45). For simple tasks, lower effort reduces them:

reply = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=1024,
    output_config={"effort": "low"},
    messages=[{"role": "user", "content": "Classify the sentiment of: 'Loved the course!'"}],
)
Task typeSuggested effort
Classification, extraction, short factual answerslow
General explanations and writingDefault (medium on Claude Opus 5.5)
Complex reasoning, difficult code, multi-step analysishigh

Layer 3: Constrain the Shape with Structured Outputs

When you need data, not prose, structured outputs (Lesson 36) limit the response to exactly the fields you define, with no explanations, introductions, or extra commentary:

from pydantic import BaseModel


class Sentiment(BaseModel):
    label: str
    confidence: float


result = client.messages.parse(
    model="claude-opus-5-5",
    max_tokens=1024,
    output_config={"effort": "low"},
    output_format=Sentiment,
    messages=[{"role": "user", "content": "Classify: 'Loved the course!'"}],
)

A JSON object with two fields is far shorter than a paragraph explaining the classification.


Layer 4: The Safety Net, max_tokens

max_tokens guarantees that no single response exceeds a known size, whatever the prompt or input. Set it from measured output sizes (Lesson 45): roughly 1.5–2× the p95 of output_tokens for the feature.

max_tokens too highmax_tokens too low
No cost when unused, since you pay only for tokens generatedTruncated or empty responses
A runaway response can be expensiveRetries cost more than the savings

Because you pay only for generated tokens, a slightly generous limit costs nothing in the normal case and protects against unusual cases. A limit that is too tight causes truncation and retries, which costs more.

Maximum Values

The maximum max_tokens for current models is 128,000 with the Messages API. With the Message Batches API, supported models can go up to 300,000 with a beta header (Lesson 13). Very large limits require streaming, because the SDK raises an error for non-streaming requests it expects to take longer than about 10 minutes.


Layer 5: Stopping a Stream Early

With streaming (Lessons 51–60), your code receives text as it is generated and can stop reading whenever it has enough, for example when a preview reaches a character limit, or when a user clicks "Stop":

collected = ""
with client.messages.stream(
    model="claude-opus-5-5",
    max_tokens=2048,
    messages=[{"role": "user", "content": "List 50 Python project ideas."}],
) as stream:
    for text in stream.text_stream:
        collected += text
        if len(collected) >= 500:
            break          # Leaving the 'with' block closes the stream

Leaving the with block closes the connection, and generation for that request ends. Tokens generated before the stream was closed are still billed. Early cancellation limits cost for open-ended requests, but it is not free. Lesson 60 covers closing streams safely.


Layer 6: Application Budgets

Per-request limits do not cap total spend. Applications that serve many users usually add budgets:

BudgetExample rule
Per user, per dayA user may use up to 200,000 output tokens per day
Per featureThe "explain code" feature's max_tokens is 2,048
Per conversationConversations are summarized when they exceed a size limit (Lesson 27)
Organization-wideSpend limits in the Claude Console

Lesson 179 (Set Usage Limits) implements user budgets. The practical example below combines per-feature limits with a simple per-user budget.


Practical Example: Per-Feature Output Profiles with a User Budget

This module defines an output profile for each feature (length instruction, effort, and max_tokens), applies it to every request, records output usage per user, and refuses requests that would exceed the user's daily output budget.

"""output_limits.py: per-feature output profiles and a simple per-user output budget."""

from collections import defaultdict
from dataclasses import dataclass

from anthropic import Anthropic

client = Anthropic()
MODEL = "claude-opus-5-5"


@dataclass(frozen=True)
class OutputProfile:
    instruction: str
    effort: str
    max_tokens: int


PROFILES = {
    "classify": OutputProfile("Reply with only the label.", "low", 1024),
    "quick_answer": OutputProfile("Answer in at most 3 sentences.", "low", 1024),
    "explain": OutputProfile("Explain in about 200 words with one short code example.", "medium", 3072),
}

DAILY_OUTPUT_BUDGET = 4_000          # Output tokens per user per day (small, for demonstration)
usage_today: dict[str, int] = defaultdict(int)


class BudgetExceeded(Exception):
    pass


def ask(user_id: str, feature: str, question: str) -> str:
    profile = PROFILES[feature]

    if usage_today[user_id] + profile.max_tokens > DAILY_OUTPUT_BUDGET:
        raise BudgetExceeded(f"{user_id} has used {usage_today[user_id]} of "
                             f"{DAILY_OUTPUT_BUDGET} output tokens today.")

    reply = client.messages.create(
        model=MODEL,
        max_tokens=profile.max_tokens,
        output_config={"effort": profile.effort},
        messages=[{"role": "user", "content": f"{question}\n\n{profile.instruction}"}],
    )
    usage_today[user_id] += reply.usage.output_tokens

    text = "".join(b.text for b in reply.content if b.type == "text").strip()
    if reply.stop_reason == "max_tokens":
        text += "\n[Answer cut off at the feature's output limit.]"
    return text


if __name__ == "__main__":
    requests = [
        ("student-7", "classify", "Sentiment of: 'The quiz was confusing.' (positive/negative/neutral)"),
        ("student-7", "quick_answer", "What does pip do?"),
        ("student-7", "explain", "How do Python decorators work?"),
        ("student-7", "explain", "How do generators work?"),
    ]
    for user_id, feature, question in requests:
        try:
            answer = ask(user_id, feature, question)
            print(f"[{feature}] {answer[:70]}...")
        except BudgetExceeded as error:
            print(f"[{feature}] refused: {error}")
        print(f"   {user_id} output used today: {usage_today[user_id]}")

Example output (numbers vary):

[classify] negative...
   student-7 output used today: 96
[quick_answer] pip is Python's package installer. It downloads packages from PyPI and ins...
   student-7 output used today: 241
[explain] A decorator is a function that takes another function and returns a modi...
   student-7 output used today: 1012
[explain] refused: student-7 has used 1012 of 4000 output tokens today.
   student-7 output used today: 1012

How It Works

  1. OutputProfile bundles the three output controls for a feature: a length instruction, an effort level, and a max_tokens ceiling. Each feature gets limits that fit its purpose, and a classification never receives the budget of a long explanation.
  2. The instruction is appended to the question, so the visible answer is shaped by the prompt, the most efficient control.
  3. The budget check reserves the worst case before sending. A request is refused if the user's usage plus the feature's max_tokens would exceed the budget. In the sample run, the fourth request is refused: 1,012 tokens already used plus the 3,072 tokens reserved for an explain request is 4,084, which exceeds the 4,000-token budget. The third request was allowed, because 241 + 3,072 = 3,313 fits within the budget.
  4. Actual usage is recorded after the reply, from usage.output_tokens, which includes thinking. Usage grows by what was really generated, not by the reservation.
  5. Truncation is reported to the user, so a cut-off answer is never presented as complete.

Note: usage_today lives in memory and resets when the program restarts. Production systems store usage in a database or cache, keyed by user and date (Lesson 179).


Common Mistakes

Relying on max_tokens Alone

What happens: answers are cut off mid-sentence instead of being concise, and thinking-heavy requests return no text.

Fix: shape length with instructions and effort; use max_tokens as the safety net.

One Limit for Every Feature

What happens: simple features waste budget, and complex features are truncated.

Fix: define output profiles per feature, based on measurements.

Forgetting That Thinking Counts

What happens: limits sized for the visible answer cause truncation.

Fix: size limits from measured output_tokens, and lower effort where thinking is not needed.

Assuming Early Stream Cancellation Is Free

What happens: costs are underestimated for cancelled requests.

Fix: tokens generated before cancellation are billed. Use cancellation to cap cost, not to eliminate it.

No Total Budget

What happens: per-request limits are respected, but a single user or script sends thousands of requests.

Fix: add per-user, per-feature, and organization-level budgets.


Best Practices

  • Shape output with instructions first, then effort, then structured outputs.
  • Use max_tokens as a measured safety net (1.5–2× p95).
  • Define output profiles per feature, and keep them in configuration.
  • Reserve the worst case when checking budgets, and record actual usage afterward.
  • Report truncation to users and in metrics.
  • Use streaming for long outputs, and let users stop generation they no longer need.
  • Re-measure after changing models, prompts, or effort.

Exercise

  1. Change DAILY_OUTPUT_BUDGET to 5_000. Before running the script, predict whether the fourth request is refused, then run it to check.
  2. Add a "summary" profile for summarizing documents in about 100 words, with suitable effort and max_tokens values.
  3. Change the budget check to use the feature's average measured output instead of max_tokens. What are the advantages and risks?
  4. Modify the streaming example to stop after the first 5 complete lines instead of 500 characters.
Show answers for steps 1, 3, and 4

Step 1: with a budget of 5,000, the check before the fourth request is 1,012 + 3,072 = 4,084, which does not exceed 5,000, so the request is allowed. (Your exact usage numbers may differ slightly, because output sizes vary between runs.)

Step 3: using the average reserves less, so users can make more requests before reaching the limit. The risk is that a request larger than average can push usage over the budget, because the check no longer covers the worst case. A common compromise is to reserve the p95 instead of the average or the maximum.

Step 4:

collected = ""
with client.messages.stream(
    model="claude-opus-5-5",
    max_tokens=2048,
    messages=[{"role": "user", "content": "List 50 Python project ideas, one per line."}],
) as stream:
    for text in stream.text_stream:
        collected += text
        if collected.count("\n") >= 5:
            break

print("\n".join(collected.splitlines()[:5]))

Counting newline characters detects when five lines are complete. The final splitlines()[:5] removes any partial sixth line.

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 Limit Output Tokens and get answers drawn from it.

Signed-in readers only.