langchain-ai/langchain · error · RuntimeError

Stream finished without producing a message

Error message

Stream finished without producing a message

What it means

`RuntimeError` raised by the `output` property of a sync chat-model stream accumulator after the stream is fully drained but no `AIMessage` was assembled. The property blocks until the stream finishes (`_drain()`), re-raises any stream error, and treats a finished stream with no output message as an internal invariant violation.

Source

Thrown at libs/core/langchain_core/language_models/chat_model_stream.py:1233

        return self._reasoning_proj

    @property
    def tool_calls(self) -> SyncProjection:
        """Tool calls — iterable of `ToolCallChunk` deltas.

        `.get()` returns finalized `list[ToolCall]`.
        """
        return self._tool_calls_proj

    @property
    def output(self) -> AIMessage:
        """Assembled `AIMessage` — blocks until the stream finishes."""
        self._drain()
        if self._error is not None:
            raise self._error
        if self._output_message is None:
            msg = "Stream finished without producing a message"
            raise RuntimeError(msg)
        return self._output_message

    # -- Raw event iteration (replay buffer) -------------------------------

    def __iter__(self) -> Iterator[MessagesData]:
        """Iterate raw protocol events with replay-buffer semantics."""
        if self._ensure_started is not None:
            self._ensure_started()
        cursor = 0
        while True:
            if cursor < len(self._events):
                yield self._events[cursor]
                cursor += 1
            elif self._error is not None:
                raise self._error
            elif self._done:
                return
            elif self._request_more is not None:

View on GitHub (pinned to e32fa9a52e)

Solutions

  1. Ensure the underlying `_stream` (or v2 event generator) yields at least one chunk that produces a message, even an empty `AIMessageChunk`.
  2. Check `stream._error` / `_drain()` behavior first: an upstream error can end the stream early and leave output unset — fix the upstream error.
  3. In tests with fake streams, yield a minimal `ChatGenerationChunk(message=AIMessageChunk(content=""))`.
  4. Guard access: only read `.output` after confirming events were produced.

Example fix

# before
async def _stream(self, messages, **kw):
    return
    yield  # empty stream -> RuntimeError on .output

# after
async def _stream(self, messages, **kw):
    yield ChatGenerationChunk(message=AIMessageChunk(content="done"))
Defensive patterns

Strategy: try-catch

Validate before calling

events = list(stream)  # or track chunks seen
if not events:
    raise ValueError("stream produced no events; refusing to read .output")

Type guard

def stream_has_output(stream) -> bool:
    stream._drain()
    return stream._output_message is not None

Try / catch

try:
    msg = stream.output
except RuntimeError as e:
    if "without producing a message" in str(e):
        msg = AIMessage(content="")  # or surface a domain-specific error
    else:
        raise

Prevention

When it happens

Trigger: Accessing `stream.output` on a stream that completed without ever producing a message — e.g. a `_stream`/protocol implementation that yields no events or only empty chunks, or a stream that was exhausted before any message-start event arrived. Also raised if the stream ended immediately due to an upstream short-circuit that did not set `_output_message`.

Common situations: Custom chat model subclasses whose `_stream` yields nothing; mock/fake streams in tests that forget to emit a message; providers that return an empty stream on unusual API responses; misconfigured protocol event generators.

Related errors


AI-assisted analysis of langchain-ai/langchain@e32fa9a52e (2026-08-14). Data as JSON: /api/errors/aeb45b5c80173f73. Report an issue: GitHub.