TinkerLab
DIAGNOSTIC TREE // SSE-HTTP200-STREAM-DROP (3 ROOT CAUSES)

LLM Streaming API Fails or Drops After HTTP 200 OK

Handle mid-stream SSE error events, reverse-proxy buffering/timeout cuts, and unclosed JSON from max_tokens truncation.

[KEY VERIFIED TAKEAWAY / TL;DR]This diagnostic decision tree for "LLM Streaming API Fails or Drops After HTTP 200 OK" provides 4 interactive nodes and 3 hardware-verified root-cause fixes (Fix: Parse In-Stream Error Events Instead of Trusting res.ok Alone; Fix: Disable Proxy Buffering & Increase Read Timeout for Reasoning/Large Prompts; Fix: Inspect finish_reason === "length" Before Parsing Structured JSON), paired with our reproducible guide "sse-http200-stream-errors".
LLM Streaming API Fails or Drops After HTTP 200 OK
Step 1: Identify How the Stream Terminated

Inspect the raw SSE chunks or logs after HTTP 200 OK headers arrive. What happens before the failure?

Diagnostic Probe Command
curl -i -N http://127.0.0.1:11434/v1/chat/completions -H "Content-Type: application/json" -d '{"model":"qwen2.5:7b","stream":true,"messages":[{"role":"user","content":"Hello"}]}'

Select what your environment outputs:
DIAGNOSTIC PROBE SEQUENCE // 完整分步探测命令与症状分支

All 1 Terminal Diagnostic Probes in "LLM Streaming API Fails or Drops After HTTP 200 OK"

Complete static reference of every read-only probe command, baseline hint, and branch transition in this decision tree.

Step 1: Identify How the Stream Terminatedstep-stream-symptom

Inspect the raw SSE chunks or logs after HTTP 200 OK headers arrive. What happens before the failure?

curl -i -N http://127.0.0.1:11434/v1/chat/completions -H "Content-Type: application/json" -d '{"model":"qwen2.5:7b","stream":true,"messages":[{"role":"user","content":"Hello"}]}'
Symptom Branches:
  • • An SSE chunk contains {"error": ...} or type: "error" mid-stream→ sol-sse-midstream-error-handler
  • • Connection freezes during long prompt evaluation or drops at exactly 60s→ sol-proxy-buffering-timeout
  • • Stream finishes with [DONE], but downstream JSON.parse fails→ sol-finish-reason-length
ALL VERIFIED FIXES IN THIS TREE // 本树涵盖的全部根因与修复方案一览

3 Verified Root Causes, Fix Configs & Commands in "LLM Streaming API Fails or Drops After HTTP 200 OK"

Fix: Parse In-Stream Error Events Instead of Trusting res.ok Alone

sol-sse-midstream-error-handler

Once an LLM server flushes HTTP 200 headers to start an SSE stream, it can no longer change the HTTP status code to 500 or 529 if an overload or OOM occurs mid-generation.

  1. Check every parsed SSE JSON payload for `event.error` or `event.type === "error"` before appending delta text.
  2. Discard partial corrupted output and trigger a bounded retry with exponential backoff.
const payload = JSON.parse(rawDataLine);
if (payload.error || payload.type === 'error') {
  throw new Error(`Mid-stream error after HTTP 200: ${payload.error?.message || JSON.stringify(payload)}`);
}

[BOUNDARY]Documented explicitly in Claude API & OpenAI-compatible streaming specifications.

Verify: node sse-stream-guard.mjs

Fix: Disable Proxy Buffering & Increase Read Timeout for Reasoning/Large Prompts

sol-proxy-buffering-timeout

Local prompt evaluation on 16K+ tokens can take 20–90 seconds before the first token is emitted. Nginx or gateway proxies default to 60s read timeout.

  1. Set proxy_buffering off and proxy_read_timeout 300s in Nginx / reverse proxy.
  2. Send periodic SSE keep-alive comments or set client chunk timeout >= 45s.
# Nginx location block for LLM SSE endpoints
location /v1/ {
    proxy_pass http://127.0.0.1:11434/v1/;
    proxy_buffering off;
    proxy_cache off;
    proxy_read_timeout 300s;
    chunked_transfer_encoding on;
}

[BOUNDARY]Also check Cloudflare Tunnel / proxy 100-second idle timeout if exposing endpoints externally.

Verify: nginx -t && sudo systemctl reload nginx

Fix: Inspect finish_reason === "length" Before Parsing Structured JSON

sol-finish-reason-length

The model hit max_tokens (or num_predict) mid-JSON-object. The stream closed cleanly with HTTP 200 and [DONE], leaving half-written JSON.

  1. Verify choice.finish_reason === "stop" before running JSON.parse.
  2. For reasoning models (DeepSeek-R1), strip <think>...</think> blocks and budget at least 2048 tokens for output.
if (lastChunk.choices?.[0]?.finish_reason === 'length') {
  throw new Error('Output truncated by max_tokens limit before JSON closed');
}

[BOUNDARY]Combining response_format: { type: "json_object" } with low max_tokens still truncates if limit is reached!

Verify: echo "Verified finish_reason guard"
PAIRED REPRODUCIBLE GUIDE // 配套实测指南与硬件环境基线

Post-HTTP-200 SSE Stream Errors: Failure Conditions & Recovery Boundaries

Why simple HTTP status-code tables fail in streaming LLM apps: handling mid-stream SSE error events, proxy idle cuts, and max_tokens JSON truncation.

TESTED HARDWARE & OS

Node.js 22.13+ SSE Client · Applicable to Claude API, OpenAI API, and Local Ollama / vLLM

PINNED STACK VERSIONS

Node.js 22.13+ · Anthropic Messages SSE · OpenAI Chat Completions SSE

VERIFIED (2026-10-23)

Applies to Both Local & Cloud Streaming APIs — Includes local zero-cost SSE guard client to test your parser against mid-stream error events.