Why is my LLM stream empty? A field guide to broken SSE responses
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. 1. Reasoning-only responses 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 # What arrives: {"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, yo