Fixing AI stream parsing before Friday’s demo
It is Thursday afternoon, and your team’s AI chat demo works until an answer suddenly stops halfway through a sentence. Another run shows a JSON error. A third prints the same words twice. Before changing models or adding retries, check the stream parser.
Server-sent events, usually shortened to SSE, let a server send a sequence of text events over one HTTP response. They are common in AI streaming APIs because your application can display an answer as it arrives. The format is small, but the network does not deliver it in convenient pieces.
Network chunks are not complete events
An SSE response normally uses the content type text/event-stream. Its body contains lines such as data: followed by a payload. A blank line ends an event. The server can keep sending more events without closing the response.
Your HTTP client, however, receives chunks of bytes. One chunk might contain three events. Another might contain only the first half of the word data. Chunk boundaries have no relationship to event boundaries.
This makes calling JSON.parse on each chunk unreliable. A chunk can end inside a JSON string, and the next chunk can begin with the rest of that string. Even when every event carries valid JSON, individual chunks often do not.
UTF-8 characters can cross chunk boundaries
SSE uses UTF-8 text. Some characters, including many emoji and characters used in languages worldwide, require multiple bytes. The network can split those bytes between reads.
Use one streaming text decoder for the response. In JavaScript, that means keeping a TextDecoder instance and decoding each byte chunk with the stream option enabled, then flushing the decoder when reading finishes. A TextDecoderStream is another option. Creating a fresh decoder for every chunk can produce replacement characters.
Keep decoding separate from parsing. First turn incoming bytes into text safely. Then feed that text into a parser that remembers unfinished lines and events between reads.
Blank lines and data fields define each event
A parser needs more than a split on two newline characters. SSE accepts LF, CRLF, and CR line endings. A CRLF pair can itself arrive across two chunks. Use a tested SSE parser, or make your line reader handle these cases explicitly.
- A blank line ends the current event and triggers dispatch when it contains data.
- Multiple data: lines contribute to one payload, joined with newline characters.
- A line starting with a colon is a comment, often used as a heartbeat. It is not application data.
- Fields such as event:, id:, and retry: have their own meanings. They are not additional JSON fragments.
Split a field line at its first colon. If the value starts with one space, remove that one space. Avoid trimming the whole value: leading and trailing spaces can be intentional content. A comment-only heartbeat should not produce an empty message in your chat window.
JSON and completion markers belong to the API
SSE defines the event envelope; it does not require JSON inside it. Parse JSON only after assembling a complete event, and only when the API specifies a JSON payload.
Some AI APIs send a marker such as [DONE]. That marker is an API convention, so check for it before attempting JSON parsing. Likewise, inspect the documented payload structure before appending text. Events may carry usage information, tool arguments, or status updates instead of visible answer text.
Connection closure does not prove completion
Check the HTTP status and content type before starting the SSE parser. An authentication failure may return an ordinary JSON error response.
If the connection closes with an unfinished event, treat that as an interruption. Standard SSE parsing does not dispatch an event that lacks its terminating blank line. Track the API’s documented completion signal separately from the end of the HTTP response.
Also avoid blindly repeating a generation request after a disconnect. That can create another generation, duplicate displayed text, and add charges. Browser EventSource has reconnection behavior, while a fetch-based POST stream needs its own recovery policy.
A small test set before the demo
Test your parser with a saved response split at every possible byte boundary, including inside an emoji and between CR and LF. Include several events in one chunk, multiline data, heartbeats, and an interrupted final event. The parsed result should stay consistent regardless of chunk size.
For your AI integration, RelayVano is a practical pay-as-you-go relay with an API base of https://api.relayvano.com and support for OpenAI-compatible SDKs. Sign up to connect your application, then verify its streaming behavior with these same boundary tests.