Streaming AI output does not require websockets, a broadcast server or a queue worker. Server-Sent Events over a plain HTTP response is enough.
The chain is:
- The driver opens a streaming HTTP request to the provider and reads the body in chunks.
- It yields a
StreamEventfor everydata:line it parses. ChatServiceconsumes that generator, accumulates the text, and re-yields.- The controller echoes each event as SSE and flushes.
- The browser reads the response body with
fetchand aReadableStreamreader.
Because every layer is a generator, nothing buffers the whole response in memory, and the assistant message row is updated once at the end with the final text and usage.
The gotchas
- Output buffering.
ob_flush()thenflush()after every event, and setX-Accel-Buffering: noso nginx does not hold the response. - Compression. gzip on a streamed response defeats the point. Disable it for this route.
- Timeouts. Raise
request_terminate_timeoutin PHP-FPM; the default will cut long generations off mid-sentence.