Skip to main content
Articles/langchain/langgraph-streaming-agent-responses
Scenario Article

Streaming Agent Responses in LangGraph: Tokens, Events, and Real-Time UI Integration

The 25-Second Spinner: Why Streaming Is a UX Requirement, Not a Nice-to-Have

Abstract Algorithms•Mar 28, 2026
💡 Executive Summary

Stream agents token by token with astream events; wire to FastAPI SSE for zero-spinner UX.

🎯 System Scenario & Requirements

System Goal

Stream agents token by token with astream events; wire to FastAPI SSE for zero-spinner UX.

Actors & Components

  • Application or API tier
  • Core service under design
  • Operator or SRE observer

Functional Requirements

  • TLDR: Stream agents token by token with astream events; wire to FastAPI SSE for zero-spinner UX.
  • Your agent takes 25 seconds to respond.
  • Users abandon after 8 seconds.

Constraints & Assumptions

  • If your graph raises an exception inside a node after 200 tokens have already been sent, the user has already seen partial text.
  • The mitigation is to emit a structured error event ( data: {"type":"error","message":"..."}\n\n ) and have the frontend replace the partial message with an error state rather than leaving the half-answer visible.

⚡ Execution Stages & Decision Trace

1

Stage 1 — Token Streaming with astream_events: Filtering, Extracting, and Handling Events

Action / Decision:

The raw event stream from astream events() is noisy — it includes start/end events for chains, tools, and the LLM itself.

Component Behavior:

In practice you filter to the events that matter for your use case.

Observable Output: Step 1 — Iterate the event stream Step 2 — Filter by event type LangGraph surfaces the following core event types: Step 3 — Extract token content Step 4 — Scope to a specific node In a multi-node graph you usually only…
Read Full Stage in Deep Dive ↓
📖

Deep Dive

Detailed Architectural Analysis

Scenario context

Stream agents token by token with astream_events; wire to FastAPI SSE for zero-spinner UX.

Your agent takes 25 seconds to respond. Users abandon after 8 seconds. You add streaming — same 25 seconds of total compute, but the first token appears in 0.4 seconds. Abandonment drops to near zero.

This is not a performance optimisation. It is a perception shift. The user's subjective experience of waiting for the…

This walkthrough focuses on The 25-Second Spinner: Why Streaming Is a UX Requirement, Not a Nice-to-Have.

Before making a design choice, clarify:

  • Your agent takes 25 seconds to respond.
  • Users abandon after 8 seconds.
  • You add streaming — same 25 seconds of total compute, but the first token appears in 0.4 seconds.

Concepts in play: llm-agents-and-langgraph, graph-traversal-algorithms, system-design-hld-architectures, event-driven-architecture

Stage 1 — Token Streaming with astream_events: Filtering, Extracting, and Handling Events

  • The raw event stream from astream events() is noisy — it includes start/end events for chains, tools, and the LLM itself.
  • In practice you filter to the events that matter for your use case.
  • Step 1 — Iterate the event stream Step 2 — Filter by event type LangGraph surfaces the following core event types: Step 3 — Extract token content Step 4 — Scope to a specific node In a multi-node graph you usually only…

Observable outcome: Step 1 — Iterate the event stream Step 2 — Filter by event type LangGraph surfaces the following core event types: Step 3 — Extract token content Step 4 — Scope to a specific node In a multi-node graph you usually only…

Stage 2 — Deep Dive: How LangGraph's Streaming Pipeline Works

  • The Internals LangGraph builds on LangChain's Runnable abstraction.
  • Every node in a LangGraph graph is a RunnableLambda or RunnableSequence under the hood.
  • When you call astream events() , LangGraph: 1.
  • Creates an AsyncCallbackManager and attaches it to the RunnableConfig passed to each node.

Observable outcome: Creates an AsyncCallbackManager and attaches it to the RunnableConfig passed to each node.

Stage 3 — The Streaming Event Flow: From Node Execution to Browser Pixel

  • Each SSE frame is a UTF-8 string prefixed with data: and terminated by \n\n .
  • The browser's EventSource API fires an onmessage callback for each frame, which appends the token to the DOM.
  • No WebSocket handshake, no polling — a plain HTTP response that never closes until the agent finishes.

Observable outcome: No WebSocket handshake, no polling — a plain HTTP response that never closes until the agent finishes.

Stage 4 — Multi-Agent Streaming: Subgraphs, Parallel Fan-Out, and Per-Agent Filtering

  • Single-agent streaming is straightforward.
  • Multi-agent graphs introduce three complications: event multiplexing (tokens from multiple agents interleave), subgraph event bubbling (subgraph events propagate to the parent stream), and parallel branch synchronisatio…
  • Subgraph event bubbling When you compile a subgraph and embed it as a node in a parent graph, events from inside the subgraph automatically appear in the parent's astream events() stream.
  • The langgraph node metadata reflects the subgraph node name, not the parent node name.

Observable outcome: The langgraph node metadata reflects the subgraph node name, not the parent node name.

Stage 5 — Real-World Applications: How ChatGPT, Claude, and Copilot Stream Responses

  • ChatGPT uses OpenAI's /v1/chat/completions endpoint with stream: true .
  • The response is an SSE stream of JSON delta objects ( {"choices":[{"delta":{"content":"The"}}]} ).
  • The frontend concatenates deltas into the visible message.
  • Error recovery: if the stream drops mid-response, the UI shows a "regenerate" button — it does not attempt to resume from the partial output.

Observable outcome: Error recovery: if the stream drops mid-response, the UI shows a "regenerate" button — it does not attempt to resume from the partial output.

Stage 6 — Practical Example: FastAPI SSE Endpoint With Streaming Research Assistant

  • This example builds a two-node LangGraph agent (search → synthesize) and exposes it as a streaming SSE endpoint, with a JavaScript EventSource client that renders tokens word by word as they arrive.
  • The research-assistant scenario was chosen because it has two nodes with meaningfully different event profiles — the search node emits tool call events, and the synthesize node emits token-by-token text chunks — which l…
  • As you read through the generate stream async generator, watch the event["event"] filter: every on chat model stream event that passes the check is one token yielded to the browser, and that is the complete streaming pi…
  • Graph definition Important : pass streaming=True to ChatOpenAI .

Observable outcome: Graph definition Important : pass streaming=True to ChatOpenAI .

End-to-end outcome

After working through the stages, you should be able to explain:

  • streaming=True on the LLM is not optional.
  • The single most common reason developers find astream events() doesn't improve TTFT is forgetting to set streaming=True on the ChatOpenAI (or equivalent) constructor.

Alternatives considered

Three Streaming Modes: values, updates, and astream_events Compared

LangGraph exposes streaming through two sync and one async family of methods.

Decision Guide: stream() vs astream() vs astream_events() — When to Use Each

Decision Guide: stream() vs astream() vs astream_events() — When to Use Each

Failure and degraded modes

Trade-offs and Failure Modes: Partial Output, Error Mid-Stream, and Backpressure

  • Detection: If your graph raises an exception inside a node after 200 tokens have already been sent, the user has already seen partial text.
  • User or operator impact: If your graph raises an exception inside a node after 200 tokens have already been sent, the user has already seen partial text.

Share this case study

Discuss architecture trade-offs and lessons with your team.

Share:XLinkedInReddit