Content hash: 6bf6c1400b3df972dcb106ad3af989ecc50f083f70523d2239a130407b5f21f0
## SSE Client Patterns
### Browser: EventSource (GET only)
```javascript
const es = new EventSource('/chat?messages=hello');
es.addEventListener('delta', (e) => {
const { delta } = JSON.parse(e.data);
outputEl.textContent += delta;
});
es.addEventListener('done', (e) => {
console.log('Stream complete');
es.close();
});
es.addEventListener('error', (e) => {
console.error('Stream error');
es.close();
});
```
### Browser: fetch with streaming (POST)
```javascript
const response = await fetch('/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ messages: 'hello' }),
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// Parse SSE events from buffer
const lines = buffer.split('\n\n');
buffer = lines.pop(); // incomplete event stays in buffer
for (const line of lines) {
if (line.startsWith('event: delta')) { /* handle */ }
if (line.startsWith('event: done')) { /* handle */ }
}
}
```
### Python client
```python
import aiohttp, json
async with aiohttp.ClientSession() as session:
async with session.post(
'http://localhost:8000/chat',
json={'messages': 'hello'}
) as resp:
async for line in resp.content:
text = line.decode().strip()
if text.startswith('data:'):
data = json.loads(text[5:])
print(data.get('delta', ''), end='', flush=True)
```
### Nginx reverse proxy config
```nginx
location /chat {
proxy_pass http://backend:8000;
proxy_buffering off; # CRITICAL: don't buffer SSE
proxy_cache off;
proxy_set_header X-Accel-Buffering no;
proxy_read_timeout 300s;
chunked_transfer_encoding on;
}
```
### Key metrics to track
- **TTFT** (time-to-first-token): first user-visible token
- **Tokens/sec**: sustained streaming speed
- **Cancellation rate**: % of streams abandoned mid-response