Python for JS Devs · LLM APIs

Streaming LLM responses

The ChatGPT typewriter effect. Stream tokens to the client as they arrive — using async generators and FastAPI's StreamingResponse.

Lesson 20Time ~15 min Win A /chat/stream endpoint that streams tokensPrereq Lesson 19

A full LLM reply can take many seconds. Streaming sends each token the moment the model produces it, so the user sees output immediately. Two new pieces: the SDK's async stream, and FastAPI's StreamingResponse driven by an async generator.

1 · The SDK stream (async iteration)

The async client's .stream() is an async context manager exposing text_stream — an async iterator of text chunks. This is async for from Lesson 10 in action.

Node
const stream = client.messages
  .stream({ ... });
for await (const ev of stream) { ... }
Python
async with client.messages.stream(
    model="claude-opus-4-8", max_tokens=1024,
    messages=[...],
) as stream:
    async for text in stream.text_stream:
        print(text, end="", flush=True)

2 · An async generator = the response body

Python's async generator (an async def with yield) is the perfect producer for a streaming HTTP body. Wrap it in StreamingResponse and FastAPI streams each yielded chunk to the client.

import anthropic
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from pydantic import BaseModel

app = FastAPI()
client = anthropic.AsyncAnthropic()

class ChatIn(BaseModel):
    message: str

async def token_stream(message: str):
    async with client.messages.stream(
        model="claude-opus-4-8", max_tokens=1024,
        messages=[{"role": "user", "content": message}],
    ) as stream:
        async for text in stream.text_stream:
            yield text        # each chunk flushed to the client

@app.post("/chat/stream")
async def chat_stream(body: ChatIn):
    return StreamingResponse(
        token_stream(body.message),
        media_type="text/plain",
    )
Why this composes so cleanly An async generator is a lazy async iterator — it produces a value, suspends, and resumes on the next pull. FastAPI pulls one chunk at a time and writes it out, so memory stays flat regardless of reply length, and the event loop is never blocked (Lesson 17). No callbacks, no manual buffering.

3 · Server-Sent Events (for browsers)

For a JS frontend using EventSource, format each chunk as SSE and set the media type. Same generator, tiny change:

async def sse_stream(message: str):
    async with client.messages.stream(...) as stream:
        async for text in stream.text_stream:
            yield f"data: {text}\n\n"      # SSE frame

@app.post("/chat/sse")
async def chat_sse(body: ChatIn):
    return StreamingResponse(sse_stream(body.message),
                             media_type="text/event-stream")
Don't lose errors mid-stream Once bytes start flowing you can't change the status code. Wrap the generator body in try/except and yield an error sentinel (or an SSE event: error frame) so the client can react — you can't raise HTTPException after streaming has begun.

🎯 Your win — the finale

Build POST /chat/stream with the async generator + StreamingResponse. Test it with curl -N -X POST localhost:8000/chat/stream -H "content-type: application/json" -d '{"message":"Write a haiku about Python"}' and watch tokens arrive live. You've now shipped a streaming, LLM-backed FastAPI service — the exact goal from Lesson 01's mission. 🎉

Check yourself

Q1. What produces the streaming response body in FastAPI?
An async def with yield feeds StreamingResponse one chunk at a time.
Q2. For a browser EventSource, the media type must be:
SSE requires text/event-stream, with each frame as data: ...\n\n.
Q3. Once streaming has started, an error should be:
You can't change the status code mid-stream — yield an error marker the client can handle.
Primary source FastAPI — StreamingResponse and the Anthropic SDK streaming helpers.
💬 You finished the course! Ask your teacher what to build next — a real project to consolidate everything, or a deeper dive (RAG, agents, testing, deployment). And join a community: Python Discord or r/FastAPI to test your skills in the wild.