Skip to content

SSEストリーミング順序・消失問題の修正

問題の概要

OpenWebUIでチャット回答のストリーミング表示に以下の問題が発生:

  1. 参考文献セクションの途中まで出力された後、セクション全体が消える
  2. 表示順序が入れ替わる(途中で戻る)
  3. FW(ファイアウォール/プロキシ)経由の場合のみ発生(ローカルでは正常)
  4. 別のチャットを開いて戻るとDBから正しく表示される

構成

agentic-ai-srv (OpenAI互換API) → FW/Proxy → OpenWebUI (OpenAI API設定)

根本原因

FWがSSEストリームをバッファリング・分割し、OpenWebUIがチャンクを正しく再構築できない

参考: OpenWebUI Discussion #13477

SSE(Server-Sent Events)はHTTP上の長時間接続を使用するため、FW/プロキシがこれを正しく処理しない場合、以下の問題が発生する:

  • チャンクのバッファリングと一括送信
  • 順序の入れ替わり
  • タイムアウトによるチャンクの破棄
  • 接続の早期切断

解決策

サーバー側(agentic-ai-srv)で以下の対策を実装:

1. レスポンスヘッダーの強化

return StreamingResponse(
    generate_stream(),
    media_type="text/event-stream",
    headers={
        "Cache-Control": "no-cache, no-store, no-transform, must-revalidate",
        "Connection": "keep-alive",
        "X-Accel-Buffering": "no",      # Nginx
        "X-Content-Type-Options": "nosniff",
        "Pragma": "no-cache",
        "Expires": "0",
    }
)

2. チャンクサイズの制御

大きなテキストを50文字ごとに分割して送信:

MAX_CHUNK_SIZE = 50
for i in range(0, len(delta), MAX_CHUNK_SIZE):
    sub_delta = delta[i:i + MAX_CHUNK_SIZE]
    chunk = {
        "id": completion_id,
        "object": "chat.completion.chunk",
        "created": int(time.time()),
        "choices": [{"index": 0, "delta": {"content": sub_delta}, "finish_reason": None}]
    }
    yield f"id: {chunk_id}\ndata: {json.dumps(chunk)}\n\n"
    chunk_id += 1
    await asyncio.sleep(0.005)  # 5ms遅延

3. Keep-alive間隔の短縮

# 変更前: 5秒
KEEPALIVE_INTERVAL = 5

# 変更後: 1秒
KEEPALIVE_INTERVAL = 1

4. SSEイベントIDの追加

各チャンクにイベントIDを付与して順序を保証:

chunk_id = 0
yield f"id: {chunk_id}\ndata: {json.dumps(chunk)}\n\n"
chunk_id += 1

修正ファイル

ファイル 変更内容
server/app.py Keep-alive間隔を5秒→1秒に短縮
server/app.py 大きなテキストを50文字ごとに分割送信
server/app.py 各チャンク送信後に5ms遅延を追加
server/app.py レスポンスヘッダーにPragma, Expires追加
server/app.py SSEイベントIDを追加

ブランチ

feature/fix-streaming-chunk-order

検証方法

  1. 修正後、FW経由でOpenWebUIからテスト
  2. 参考文献を含む長い回答が正しく表示されることを確認
  3. テキストの順序が入れ替わらないことを確認
  4. ブラウザDevToolsのNetworkタブでSSEイベントを監視

代替案: FW設定の変更

FW管理者に確認すべき設定:

  1. SSE/長時間HTTP接続のバッファリング無効化
  2. アイドルタイムアウトを60秒以上に設定
  3. text/event-streamを即時転送する設定
  4. SSEストリームをWAF検査対象から除外