Server-Sent Events(SSE)(サーバーセントイベンツ)
HTTPベースの単方向ストリーミングプロトコル。サーバーからクライアントへテキストベースのイベントを持続的に送信でき、LLM APIのトークンストリーミングで標準的に採用されている通信方式。
Server-Sent Events(SSE)とは
Server-Sent Events(SSE)は、W3Cが標準化したHTTPベースの単方向ストリーミングプロトコルです。サーバーからクライアントへ持続的にイベントを送信でき、WebSocketのような双方向通信は不要だがサーバープッシュが必要な場面で使われます。LLMのトークンストリーミングでは、OpenAI・Anthropic・Google・Mistral・Cohere など主要APIプロバイダすべてがSSEを採用しており、事実上の業界標準です。
SSEの基本仕様
HTTPレスポンスヘッダ
SSEレスポンスは通常のHTTPレスポンスですが、Content-Typeが text/event-stream に設定されます。
| ヘッダ | 値 | 目的 |
|---|---|---|
| Content-Type | text/event-stream | SSEストリームであることを宣言 |
| Cache-Control | no-cache | プロキシやCDNによるキャッシュを防止 |
| Connection | keep-alive | TCP接続を維持 |
| Transfer-Encoding | chunked | チャンク転送エンコーディング |
イベントフォーマット
各イベントは改行区切りのテキストフィールドで構成されます。
- data: イベントのペイロード(複数行可)
- event: イベント種別(省略時は「message」)
- id: イベントID(再接続時のラストイベントID送信に使用)
- retry: 再接続間隔(ミリ秒)
イベント間は空行(\\n\\n)で区切られます。
LLM APIにおけるSSEの使い方
OpenAI Chat Completions API
OpenAIのストリーミングレスポンスでは、各SSEイベントの data フィールドにJSON形式の ChatCompletionChunk オブジェクトが含まれます。choices[0].delta.content にトークンテキストが格納され、最後に data: [DONE] が送信されてストリーム終了を通知します。
Anthropic Messages API
AnthropicのSSEイベントは event フィールドでイベント種別を区別します。content_block_delta イベントの delta.text にトークンが含まれ、message_stop で完了を通知します。入力トークン数は message_start の usage.input_tokens で、出力トークン数は message_delta の usage.output_tokens で確認できます。
SSE vs WebSocket vs gRPC
| 比較項目 | SSE | WebSocket | gRPC Streaming |
|---|---|---|---|
| 通信方向 | 単方向(サーバー→クライアント) | 双方向 | 双方向 |
| プロトコル | HTTP/1.1, HTTP/2 | ws:// / wss:// | HTTP/2 |
| データ形式 | テキスト(UTF-8) | テキスト or バイナリ | Protocol Buffers |
| 自動再接続 | ブラウザAPI内蔵 | 手動実装が必要 | ライブラリ依存 |
| ブラウザ対応 | EventSource API | WebSocket API | grpc-web経由 |
| プロキシ透過性 | 高い(通常のHTTP) | 低い(Upgrade必要) | 中程度 |
| 同時接続上限 | HTTP/1.1: 6本/ドメイン | 制限なし | 制限なし |
| オーバーヘッド | テキストベースでやや大 | フレーミングのみ | 最小(バイナリ) |
LLMのトークンストリーミングには単方向送信で十分なため、実装の簡潔さ・プロキシ透過性・ブラウザ標準APIの3点からSSEが最適解とされています。
ブラウザでのSSE受信
EventSource API
ブラウザ標準の EventSource APIを使えば、数行のJavaScriptでSSEストリームを受信できます。自動再接続機能が内蔵されており、ネットワーク切断時に retry フィールドで指定された間隔で自動的に再接続を試みます。
fetch + ReadableStream
EventSource APIはPOSTリクエストに対応していないため、LLM APIの呼び出し(通常POST)には fetch APIと ReadableStream を組み合わせる方法が一般的です。レスポンスボディを getReader() で取得し、TextDecoder でデコードしてSSEイベントをパースします。
SSEの制約と対策
HTTP/1.1の同時接続制限
HTTP/1.1ではブラウザが1ドメインあたり最大6本のTCP接続しか開けないため、SSEストリームが1本開くと残り5本になります。HTTP/2ではストリーム多重化により実質的に制限がなくなるため、SSEを使うAPIエンドポイントはHTTP/2対応が推奨されます。
プロキシ・CDNのタイムアウト
Nginx(デフォルト60秒)、Cloudflare(100秒)などのリバースプロキシは長時間接続をタイムアウトする場合があります。LLMの長い回答(2,000トークン以上)ではストリーム持続時間が60秒を超えることがあるため、プロキシのタイムアウト値を適切に設定する必要があります。
FAQ
Q1: SSEはHTTP/2でも使える?
A1: はい。HTTP/2ではストリーム多重化により、1つのTCP接続上で複数のSSEストリームを並行処理できます。HTTP/1.1の6接続制限も解消されるため、HTTP/2環境でのSSE利用が推奨されます。
Q2: SSEで画像やバイナリデータは送れる?
A2: SSEはUTF-8テキスト専用です。バイナリデータを送る場合はBase64エンコードが必要ですが、オーバーヘッドが33%増加します。マルチモーダルLLMの画像出力にはWebSocketまたはgRPCの使用が適切です。
Q3: SSEストリームが途切れた場合の再接続は?
A3: ブラウザの EventSource APIは自動再接続機能を持ち、サーバーの retry フィールドで間隔を制御できます。fetch + ReadableStream の場合は手動でリトライロジックを実装し、最後に受信した id を Last-Event-ID ヘッダで送信して途中から再開します。