AI & LLM APIs cookbook · Intermediate

Stream model answers to the browser with SSE

Show the answer as it is generated instead of making users wait several seconds for the full reply.

Create a free account to save this page, take notes and track your progress. Sign in

Approach

Use the SDK's messages.stream() helper on the server and forward each text delta as a server-sent event. The browser reads the stream with EventSource or fetch. Keep the API key on the server, send a final event with usage and stop reason, and handle errors mid-stream.

Solution

import json

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

app = FastAPI()
client = anthropic.AsyncAnthropic()               # reads ANTHROPIC_API_KEY


class Ask(BaseModel):
    question: str


def sse(event: str, data: dict) -> str:
    return f"event: {event}\ndata: {json.dumps(data)}\n\n"


@app.post("/ask")
async def ask(body: Ask):
    async def events():
        try:
            async with client.messages.stream(
                model="claude-sonnet-5-5",
                max_tokens=1024,
                system="You are a concise support assistant.",
                messages=[{"role": "user", "content": body.question}],
            ) as stream:
                async for text in stream.text_stream:
                    yield sse("delta", {"text": text})
                final = await stream.get_final_message()
            yield sse("done", {"stop_reason": final.stop_reason,
                               "output_tokens": final.usage.output_tokens})
        except anthropic.APIError as exc:
            yield sse("error", {"message": "The answer was interrupted. Please try again."})
            raise exc

    return StreamingResponse(events(), media_type="text/event-stream",
                             headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"})

Pitfalls

  • Buffering proxies (nginx, some CDNs) hold the stream until it ends; disable buffering for this route.
  • Validating structured output per chunk: parse only after the stream is complete.
  • Calling the API from the browser with your key; always proxy through your server.

Variations

  • Use fetch with a ReadableStream reader in the browser when you need POST bodies (EventSource is GET-only).
  • For WebSocket apps, send the same delta and done messages over the socket.

Further reading: Streaming messages

Reference: https://docs.anthropic.com/en/docs/build-with-claude/streaming

More AI & LLM APIs recipes

Was this page helpful?