Fix JSONDecodeError: Extra data in LLM Streaming
You're streaming from an LLM API and suddenly hit json.decoder.JSONDecodeError: Extra data: line 1 column X (char Y). This guide covers the three root causes and working Python fixes for each.
Traceback (most recent call last):
File "app.py", line 42, in process_stream
data = json.loads(content)
^^^^^^^^^^^^^^^^^^^
json.decoder.JSONDecodeError: Extra data: line 1 column 27 (char 26)
Three root causes
SSE framing — missing double newlines
Server-Sent Events (SSE) separate each event with a blank line (\n\n). When a proxy or middleware strips these blank lines, consecutive data: lines merge in the buffer. The OpenAI Python SDK tries to json.loads() the combined result and hits "Extra data".
data: {"id":"chatcmpl-1","choices":[{"delta":{"content":"{"}}]}
data: {"id":"chatcmpl-1","choices":[{"delta":{"content":"name"}}]}
data: {"id":"chatcmpl-1","choices":[{"delta":{"content":":"}}]}
# ↑ No blank lines → SDK concatenates → json.loads fails
data: {"id":"chatcmpl-1","choices":[{"delta":{"content":"{"}}]}
↵ (blank line)
data: {"id":"chatcmpl-1","choices":[{"delta":{"content":"name"}}]}
↵ (blank line)
This happens most often when you're running a custom proxy or middleware. The fix is to ensure each yielded SSE chunk ends with \n\n:
def sse_frame(chunk: str) -> str: """Ensure every SSE chunk ends with the required double newline.""" if chunk.endswith('\n\n'): return chunk return chunk.rstrip('\n') + '\n\n' # In your async generator: async def stream_proxy(upstream_response): async for chunk in upstream_response.aiter_text(): yield sse_frame(chunk) # ← not just `yield chunk`
upstream_generator() for logging purposes — causing the exact error above for all users of the OpenAI Python SDK. The sse_frame() helper fixed it.
Concatenating streaming chunks before parsing
If you manually iterate a streaming response and join all chunks, you end up with the raw SSE protocol text — not JSON. Calling json.loads() on that gives "Extra data" because of the data: prefixes between events.
import httpx, json resp = httpx.post(url, headers=headers, json=body) raw = resp.text # SSE protocol text, not JSON! # raw looks like: # data: {"choices":[{"delta":{"content":"{"}}]} # # data: {"choices":[{"delta":{"content":"name"}}]} # ... data = json.loads(raw) # 💥 JSONDecodeError: Extra data
import httpx, json chunks = [] with httpx.stream("POST", url, headers=headers, json=body) as resp: for line in resp.iter_lines(): if not line.startswith("data: "): continue payload = line[6:] # strip "data: " prefix if payload == "[DONE]": break event = json.loads(payload) # parse each event delta = event["choices"][0]["delta"].get("content", "") if delta: chunks.append(delta) full_content = "".join(chunks) data = json.loads(full_content) # ✅ parse the assembled content
Or simply use the OpenAI Python SDK which handles SSE parsing internally — no manual chunk handling needed.
Model returned multiple top-level JSON values
json.loads() only accepts a single JSON value. If the model output contains additional content after the closing brace — a newline, an explanation, or a second object — you get "Extra data".
# Model said: {"name": "Alice", "age": 30} Note: I've extracted the name and age from the text. # json.loads() sees: {...}\nNote: I've extracted... json.loads(content) # 💥 JSONDecodeError: Extra data at 'Note: I'…'
import re, json def extract_json(text: str) -> dict: # Strip markdown fences first text = re.sub(r'```(?:json)?\s*', '', text).strip() # Find first { ... } block start = text.find('{') if start == -1: raise ValueError("No JSON found") depth = 0 for i, ch in enumerate(text[start:], start): if ch == '{': depth += 1 elif ch == '}': depth -= 1 if depth == 0: return json.loads(text[start:i+1]) raise ValueError("Incomplete JSON") data = extract_json(content) # ✅ ignores trailing prose
Skip all of this — StreamFix handles it automatically
StreamFix is a drop-in OpenAI-compatible proxy that fixes all three causes above — SSE framing, concatenation bugs, trailing content, markdown fences — in real-time. One base_url change.
from openai import OpenAI client = OpenAI( api_key="sk_YOUR_STREAMFIX_KEY", base_url="https://streamfix.up.railway.app/v1", # ← that's it ) # Streaming now works reliably — no JSONDecodeError stream = client.chat.completions.create( model="openai/gpt-4o-mini", messages=[{"role": "user", "content": "Return JSON: {name, age}"}], stream=True, )