4 min read · 789 words
When building LLM-powered applications, user experience lives or dies by latency. Waiting 5 to 10 seconds for a full completion to render makes apps feel broken. Streaming responses reduces your Time to First Token (TTFT) to a few hundred milliseconds, providing real-time text delivery that keeps users engaged.
In this guide, you will learn how to stream responses from Anthropic's Claude API using both Python and Node.js, handle stream lifecycle events, and pipe tokens directly to a browser client.
How Claude Streaming Works
Under the hood, Claude streaming relies on Server-Sent Events (SSE). Instead of returning a single JSON payload after generating the full response, the Anthropic API opens an HTTP connection and pushes small JSON chunks (deltas) as tokens are produced.
The Anthropic SDKs abstract raw SSE handling into convenient stream helpers that expose two interface paradigms:
- Iterators: Yield raw tokens as string deltas.
- Event Listeners: Emit named events for precise lifecycle tracking (e.g., stream start, text delta, completion).
Option 1: Streaming in Python
For Python services, backend scripts, or CLI tools, the standard approach uses the messages.stream() context manager. This handles opening and closing the HTTP connection automatically.
Prerequisites
Install the official Anthropic Python SDK:
pip install anthropic
Make sure your ANTHROPIC_API_KEY is set in your environment variables.
Code Example: Python Console Stream
import os
import anthropic
# Initialize client (reads ANTHROPIC_API_KEY from env)
client = anthropic.Anthropic()
def stream_claude_response(prompt: str):
print("Claude: ", end="", flush=True)
# Open streaming context
with client.messages.stream(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}]
) as stream:
# text_stream yields text deltas directly as strings
for text_delta in stream.text_stream:
print(text_delta, end="", flush=True)
print("\n")
if __name__ == "__main__":
stream_claude_response("Write a 3-step action plan to optimize database indexing.")
Key Elements
-
stream.text_stream: An iterator yielding only text deltas, bypassing unnecessary metadata parsing. -
flush=True: Ensures Python prints chunks instantly to stdout without buffering.
Option 2: Streaming to Web Clients (Node.js + Express)
To build web applications, you typically proxy requests through a Node.js backend and stream tokens to the browser via SSE.
Prerequisites
Install the required packages:
npm install @anthropic-ai/sdk express
Code Example: Express SSE Server
import Express from 'express';
import Anthropic from '@anthropic-ai/sdk';
const app = Express();
const anthropic = new Anthropic(); // Reads process.env.ANTHROPIC_API_KEY
app.use(Express.json());
app.get('/api/stream', async (req, res) => {
const prompt = req.query.prompt || 'Explain event loops simply.';
// 1. Configure SSE headers
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
try {
// 2. Initiate Claude stream
const stream = anthropic.messages.stream({
model: 'claude-3-5-sonnet-20241022',
max_tokens: 1024,
messages: [{ role: 'user', content: prompt }],
});
// 3. Attach stream lifecycle listeners
stream.on('text', (textDelta) => {
res.write(`data: ${JSON.stringify({ text: textDelta })}\n\n`);
});
stream.on('end', () => {
res.write('data: [DONE]\n\n');
res.end();
});
stream.on('error', (error) => {
console.error('Streaming error:', error);
res.status(500).end();
});
} catch (err) {
console.error('Server error:', err);
res.status(500).json({ error: 'Failed to initialize stream' });
}
});
app.listen(3000, () => {
console.log('Streaming server listening on http://localhost:3000');
});
How the Frontend Consumes This
On the client side, use the native EventSource API or fetch with readable streams to consume this endpoint:
const eventSource = new EventSource('/api/stream?prompt=Hello');
eventSource.onmessage = (event) => {
if (event.data === '[DONE]') {
eventSource.close();
return;
}
const payload = JSON.parse(event.data);
document.getElementById('output').innerText += payload.text;
};
Understanding Stream Events
If you need full control over the response object—such as tracking token usage metrics or capturing tool calls—listen to specific SSE events instead of raw text:
| Event Name | Description |
|---|---|
message_start |
Contains high-level metadata (model, input token counts). |
content_block_start |
Signals the start of a block (text or tool call). |
text / text_delta
|
The actual string payload generated in the current chunk. |
message_delta |
Emitted when output stops; includes output token usage and stop reason. |
end |
Triggered when the HTTP stream closes completely. |
Essential Best Practices
-
Client Disconnects: Handle connection drops cleanly. In Node.js, listen for
req.on('close')and callstream.controller.abort()to terminate API generation and avoid paying for unconsumed tokens. -
Buffer Management: Do not try to parse partial JSON across streaming boundaries. Keep messages formatted as simple SSE events (
data: {...}\n\n). -
Error Boundaries: Wrap streaming initializations in
try/catchblocks. Network failures can happen mid-stream, so ensure your UI handles partial output gracefully.
Conclusion
Streaming Claude responses converts slow LLM roundtrips into fluid, immediate user interfaces. Whether you use Python's stream context iterator or Node.js event hooks with SSE, integrating streaming takes under 50 lines of code.
What streaming patterns or UI frameworks are you combining with Claude? Share your approach or ask questions in the comments below!
Top comments (0)