LangChain .stream() — output arrives as one giant chunk
Streaming in LangChain is a chain-wide property: it works only when every step supports streaming natively. A single buffering step turns your beautiful token-by-token stream into a one-shot delivery.
Quick fix (TL;DR)
.stream() / .astream() yields incremental chunks only when every runnable in the chain implements streaming. Common buffering culprits: PydanticOutputParser, with_structured_output() with method="function_calling", custom RunnableLambda, and JsonOutputParser on non-array roots. Fix by (a) using StrOutputParser where structure is not needed, (b) using .astream_events() to inspect what each step emits, and (c) rewriting custom lambdas as generators.Real error messages you'll see
These are the exact strings returned by the LangChain framework and its integrations when this error occurs. Copy-paste-searching any of them should land on this page.
# Expected token-by-token output; got one final chunk instead
chain = prompt | model | PydanticOutputParser(pydantic_object=MyModel)
for chunk in chain.stream({"q": "..."}):
print(chunk)
# <Instance MyModel> <-- only one iteration, no incremental chunks# Same problem async — no yield until the whole thing completes
async for chunk in chain.astream({"q": "..."}):
print(chunk)
# ... several seconds of no output ... then one dump# The lambda materializes the whole output before returning chain = model | (lambda ai: ai.content.upper()) # ← buffers # Rewrite as a generator or use StrOutputParser instead
Reference
Streaming behaviour by common LangChain component
| Component | Streams natively? | Notes |
|---|---|---|
ChatOpenAI, ChatAnthropic, other chat models | Yes | Stream tokens by default |
StrOutputParser | Yes | Passes chunks through |
JsonOutputParser (list root) | Yes — array elements | Emits complete items as they parse |
JsonOutputParser (object root) | Partial — merged chunks | Emits progressive partial dicts |
PydanticOutputParser | No — buffers to validate | Whole message needed before validation |
with_structured_output() | Depends on method | function_calling buffers; json_mode streams |
Custom RunnableLambda | No unless generator | Buffers by default |
Retriever | No | Retrieval is one-shot |
Root causes, ranked by frequency
Based on developer reports across LangChain forums, GitHub issues, and Discord community during 2025–2026.
- 28%PydanticOutputParser at the end of the chain. Waits for the whole output to validate; nothing streams past it.
- 18%Custom lambda that materializes the full input.
lambda ai: ai.content.upper()consumes the whole AIMessage before returning. - 14%with_structured_output with function_calling method. Provider tool_use responses are not streamed as text.
- 12%Retriever step in the middle of the chain. Not a streaming primitive; blocks until documents return.
- 8%Server-side buffering. Your web framework (Flask default, some FastAPI configs) buffers responses before forwarding.
- 8%Model provider or SDK not streaming. Some providers or model versions do not support streaming; the chat model falls back to non-stream.
- 7%Using invoke and expecting stream.
chain.invoke()never streams; you must usechain.stream()orchain.astream(). - 5%Iteration consumed elsewhere. Wrapping the stream in a list comprehension defeats the purpose.
Fixes — copy-paste solutions
Identify the buffering step with astream_events
.astream_events() emits a stream of events — on_chain_start, on_llm_stream, on_parser_stream, etc. Filter events to see whether each step is streaming as expected.
import asyncio from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser, PydanticOutputParser from pydantic import BaseModel class Answer(BaseModel): answer: str confidence: float prompt = ChatPromptTemplate.from_template("Answer: {question}") model = ChatOpenAI(model="gpt-4o-mini") parser = PydanticOutputParser(pydantic_object=Answer) # Chain A: streams ✓ chain_a = prompt | model | StrOutputParser() # Chain B: does not stream past the parser ✗ chain_b = prompt | model | parser async def diagnose(chain, name): print(f"\n=== {name} ===") chunk_count = 0 async for event in chain.astream_events({"question": "hi"}, version="v2"): kind = event["event"] if kind in ("on_chat_model_stream", "on_parser_stream", "on_chain_stream"): data = event.get("data", {}).get("chunk", "") if hasattr(data, "content"): data = data.content if data: chunk_count += 1 print(f" [{event['name']:20s}] chunk: {str(data)[:40]!r}") print(f" Total streaming chunks: {chunk_count}") asyncio.run(diagnose(chain_a, "chain_a: prompt | model | StrOutputParser")) asyncio.run(diagnose(chain_b, "chain_b: prompt | model | PydanticOutputParser")) # chain_a: many chunks streaming from model and passing through StrOutputParser # chain_b: chunks from model, then a single emission at the end from parser
Replace buffering parsers with streaming-friendly alternatives
If the buffering step is PydanticOutputParser or with_structured_output(), switch to JSON mode with JsonOutputParser which emits progressive partial dicts as tokens arrive.
from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import JsonOutputParser from pydantic import BaseModel, Field from typing import List # ❌ NON-STREAMING — PydanticOutputParser buffers # from langchain_core.output_parsers import PydanticOutputParser # chain = prompt | model | PydanticOutputParser(pydantic_object=Answer) # ✓ STREAMING — JsonOutputParser emits progressive partial dicts class Answer(BaseModel): steps: List[str] = Field(description="Reasoning steps") final: str = Field(description="Final answer") # Use JSON mode on the model, JsonOutputParser at the end model_json = ChatOpenAI(model="gpt-4o-mini").bind( response_format={"type": "json_object"} ) parser = JsonOutputParser(pydantic_object=Answer) prompt = ChatPromptTemplate.from_messages([ ("system", "Answer as JSON. {format_instructions}"), ("human", "{question}"), ]).partial(format_instructions=parser.get_format_instructions()) chain = prompt | model_json | parser # Now streaming yields progressively-populated dicts: for chunk in chain.stream({"question": "Explain photosynthesis"}): print(chunk) # {} # {'steps': []} # {'steps': ['Plants absorb']} # {'steps': ['Plants absorb sunlight']} # {'steps': ['Plants absorb sunlight', 'Chlorophyll captures']} # ... # {'steps': [...], 'final': 'Photosynthesis converts...'}
Rewrite custom lambdas as generators
When you need a custom transformation but must preserve streaming, wrap it in RunnableGenerator or write it as a generator function.
from typing import Iterator from langchain_core.runnables import RunnableGenerator from langchain_openai import ChatOpenAI from langchain_core.output_parsers import StrOutputParser model = ChatOpenAI(model="gpt-4o-mini") # ❌ BUFFERING — full text arrives, then transformation runs # chain = model | StrOutputParser() | (lambda s: s.upper()) # ✓ STREAMING — generator preserves per-token flow def upper_generator(chunks: Iterator[str]) -> Iterator[str]: """Uppercase every chunk as it arrives.""" for chunk in chunks: yield chunk.upper() chain = model | StrOutputParser() | RunnableGenerator(upper_generator) for chunk in chain.stream("tell me a haiku"): print(chunk, end="", flush=True) # Streams uppercase chunks in real time # For async chains, use an async generator async def upper_agen(chunks): async for chunk in chunks: yield chunk.upper() # Async version achain = model | StrOutputParser() | RunnableGenerator(upper_agen) # For running arbitrary sync transformations that ARE per-chunk: # Just use a lambda with StrOutputParser BEFORE it, and remember every # lambda receives ONE full accumulated input by default. For per-chunk, # always use a generator.
RunnableLambda receives the full input, RunnableGenerator receives an iterator of chunks. Use the right primitive for your intent.Prevention checklist
Ship these seven safeguards once and this error stops appearing in your logs.
- Prefer
StrOutputParserandJsonOutputParserover Pydantic parsers when streaming is required. - Wrap custom transformations in
RunnableGeneratorwhen they must preserve streaming. - Use
astream_events(version="v2")for diagnosing where streaming breaks. - For structured streaming output, use JSON mode +
JsonOutputParser, not tool_calling parsers. - Test streaming behaviour in unit tests: assert that streaming yields more than one chunk.
- For web apps, verify your framework (FastAPI, Flask, Node) does not buffer the response.
- Document per-chain: "streaming friendly" or "one-shot" — the difference matters for user experience.
Frequently asked questions
StreamingResponse with an async generator that yields each chunk. Flask uses Response(generator, mimetype="text/event-stream"). Both require your web server (uvicorn, gunicorn) to not buffer — disable proxy buffering (nginx proxy_buffering off;).RunnableParallel streams multiple branches concurrently and merges chunks. Each chunk is a partial dict with one or more branches populated. Downstream steps see this progressive-dict flow.version="v2" in new code — v1 will eventually be removed.Related errors & hubs
Get the weekly AI-error digest
New fixes, provider status recaps, and one deep tutorial — every Tuesday. 8,400+ engineers.