Streaming Responses with the Python SDK
Streaming lets your application render Claude's answer as it is generated, instead of waiting for the full response.
The anthropic package exposes this through client.messages.stream(), a context manager built specifically for consuming a response incrementally.
Summary
A non-streaming call to messages.create() returns one complete Message object after the model has finished generating.
For long responses, that means your application sits idle, showing nothing, until the very last token is ready.
messages.stream() returns a context manager instead, exposing events (and a convenient text_stream iterator) as they arrive from the server.
The final, fully assembled message is still available once the stream ends, so streaming adds a way to observe the response incrementally without losing anything you'd get from a normal call.
Streaming is also the recommended way to request large outputs, because non-streaming requests risk hitting client-side HTTP timeouts once max_tokens climbs into the tens of thousands.
Recipe
import anthropic
client = anthropic.Anthropic()
with client.messages.stream(
model="claude-sonnet-5",
max_tokens=2048,
messages=[{"role": "user", "content": "Write a short poem about the ocean."}],
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
final_message = stream.get_final_message()
print()
print(f"\nStop reason: {final_message.stop_reason}")When to reach for this:
- Chat interfaces where you want to show text as it's generated, matching how users expect a conversational UI to feel.
- Any request with
max_tokensabove roughly 16,000, where a non-streaming call risks a client-side timeout. - Long-form generation (articles, reports, large code files) where partial output is still useful to the user before the full response completes.
- CLI tools that print output progressively rather than pausing silently.
Working Example
import anthropic
client = anthropic.Anthropic()
def stream_answer(question: str) -> str:
"""Stream a response to stdout and return the full text once done."""
full_text = ""
with client.messages.stream(
model="claude-sonnet-5",
max_tokens=4096,
system="You are a concise technical writer.",
messages=[{"role": "user", "content": question}],
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
full_text += text
final_message = stream.get_final_message()
print() # newline after the streamed output
print(f"[stop_reason={final_message.stop_reason}, "
f"output_tokens={final_message.usage.output_tokens}]")
return full_text
if __name__ == "__main__":
stream_answer("Explain what a context window is, in three sentences.")What this demonstrates:
client.messages.stream(...)is used as a context manager (with ... as stream:), which handles opening and closing the underlying connection.stream.text_streamyields plain text chunks as they arrive - no manual event parsing required for the common case.stream.get_final_message()(called after theforloop finishes) returns the same completeMessageobject a non-streaming call would have returned, includingstop_reasonandusage.- Accumulating chunks into
full_textlets you both print progressively and still return the complete string to the caller.
Deep Dive
How It Works
- Under the hood,
messages.stream()opens an HTTP connection and reads a series of server-sent events (message_start,content_block_delta,message_delta,message_stop, and others) as they arrive. text_streamis a convenience iterator built on top of those raw events - it filters for text deltas and yields just the string content, so you don't parse event types yourself for basic use.- The stream object buffers everything it has seen, which is what lets
get_final_message()return a complete, typedMessageafter the loop ends, with all the same fields (content,stop_reason,usage) as a non-streaming response. - Token usage and billing are unaffected by streaming - you pay for the same output tokens whether you receive them as one block or as many small chunks.
- If the connection drops mid-stream, the exception surfaces from the iteration itself (inside the
withblock), so wrap streaming calls in the same retryable-error handling you'd use for any other API call.
Event Types You Can Access Directly
For anything beyond plain text, iterate the stream object itself instead of text_stream:
| Event / accessor | What it gives you |
|---|---|
for event in stream: | Raw stream events (content_block_start, content_block_delta, message_delta, etc.) |
stream.text_stream | Just the text deltas, as plain strings |
stream.get_final_message() | The complete Message object after the stream finishes |
Python Notes
# Inspecting raw events instead of using text_stream - useful when you
# need thinking content or tool_use blocks as they stream, not just text.
with client.messages.stream(
model="claude-sonnet-5",
max_tokens=2048,
messages=[{"role": "user", "content": "Explain recursion."}],
) as stream:
for event in stream:
if event.type == "content_block_delta" and event.delta.type == "text_delta":
print(event.delta.text, end="", flush=True)
elif event.type == "message_delta":
# carries incremental usage and stop_reason updates
passAsync Streaming
import asyncio
import anthropic
client = anthropic.AsyncAnthropic()
async def stream_answer(question: str) -> None:
async with client.messages.stream(
model="claude-sonnet-5",
max_tokens=2048,
messages=[{"role": "user", "content": question}],
) as stream:
async for text in stream.text_stream:
print(text, end="", flush=True)
print()
asyncio.run(stream_answer("What is a coroutine, in two sentences?"))AsyncAnthropic() streaming uses async with and async for in place of with and for - everything else about the API is the same.
Gotchas
- Using
withinstead ofasync withon the async client.AsyncAnthropic().messages.stream(...)returns an async context manager; opening it with plainwithraises aTypeError. Fix: useasync withandasync forwhenever you're onAsyncAnthropic(). - Forgetting
flush=Truewhen printing streamed text. Without it, Python may buffer stdout and the output appears to arrive in bursts rather than smoothly. Fix: passflush=Truetoprint(), or write directly tosys.stdoutand flush explicitly. - Reading
stream.text_streamtwice. The iterator is consumed once; iterating it again after the loop finishes yields nothing. Fix: accumulate the text into a variable during the first pass if you need it again afterward. - Calling
get_final_message()before the stream has been fully consumed. If you break out of theforloop early, the final message may be incomplete or the call may block waiting for the rest of the stream. Fix: let the iteration finish naturally, or explicitly close the stream first if you intend to abandon it early. - Assuming streaming avoids the
max_tokenstruncation problem. Streaming changes how you receive output, not how much of it the model is allowed to generate;stop_reason == "max_tokens"can still happen. Fix: sizemax_tokensfor the task regardless of whether you stream. - Not handling network errors during iteration. An interrupted connection surfaces as an exception raised from inside the
for text in stream.text_stream:loop, not before it starts. Fix: wrap thewithblock in the same typed-exception handling you use for non-streaming calls.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
Non-streaming messages.create() | The response is short, or your application only needs the final text, not incremental display | max_tokens is large enough to risk a client-side HTTP timeout |
messages.stream() with text_stream | You want progressive text output with minimal code (chat UIs, CLIs) | You need to inspect tool-use or thinking events as they arrive, not just text |
| Iterating raw stream events directly | You need fine-grained access to tool calls, thinking blocks, or usage deltas mid-stream | Plain text is all you need - text_stream is simpler for that case |
FAQs
Does streaming change what the model actually generates?
No.
The same text, stop_reason, and token usage are produced whether you stream or not; streaming only changes when you get to see pieces of the output.
How do I get the complete Message object after streaming?
Call stream.get_final_message() after the for loop over text_stream (or over the raw stream) has finished. It returns the same typed Message a non-streaming call would return.
Is text_stream the only way to consume a stream?
No.
You can iterate the stream object directly (for event in stream:) to see every raw event type, including tool-use and thinking deltas, not just text.
Do I need to stream on the async client differently than the sync client?
Yes, syntactically: use async with instead of with, and async for instead of for when iterating text_stream or the raw stream on AsyncAnthropic().
Why does my printed output look chunky instead of smooth?
Standard output buffering is usually the cause.
Pass flush=True to print() so each chunk is written to the terminal immediately rather than held in a buffer.
Can I cancel a stream partway through?
Breaking out of the loop or exiting the with block early stops your client from processing further chunks. Treat this as best-effort; already-generated tokens up to that point are still billed.
Does streaming cost more than a non-streaming request?
No.
Billing is based on tokens generated, identical for streaming and non-streaming requests of the same content.
What happens if the network connection drops mid-stream?
An exception is raised from inside the iteration over text_stream (or the raw stream). Handle it the same way you'd handle any other network error from the SDK - see the exception reference page.
Should I always stream, even for short answers?
Not necessarily.
For short, quick responses where you only need the final text, a plain messages.create() call is simpler and has no meaningful downside.
Can I access token usage while the stream is still running?
Partial usage information arrives incrementally in message_delta events if you iterate the raw stream. The complete, final usage is available on the object returned by get_final_message().
Does streaming work with tool use?
Yes - content_block_delta events include incremental tool-input JSON as the model builds a tool call, alongside text deltas. See the async tool-use streaming page for a worked example.
Related
- The Anthropic Python SDK Mental Model - how streaming fits with sync/async clients
- Python SDK Basics - your first non-streaming request, for comparison
- Handling Streaming Tool Use in Async Python Workflows - streaming combined with tool_use
- Python SDK Retry and Timeout Configuration Reference - timeout behavior during long streams
Stack versions: Written against the Claude model lineup current as of ~June 2026 - Claude Fable 5, Claude Opus 4.8, Claude Sonnet 5 (the default), and Claude Haiku 4.5 - and the official
anthropicPython SDK (latest 0.x release). Model names, SDK versions, and pricing move quickly - verify current specifics at platform.claude.com/docs before relying on them.