Streaming Intro

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

What Streaming Is

Without streaming, client.messages.create() waits until Claude has generated the entire response, then returns it all at once. For a long answer, the user sees nothing for many seconds, then everything appears.

With streaming, the response is delivered piece by piece while it is being generated. Your program receives small chunks of text, usually a few words at a time, and can show them immediately:

from anthropic import Anthropic

client = Anthropic()

with client.messages.stream(
    model="claude-opus-5-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Explain what an API is in three sentences."}],
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)
print()

The answer appears on screen progressively, the way chat interfaces such as the Claude app display responses.


How Streaming Works

Streaming uses server-sent events (SSE), a standard way for a server to send a sequence of messages over one long-lived HTTP connection. When you request streaming ("stream": true in the raw API), the API keeps the connection open and sends events as generation progresses:

message_start          → a Message shell with empty content
content_block_start    → a new content block begins (text, thinking, tool_use, ...)
content_block_delta    → a small piece of that block's content (repeated many times)
content_block_stop     → the block is complete
   ... more content blocks ...
message_delta          → top-level changes: stop_reason and cumulative usage
message_stop           → the stream is finished

ping events can also appear at any time, to keep the connection active. Lessons 53 and 54 look at events in detail. For most applications, the SDK's helpers hide the event details completely.


Why Stream?

BenefitExplanation
Faster perceived responseThe first words appear in a fraction of the total time, so users see progress immediately
Better user experienceReading starts while the rest is still being written
Reliable long responsesThe connection stays active, avoiding timeouts that can affect long, silent requests
Required for very large outputsThe SDK raises an error for non-streaming requests it expects to take longer than about 10 minutes (Lesson 13)
Early stoppingYour code, or the user, can stop a response that is no longer needed (Lesson 60)

Two Kinds of Latency

MeasureMeaningEffect of streaming
Time to first tokenHow long until the first piece of the answer arrivesMuch shorter in practice: you can show text as soon as it exists
Total timeHow long until the complete answer has been generatedAbout the same as without streaming

Streaming does not make Claude generate faster. It makes the waiting visible and useful, which is what users notice.

Note: with thinking models, the first visible text may take a moment to appear, because the model can think before it starts writing the answer (Lesson 16). A brief "thinking…" indicator helps here (Lesson 53).


When to Use Streaming

SituationStream?
Chat interfaces and interactive toolsYes: users expect text to appear progressively
Long answers, documents, or code generationYes: better experience and more reliable connections
Very large max_tokens valuesYes: required to avoid timeouts
Short, background tasks such as classification or extractionUsually not: the full result is needed before anything happens
Batch processing of many itemsNo: use the Message Batches API (Lessons 131–140)
Structured data that must be complete before useOptional: stream for reliability, but use the final message

Three Ways to Stream with the SDK

ApproachHowBest for
Stream helperwith client.messages.stream(...) as stream:Most applications: text iteration, events, and the final message
Final message onlystream.get_final_message() inside the helperLong requests where you only need the complete result
Raw eventsclient.messages.create(..., stream=True)Low-level control, or forwarding raw events
with client.messages.stream(model="claude-opus-5-5", max_tokens=1024, messages=messages) as stream:
    for text in stream.text_stream:          # Only the answer text, chunk by chunk
        print(text, end="", flush=True)
    message = stream.get_final_message()     # The complete Message, like create() returns

messages.stream() takes the same parameters as messages.create(). The with block makes sure the connection is closed properly, even if an error occurs.

Final Message Only

For long requests where you do not need to display progress, streaming can still be used internally:

with client.messages.stream(model="claude-opus-5-5", max_tokens=64000, messages=messages) as stream:
    message = stream.get_final_message()

According to the documentation, this returns a Message identical to what create() would return, and it is especially useful for large max_tokens values, where the SDK requires streaming to avoid HTTP timeouts.

Raw Events

stream = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=1024,
    messages=messages,
    stream=True,
)
for event in stream:
    print(event.type)

With stream=True, create() returns an iterator of raw events, without the helper's text iteration or message accumulation. Lesson 53 uses events in detail.


Practical Example: Measuring the Difference

This script sends the same request twice, once without streaming and once with streaming, and measures time to first text and total time for each. It shows concretely what streaming changes, and what it does not.

"""streaming_vs_blocking.py: compare perceived latency with and without streaming."""

import time

