C.W.K.
Stream
Lesson 01 of 04 · published

NDJSON, Not SSE

~18 min · streaming, ndjson

Level 0Downloader
0 XP0/41 lessons0/11 achievements
0/120 XP to next level120 XP to go0% complete

The single most important streaming difference

Cloud APIs (OpenAI, Anthropic, Google) stream over Server-Sent Events (SSE) — each chunk is data: {json}\n\n. Ollama streams over newline-delimited JSON (NDJSON) — each chunk is just {json}\n. No data: prefix, no double newlines.

If you point an SSE parser at Ollama, it sees one giant blob and waits forever for a data: that never comes. If you point a JSON parser at Ollama, it reads one whole streaming response as a single token and crashes when it hits the second {.

The shape of one NDJSON line

Each line is a complete, parseable JSON object. The interesting fields:

  • message.content — text delta (one or a few tokens).
  • done — boolean. false for incremental chunks, true on the final chunk.
  • Final chunk only: timing fields (total_duration, eval_count, eval_duration, etc.) for performance metrics.

Why Ollama chose NDJSON

NDJSON is simpler to produce server-side, simpler to parse client-side (split on newline, JSON-parse each line), and doesn't require the SSE keep-alive comment ritual. The cost is that you have to know it's not SSE before you reach for an SSE library.

Code

Anatomy of a streaming response·typescript
// What you actually receive over the wire from /api/chat with stream:true
{"model":"qwen2.5:7b","created_at":"2026-05-03T10:30:00Z","message":{"role":"assistant","content":"The"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-05-03T10:30:00Z","message":{"role":"assistant","content":" sky"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-05-03T10:30:00Z","message":{"role":"assistant","content":" is"},"done":false}
// ...many more delta chunks...
// Final chunk:
{"model":"qwen2.5:7b","created_at":"2026-05-03T10:30:01Z","message":{"role":"assistant","content":""},"done":true,"total_duration":1234567890,"eval_count":42,"eval_duration":900000000}
Don't reach for the SSE library·python
# WRONG — using sseclient on Ollama hangs forever
# import sseclient  # ❌

# RIGHT — read raw lines and parse each as JSON
import httpx, json

with httpx.stream("POST", "http://localhost:11434/api/chat",
                  json={"model": "qwen2.5:7b",
                        "messages": [{"role": "user", "content": "hi"}],
                        "stream": True},
                  timeout=None) as r:
    for line in r.iter_lines():
        if not line:
            continue
        chunk = json.loads(line)
        print(chunk["message"]["content"], end="", flush=True)
        if chunk.get("done"):
            break

External links

Exercise

Send a streaming /api/chat request with curl and pipe through jq line-by-line. Confirm each line is independently parseable. Then write the same request in your language of choice using raw line iteration (NOT an SSE library).

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.