{"slug": "why-is-my-llm-stream-empty-a-field-guide-to-broken-sse-responses", "title": "Why is my LLM stream empty? A field guide to broken SSE responses", "summary": "A developer has identified four common failure modes that cause empty LLM streams from OpenAI-compatible APIs, including reasoning-only responses, missing finish_reason, malformed SSE framing, and truncated tool calls. To address these issues, the developer built an open-source diagnostic toolkit called agent-stream-doctor that records and analyzes raw streams to pinpoint the failure class automatically.", "body_md": "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.\n\nAfter 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.\n\nSome models emit their entire answer inside a **reasoning channel** (the \"thinking\" part) and mark the actual `content`\n\nchannel as empty.\n\nThe stream *works*. Token usage is reported. Your parser is happy. Your UI shows nothing.\n\n```\npython\n# What arrives:\n{\"delta\": {\"reasoning_content\": \"Let me analyze this...\"}, ...}\n{\"delta\": {\"content\": \"\"}, \"finish_reason\": \"stop\"}\nThe 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.\n\n2. Missing finish_reason\nWhen a proxy or router truncates the final chunk, finish_reason quietly disappears — and many client libraries silently drop the message instead of raising.\n\nThe 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.\n\n3. Malformed SSE framing\nSSE looks trivial: lines of data: {...} ending with data: [DONE]. But:\n\nmulti-byte UTF-8 characters can be split across chunk boundaries\nsome proxies rewrite or strip the data: prefix\nchunks can arrive after [DONE], or the stream can end without it\nEach of these breaks parsers quietly — you lose characters in the middle of words, or the client hangs waiting for a terminator that never comes.\n\nThe fix: log the raw frames before parsing. When something looks wrong downstream, the raw log is the only witness that tells the truth.\n\n4. Truncated tool calls\nAgents 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.\n\nThe fix: accumulate arguments across deltas, validate the assembled call, and set a timeout on \"hanging\" tool states.\n\nThe workflow that catches all four\nEvery 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:\n\nObserve the raw stream first. Reason about the protocol second.\n\nThat 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:\n\nempty streams & reasoning-only responses\nmissing / inconsistent finish_reason\nmalformed SSE lines and broken [DONE] sentinels\nincomplete tool calls and truncated arguments\npip install git+<https://github.com/Mohammad-Hasan-Kaman/agent-stream-doctor.git>\n\n# record a live stream to a file\nagent-stream-doctor record --base-url <https://api.example.com/v1/chat/completions> \\\n  --model your-model --message \"Hello!\"\n\n# diagnose a captured stream\nagent-stream-doctor analyze stream.txt\n\nIt 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.\n\n---\n\n*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).*\n```\n\n", "url": "https://wpnews.pro/news/why-is-my-llm-stream-empty-a-field-guide-to-broken-sse-responses", "canonical_source": "https://dev.to/mohammadhasankaman/why-is-my-llm-stream-empty-a-field-guide-to-broken-sse-responses-4l0k", "published_at": "2026-09-02 18:18:56+00:00", "updated_at": "2026-09-02 18:23:53.113772+00:00", "lang": "en", "topics": ["large-language-models", "developer-tools", "mlops"], "entities": ["OpenAI", "agent-stream-doctor", "Mohammad-Hasan-Kaman"], "alternates": {"html": "https://wpnews.pro/news/why-is-my-llm-stream-empty-a-field-guide-to-broken-sse-responses", "markdown": "https://wpnews.pro/news/why-is-my-llm-stream-empty-a-field-guide-to-broken-sse-responses.md", "text": "https://wpnews.pro/news/why-is-my-llm-stream-empty-a-field-guide-to-broken-sse-responses.txt", "jsonld": "https://wpnews.pro/news/why-is-my-llm-stream-empty-a-field-guide-to-broken-sse-responses.jsonld"}}