from anthropic import Anthropic

client = Anthropic()
REQUEST = {
    "model": "claude-opus-5-5",
    "max_tokens": 2048,
    "messages": [{"role": "user", "content": "Explain how HTTPS protects data, in about 250 words."}],
}


def without_streaming() -> None:
    start = time.perf_counter()
    message = client.messages.create(**REQUEST)
    total = time.perf_counter() - start
    text = "".join(b.text for b in message.content if b.type == "text")
    print(f"[blocking]  first text after {total:.1f}s, finished after {total:.1f}s, "
          f"{len(text.split())} words")


def with_streaming() -> None:
    start = time.perf_counter()
    first_text_at = None
    words = 0
    with client.messages.stream(**REQUEST) as stream:
        for text in stream.text_stream:
            if first_text_at is None and text.strip():
                first_text_at = time.perf_counter() - start
            words += len(text.split())
    total = time.perf_counter() - start
    print(f"[streaming] first text after {first_text_at:.1f}s, finished after {total:.1f}s, "
          f"~{words} words")


if __name__ == "__main__":
    without_streaming()
    with_streaming()

Example output (timings vary):

[blocking]  first text after 9.8s, finished after 9.8s, 252 words
[streaming] first text after 2.1s, finished after 9.6s, ~251 words

How It Works

  1. Both functions send the same request, using the shared REQUEST dictionary, so the comparison is fair.
  2. Without streaming, nothing is available until create() returns, so the first text and the full text arrive at the same moment.
  3. With streaming, first_text_at records when the first non-empty chunk arrives. text.strip() skips chunks that contain only whitespace.
  4. time.perf_counter() is a high-resolution timer for measuring elapsed time.
  5. The word count from chunks is approximate (~), because a chunk can end in the middle of a word, which split() then counts twice or not at all. Use the final message for exact text.

The result shows the key point: the total time is about the same, but with streaming, the user starts reading after about 2 seconds instead of waiting nearly 10.


Common Mistakes

Expecting Streaming to Make Generation Faster

What happens: disappointment when total time does not change.

Fix: use streaming to improve perceived latency and reliability. To reduce total time, shorten outputs, lower effort, or choose a faster model.

Forgetting flush=True

What happens: text appears in bursts, or only at the end, because Python buffers output.

Fix: print chunks with print(text, end="", flush=True).

Not Using the with Block

What happens: connections may not be closed properly when errors occur, or when your code stops reading early.

Fix: always use with client.messages.stream(...) as stream: (Lesson 60).

Streaming Batch or Background Work

What happens: extra complexity with no user-facing benefit.

Fix: use regular requests for short background tasks, and the Batches API for bulk work.

Building the Final Text from Chunks for Storage

What happens: stored text is fine for plain answers, but thinking blocks, tool calls, and usage are lost.

Fix: store stream.get_final_message() (Lesson 55).


Best Practices

  • Stream every user-facing response that may take more than a second or two.
  • Use the messages.stream() helper with a with block.
  • Show progress immediately, including a thinking indicator for thinking models.
  • Use get_final_message() for storage, usage, and stop reasons.
  • Use streaming for large max_tokens values, even when you do not display progress.
  • Keep non-streaming requests for short background tasks.

Exercise

  1. Run streaming_vs_blocking.py with a prompt that asks for about 50 words, then with one that asks for about 600 words. How does the difference in time to first text change?
  2. Modify with_streaming() to also print the number of chunks received.
  3. Use stream.get_final_message() inside with_streaming() to print the exact word count and usage.output_tokens.
  4. Explain why first_text_at can be several seconds even with streaming, especially for complex questions.
Show answers for steps 3 and 4

Step 3: call get_final_message() after the loop, still inside the with block:

with client.messages.stream(**REQUEST) as stream:
    for text in stream.text_stream:
        ...
    message = stream.get_final_message()

final_text = "".join(b.text for b in message.content if b.type == "text")
print(f"exact words: {len(final_text.split())}, output tokens: {message.usage.output_tokens}")

Step 4: current models may think before writing the answer. Thinking tokens are generated first, and text_stream yields only answer text, so the first visible text appears only after the thinking phase. Complex questions usually involve more thinking. A visible "thinking…" indicator, or a lower effort setting for simple tasks, keeps the experience responsive.

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 Streaming Intro and get answers drawn from it.

Signed-in readers only.