🍳

【Next.js】 Rewritesを使うとVercel AI SDK でストリーミングができない

2024/12/20に公開

問題

ChatGPTのようにリアルタイムに出力をクライアントへ送信してぬるぬるさせたい。
ところが、なぜかストリーミングせず、生成が終わったタイミングで出力がすべて一度に送られてくるので、調査開始。

技術構成

フロントエンドはNext.jsでバックエンドはHonoで実装している(伏線)。

調査

フロントエンド側が問題なのか、バックエンド側が問題なのかをはっきりさせるために、それぞれで検証した。

curlコマンドでAPIを直接叩く

curlコマンドを使って、Honoサーバー側から返されるレスポンスを見てみた。

curl -i -X POST http://localhost:8080/chat

出力が順次出てきたので、期待される結果となった。バックエンド側は問題なく動作しているようである。

クライアント側

クライアント側でai-sdkの提供する機能を使用せず、fetchからAPIを呼び出すようにしてみた。
こちらはやはりすべて一度に出力されてしまう。

原因

バックエンド側もクライアント側も問題ないとなると、Next.jsのプロキシ部分がどうも怪しそうである。
調べていると、それらしきissueを発見。
https://github.com/vercel/next.js/issues/45048

/api宛のリクエストは、Next.jsのRewrite機能を用いて、Honoが実行しているlocalhost:8080へフォワードするように設定していたが、これが原因だった。
Next.jsのRewriteでは、text/event-stream ヘッダの付くストリーミングレスポンスに対応しておらず、すべてのレスポンスが終わるまでバッファリングしてから、一度に送ってしまうようである。

解決策

Next.jsのRewriteは使えないので、/api内に自分でプロキシを実装して解決。

api/chat/route.ts
export const runtime = "edge";
export async function POST(req: Request) {
  // localhost:8080へfetchし、そのレスポンスのbodyをそのまま返す
  const res = await fetch("http://localhost:8080/chat", {
    method: "POST",
    body: await req.text(),
    headers: { "Content-Type": "application/json" },
  });

  return new Response(res.body, {
    headers: {
      "Content-Type": "text/event-stream",
    },
  });
}

Discussion