If you have ever called an OpenAI-compatible API with streaming enabled and received... nothing, you are not alone. No error, no exception — just a stream that "completes" successfully while your UI stays empty.
After debugging dozens of these cases — and building an open-source toolkit to automate the diagnosis — I keep seeing the same four failure modes. Here is the field guide I wish I had.
Some models emit their entire answer inside a reasoning channel (the "thinking" part) and mark the actual content
channel as empty.
The stream works. Token usage is reported. Your parser is happy. Your UI shows nothing.
python
{"delta": {"reasoning_content": "Let me analyze this..."}, ...}
{"delta": {"content": ""}, "finish_reason": "stop"}
The fix: always inspect the delta fields your model actually uses, not just content. If your client only reads choices[0].delta.content, a reasoning-only response is indistinguishable from an empty one.
2. Missing finish_reason
When a proxy or router truncates the final chunk, finish_reason quietly disappears — and many client libraries silently drop the message instead of raising.
The fix: treat a missing finish_reason as a red flag, not a quirk. Log it. Alert on it. A stream that ends without stop, length, or tool_calls did not end — it was cut.
3. Malformed SSE framing
SSE looks trivial: lines of data: {...} ending with data: [DONE]. But:
multi-byte UTF-8 characters can be split across chunk boundaries
some proxies rewrite or strip the data: prefix
chunks can arrive after [DONE], or the stream can end without it
Each of these breaks parsers quietly — you lose characters in the middle of words, or the client hangs waiting for a terminator that never comes.
The fix: log the raw frames before parsing. When something looks wrong downstream, the raw log is the only witness that tells the truth.
4. Truncated tool calls
Agents assemble tool calls from multiple deltas. If the stream dies halfway, you are left with partial tool_call deltas that never merge into a complete call — and an agent that hangs forever waiting for arguments that will never arrive.
The fix: accumulate arguments across deltas, validate the assembled call, and set a timeout on "hanging" tool states.
The workflow that catches all four
Every one of these bugs shares one property: the protocol layer lies to you. The stream reports success while the payload is broken. So the fix is always the same:
Observe the raw stream first. Reason about the protocol second.
That is exactly why I built agent-stream-doctor — an open-source diagnostic toolkit (Python, zero dependencies for the core) that records raw OpenAI-compatible streams and pinpoints the failure class automatically:
empty streams & reasoning-only responses
missing / inconsistent finish_reason
malformed SSE lines and broken [DONE] sentinels
incomplete tool calls and truncated arguments
pip install git+<https://github.com/Mohammad-Hasan-Kaman/agent-stream-doctor.git>
agent-stream-doctor record --base-url <https://api.example.com/v1/chat/completions> \
--model your-model --message "Hello!"
agent-stream-doctor analyze stream.txt
It ships with 16 tests running in CI on Python 3.9–3.13, and works offline on captured stream files — so you can diagnose a failure that happened in production last night.
---
*This article was originally published on [my portfolio](https://mhkaman.com/en/articles/why-is-my-llm-stream-empty/), where I write about building AI systems end-to-end. I'm a full-stack developer (Python / Django / C#) open to remote opportunities — say hi on [GitHub](https://github.com/Mohammad-Hasan-Kaman).*