AI チャットの SSE(ストリーミング)をローカル・本番で確認した手順と、環境差分で再発しうる罠。ai_artifacts / audit_logs を厚くする理由と、本文をサーバログに出さない方針。
体感で言うと、チャットは ストリーミングが正義に近い。待ち時間が「無」にならなくても、文字が流れるだけで人は我慢できる。daily-snap でも、遅延と戦うより先に SSE で体感を守る方向に寄せた。
当初の下書きでは、ローカルと本番で挙動がズレうる前提から「本番は未検証」と書いていた。プロキシ、HTTP/2、タイムアウト、Cookie 周りが絡むと、ローカルと本番は別ゲームになりがちだからだ。2026-05-01 時点で、https://snap.yutok.dev 上でも開発者ツールの Network で該当リクエストが text/event-stream になっていることを確認し、最低限の本番チェックは通した(以降インフラを変えたら再確認が必要)。
それでも「本番は別腹」という警告は残す。環境を一つ変えるだけで、バッファリングやタイムアウトが再び顔を出すからだ。
もう一本柱になるのが 監査だ。モデル名や遅延、参照した範囲、生成の種類、推定コストみたいなメタは、ai_artifacts や audit_logs 側に寄せて 詳細に残す方針。一方で、本文や会話そのものをサーバログに垂れ流すのは避けたい。個人利用とはいえ、ログ基盤の行き先は想像以上に広い。
schema.prisma で見える監査の形正は web/prisma/schema.prisma。ざっくり、AIArtifact は kind(ArtifactKind enum)、model / latencyMs / tokenEstimate / promptVersion / cacheKey / cacheHit / metadata など。AuditLog は action と metadata。ChatMessage にも model / latencyMs / tokenEstimate / agentName が載る。本文は DB に残しても サーバログに垂れ流さない、という前提とセットで読むと分かりやすい。
この設計は気持ちがいい反面、実装と運用のコストも増える。フィールドが増えるほど、マイグレーションとプライバシー説明の責務も増える。でも「後から言い訳できない」領域だと思っている。AI は便利だけど、説明責任のかかる部品だからだ。
次回は、ロマンが強い PWA / オフライン。理想と現状(開発途中)を分けて書く。
EventStream または fetch / xhr を切り替えつつ、チャット送信直後のリクエストを探す。eventsource 相当(表示はブラウザ依存だが、レスポンスがストリーム扱い)か、Headers の content-type に text/event-stream が載っているかを見る。proxy_buffering off 等)、タイムアウトを疑う。Secure / SameSite / パスが API と揃っているか、04-17 の proxy / secureCookie の話とセットで見る。ローカルで「一発で event-stream」と出ても、本番で同じとは限らない。環境を変えたら 1〜3 をもう一度、が運用の現実解に近い。
体験の核は速さだけじゃなくて、待ち方だ。ストリーミングは待ち方を変える。だから手を抜くとユーザーが一番敏感になる。
本番確認の結果は、いつ・何を見て・何が OK だったかを一行でも残す。リバースプロキシや Node のバージョンを上げたら、同じチェックを繰り返す。感覚だけの「直った」は三週間後に消える。
開発者ツールの Network で event-stream になっている行、レスポンスヘッダの抜粋(秘密はマスク)、可能ならサーバ側の監査レコード画面や DB 行のメタだけが見えるモザイク済みスクショ。本文は載せない。
OpenAI だけに寄せたのは、品質だけじゃなく 評価の分裂を避けるためでもある。マルチプロバイダは強いが、失敗の切り分けが増える。個人アプリでは「まず動く一本」を優先した。
この回のまとめは、ストリーミングは体験の主戦場、監査は信頼の主戦場。どちらも「速さ」だけ見ると事故る。だから本番未検証を明記しつつ、確認手順までセットで持つ。
補足として、利用枠(1日あたりの上限)みたいな制御は、個人アプリでも「うっかり破産」防止になる。体験を殺さない範囲で、数字は最初から置いておくのが楽だった。
あとがきに近いが、SSE は仕様というより 挙動の寄せ集めに見えることがある。だから環境ごとに確認が要る、というのがこの回の核心だ。
daily-snap 開発ログ
前: カレンダーの色まで味方にした…
次: オフライン日記はロマン。現実は IndexedDB と殴り合い(途中)
索引: 04-02 · … 04-30
Google OAuth・許可リスト・JWT セッションの方針と、middleware から proxy へ寄せた経緯。HTTPS 背後での secureCookie、Docker 本番との相性。04-17 前後のコミットを手がかりに。
dairy-snap に MAS(マルチエージェント)を入れた理由と、オーケストレータ+サブエージェント分割の効用。04-10 前後の変更と、品質・拡張性・レイテンシのトレードオフの話。
dairy-snap を Docker / Cloud Build / Cloud Run 気味に寄せるときに踏んだ地雷の型。standalone、prisma generate、Node heap、ダミー DATABASE_URL、PORT。04-16 前後のコミット列を軸に、個人開発でもハマる点を整理する。
天気(Open-Meteo)と Google カレンダーのキャッシュ・分類が、日記と AI の文脈にどう効くか。entry-weather・WeatherAmPmDisplay・weather-tool などコード上の出所と、設計意図・本番運用をまとめる。
daily-snap の画像アップロード。最大辺2048px、AVIF優先(非対応ならWebP)の圧縮、ストレージ抽象と本番 GCS 前提。日記アプリで画像が重い問題への殴り方。
Prisma + Postgres で daily-snap のテーブル責務を切った話。ベクトル検索は未実装だが、エントリ・チャット・画像・ai_artifacts・memory 系をどう置くか。次に足すならどこか。