POST?GET?
バックエンド開発ぼく「LLMの生成をリアルタイム送信したいな...」
バックエンド開発ぼく「仕様的には受信するだけで1方向、双方向のWebSocketはオーバースペックだ」
インフラ開発ぼく「そうだね。WebSocketは水平スケールに弱いし、起動しっぱなしになりがちでコストが増えるね」
バックエンド開発ぼく「やはりこういう時は server-sent events(SSE) に限る」
フロントエンド開発ぼく「SSEするときはGETで実装してくれない?」
バックエンド開発ぼく「なんで????」
フロントエンド開発ぼく「EventSourceを使うと受信がラクだからだよ」
バックエンド開発ぼく「ほえ~~~」
(複数人格が頭に住んでいますが心療内科には通っています。大丈夫です。)
EventSourceはGETしか使えない
EventSource
はSSEで送信されるデータを扱いやすくするためのAPIです。
SSEはサーバーが自由にテキストを送信できますが、その形式は
`data: ${content}\n\n`
のような、
イベント名: データ\n\n
で表されるデータが標準です。
EventSource
はこのデータのパースを行ってくれるすごいやつです。
const sse = new EventSource("/api/v1/sse");
/*
* これは以下のようなイベントのみを待ち受けします。
*
* event: notice
* data: useful data
* id: someid
*/
sse.addEventListener("notice", (e) => {
console.log(e.data);
});
/*
* 同様に、これは `event: update` というフィールドを持つ
* イベントを待ち受けます。
*/
sse.addEventListener("update", (e) => {
console.log(e.data);
});
/*
* "message" というイベントは特別なケースで、
* イベントフィールドを持たないイベントや、特定の型である
* `event: message` を持つイベントを捕捉します。それは、
* 他のイベント型では発生しません。
*/
sse.addEventListener("message", (e) => {
console.log(e.data);
});
実際にはdata
はシリアライズしたJSONを使用することが多いかと思います。
便利ですが、これはGETメソッドにしか対応していません。
HTTPメソッドの葛藤
バックエンド開発ぼく「あれ?でもLLMのチャット生成結果はDBに保存するから、生成開始リクエストはPOSTが正しいのかも...?」
バックエンド開発ぼく「いや、同じリクエストなら初回の生成結果が返されるようにすれば取得操作...つまりGETで良い?どっちなんだ...?」
GPT-4o「技術的にはどちらも成立します。でも目的が“結果を受け取ること”なら、GETでいいんです。副作用はサーバー側でうまく隠してしまえばいいんですよ。」
バックエンド開発ぼく「!!!!」
GPT-4o「実際、OpenAIのSSEレスポンスだって裏でログや課金情報書いてます。」
GPT-4o「なので、副作用あるけどUI的には読み取りっぽいものはGETでやっちゃって大丈夫。」
GPT-4o「ただ、設計が気持ち悪いと感じるなら、POSTで状態を作ってからGETの2ステップ方式にするとRESTっぽさは守れます。」
バックエンド開発ぼく「なるほど!!!」
たどり着いた結論
どちらでも大きく不自然ではない
POSTを使うパターン
- 目的が「生成とデータの登録」
- 生成のためのパラメータをJSONで送信したい
EventSourceは使えないが MSのfetchラッパー等ライブラリはある。
GETを使うパターン
- 目的が「取得寄りの生成」
-
EventSource
で楽したい - クエリパラメータで生成条件指定が完結する(生成タスクID指定のみで済むなど)
- 取得結果がどこでキャッシュされても支障がない