sse_server.py

script

← Back to skill

Content hash: 74eb8bc1c826618a0067fb0c438a2700d3be76014ef0b18c07269c863fc63359
#!/usr/bin/env python3
"""SSE streaming server: demonstrates server-sent events for LLM token streaming.

Run: python sse_server.py
Then: curl -N -X POST http://localhost:8000/chat -H "Content-Type: application/json" -d '{"messages": "hello"}'
"""
from __future__ import annotations

import asyncio
import json
import sys
import time

try:
    from fastapi import FastAPI
    from fastapi.responses import StreamingResponse
    import uvicorn
except ImportError:
    print("Install: pip install fastapi uvicorn")
    sys.exit(1)

app = FastAPI()


# Simulate an LLM streaming provider (replace with real provider: openai, vllm, etc.)
async def mock_llm_stream(messages: str) -> str:
    """Simulated LLM that yields tokens one at a time with realistic delays."""
    response = f"Echo: {messages[:40]}. This is a streaming response with multiple tokens."
    words = response.split()
    for i, word in enumerate(words):
        await asyncio.sleep(0.05)  # simulate per-token latency
        yield word + (" " if i < len(words) - 1 else "")


async def event_stream(messages: str):
    """SSE event generator: streams tokens as delta events, finishes with done."""
    try:
        token_buffer = []
        async for token in mock_llm_stream(messages):
            token_buffer.append(token)
            # Batch small tokens to reduce event count without hurting perceived latency
            if len(token_buffer) >= 3 or token.endswith(" "):
                delta = "".join(token_buffer)
                token_buffer.clear()
                yield f"event: delta\ndata: {json.dumps({'delta': delta})}\n\n"

        # Flush remaining tokens
        if token_buffer:
            delta = "".join(token_buffer)
            yield f"event: delta\ndata: {json.dumps({'delta': delta})}\n\n"

        yield json.dumps({"event": "done", "data": {"status": "ok", "usage": {"completion_tokens": 24}}})
        # SSE format for done event
        yield f"event: done\ndata: {json.dumps({'status': 'ok'})}\n\n"

    except asyncio.CancelledError:
        # Client disconnected: cancel upstream LLM call
        print("[server] Client disconnected — cancelling upstream LLM call")
        yield f"event: error\ndata: {json.dumps({'error': 'cancelled'})}\n\n"


@app.post("/chat")
async def chat(req: dict):
    """POST endpoint returning SSE stream."""
    messages = req.get("messages", "")
    return StreamingResponse(
        event_stream(messages),
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "X-Accel-Buffering": "no",   # bypass nginx buffering
            "Connection": "keep-alive",
        },
    )


@app.get("/health")
async def health():
    return {"status": "ok"}


def main() -> None:
    print("SSE streaming server on http://localhost:8000")
    print("Test: curl -N -X POST http://localhost:8000/chat \\")
    print('     -H "Content-Type: application/json" -d \'{"messages":"hello"}\'')
    uvicorn.run(app, host="127.0.0.1", port=8000, log_level="warning")


if __name__ == "__main__":
    main()