Most streaming LLM failures are not model failures; they are chunk-passing failures between a server and a client.
Current feeds are full of free model endpoints and free server tiers. The useful skill is not trying every new model. The useful skill is proving where a stream breaks. A recent DEV bug-smash challenge asked contributors to restore a dropped chat config in an SDK. That is the same class of silent stream loss this recorder catches.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
What you will build
A two-file SSE recorder:
-
slow_sse_server.pyfakes a slow stream. -
sse_recorder.pyprints raw chunks. - optional: run the recorder against a free MonkeyCode server/model endpoint to see the same contract.
No third-party packages. Python 3.11 or newer.
Prerequisites
- Python 3.11+
- two terminals
- 20 minutes
- optional: a free MonkeyCode endpoint with its operator-reported 30,000,000-token allowance
Step 1: fake the slow server
Create slow_sse_server.py:
import http.server
import json
import os
import time
CHUNKS = [
{'choices': [{'delta': {'content': 'Hello'}}]},
{'choices': [{'delta': {'content': ' from'}}]},
{'choices': [{'delta': {'content': ' the'}}]},
{'choices': [{'delta': {'content': ' server.'}}]},
{'choices': [{'delta': {}, 'finish_reason': 'stop'}]},
]
class Handler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header('Content-Type', 'text/event-stream')
self.send_header('Cache-Control', 'no-cache')
self.end_headers()
for payload in CHUNKS:
line = 'data: ' + json.dumps(payload) + os.linesep + os.linesep
self.wfile.write(line.encode())
self.wfile.flush()
time.sleep(0.2)
self.wfile.write(('data: [DONE]' + os.linesep + os.linesep).encode())
self.wfile.flush()
if __name__ == '__main__':
http.server.HTTPServer(('127.0.0.1', 8899), Handler).serve_forever()
Step 2: record what the client actually sees
Create sse_recorder.py:
import sys
import urllib.request
URL = sys.argv[1] if len(sys.argv) > 1 else 'http://127.0.0.1:8899/v1/chat/completions'
req = urllib.request.Request(URL, headers={'Accept': 'text/event-stream'})
with urllib.request.urlopen(req, timeout=10) as resp:
print('status', resp.status)
print('headers', dict(resp.headers))
event = []
for raw in resp:
line = raw.decode('utf-8').rstrip()
if line == '':
if event:
print('event', event)
event = []
continue
if line.startswith(':'):
print('comment', line)
continue
if line.startswith('data:'):
event.append(line[5:].strip())
if event:
print('trailing', event)
Run it:
python slow_sse_server.py
In a second terminal:
python sse_recorder.py
Expected output:
status 200
headers contain text/event-stream
event count 5
final event [DONE]
Read the raw output before trusting a client
Check these five signals before you wrap a free server with an SDK:
- number of
data:events received - presence of
finish_reason - presence of
[DONE] - blank lines between events
- any trailing event after the loop
A client abstraction can hide any of these. The recorder shows what was actually sent on the wire.
Where this breaks
Create not_sse_server.py:
import http.server
import json
class Handler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
body = json.dumps({'choices': [{'delta': {'content': 'Hello'}}]})
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.send_header('Content-Length', str(len(body)))
self.end_headers()
self.wfile.write(body.encode())
http.server.HTTPServer(('127.0.0.1', 8898), Handler).serve_forever()
Run it and point the recorder at it:
python not_sse_server.py
python sse_recorder.py http://127.0.0.1:8898/v1/chat/completions
Expected failure:
status 200
headers contain application/json
event count 1
no [DONE]
There is no blank line and no [DONE]. If your client expects an SSE delta, it may return an empty string, hang, or drop the final assistant message.
This is the same failure mode as a proxy that buffers a stream or strips newlines.
Fix checklist for a free server
- send
Accept: text/event-stream - disable output buffering if you control the proxy
- read until EOF, not until the first blank line
- print raw lines before parsing JSON
- verify the final
data: [DONE]orfinish_reason
Where MonkeyCode fits
I use this recorder as a smoke test before I trust any free model endpoint. The operator reports that MonkeyCode offers free model access, a free server option, and a 30,000,000-token allowance.
Use it for:
- small stream-contract tests
- comparing one model response path
- reproducing a dropped-chunk bug
Do not use it for:
- production traffic
- latency or quality benchmarks
- assumptions about permanence; verify the current quota on the product page
Common mistakes
- parsing only the first
data:event - treating
[DONE]as JSON - closing the connection after one blank line
- ignoring
response.statusandresponse.headers
Extension
Change sse_recorder.py to record a timestamp and byte length for every chunk. Then compare the timestamps against your free server. If the first chunk arrives after a long delay, the problem is upstream buffering, not your parser.
If you try this, post the raw event count you observe before and after adding the Accept header.
Top comments (0)