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.
Search across all documentation pages
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.
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.
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:
max_tokens above roughly 16,000, where a non-streaming call risks a client-side timeout.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_stream yields plain text chunks as they arrive - no manual event parsing required for the common case.stream.get_final_message() (called after the for loop finishes) returns the same complete Message object a non-streaming call would have returned, including stop_reason and usage.full_text lets you both print progressively and still return the complete string to the caller.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_stream is 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.get_final_message() return a complete, typed Message after the loop ends, with all the same fields (content, stop_reason, usage) as a non-streaming response.with block), so wrap streaming calls in the same retryable-error handling you'd use for any other API call.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 |
# 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
passimport 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.
with instead of async with on the async client. AsyncAnthropic().messages.stream(...) returns an async context manager; opening it with plain with raises a TypeError. Fix: use async with and async for whenever you're on AsyncAnthropic().flush=True when printing streamed text. Without it, Python may buffer stdout and the output appears to arrive in bursts rather than smoothly. Fix: pass flush=True to print(), or write directly to sys.stdout and flush explicitly.stream.text_stream twice. 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.get_final_message() before the stream has been fully consumed. If you break out of the for loop 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.max_tokens truncation 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: size max_tokens for the task regardless of whether you stream.for text in stream.text_stream: loop, not before it starts. Fix: wrap the with block in the same typed-exception handling you use for non-streaming calls.| 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 |
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.
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.
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.
Yes, syntactically: use async with instead of with, and async for instead of for when iterating text_stream or the raw stream on AsyncAnthropic().
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.
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.
No.
Billing is based on tokens generated, identical for streaming and non-streaming requests of the same content.
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.
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.
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().
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.
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.
Reviewed by Chris St. John·Last updated Jul 19, 2026