🧠

Vercel AI SDKでストリーミングUIを実装してみた - LLMアプリのUX改善への取り組み

に公開

はじめに

こんにちは、ispecでエンジニアをしている堀です。

現在私は、LLMを活用したプロダクトのプロトタイピングを行っています。Amazon BedrockのClaude Sonnet 4を使用し、バックエンドとフロントエンドはNext.jsを使って実装しているのですが、LLMからの応答を待つ間のローディング体験に課題を感じていました。

https://aws.amazon.com/jp/bedrock/

課題:LLMの応答待ちでユーザが不安になる

LLMは高性能になるほど応答時間が長くなる傾向があります。特にClaude Sonnet 4のような大規模モデルでは、複雑な質問に対して数秒から十数秒の応答時間がかかることも珍しくありません。

現状の実装では、ユーザがメッセージを送信してから応答が完了するまで、ローディングアニメーションが表示されるようになっています。しかしこれには、以下のような問題があります。

  • システムが動作しているのか不安になる
  • なんの情報も得ることができない
  • 応答が遅いと感じて離脱してしまう可能性がある

解決策:Vercel AI SDKによるストリーミング実装

そこで目をつけたのが、Vercel AI SDKです。

  • 簡単なストリーミング実装
    • 複雑な実装なしで、LLMからの応答をリアルタイムで受け取れる
  • 抽象化されたフロントエンド
    • ストリーミング処理がReact Hooksとして提供されており、状態管理が簡単
  • マルチプロバイダー対応
    • Amazon Bedrock以外にも、OpenAI、Anthropic、Google AI等に対応

https://ai-sdk.dev/

ストリーミングの仕組み

Vercel AI SDKは、Server-Sent Events (SSE) を使用してサーバからクライアントへリアルタイムでデータを送信します。LLMが生成したテキストを小さなチャンクに分割し、生成と同時にクライアントに送信することで、ユーザは応答の生成過程をリアルタイムで確認できます。

https://zenn.dev/cybozu_frontend/articles/try-server-sent-events

サンプル:実際にフロントで受け取ったレスポンス内容

f:{"messageId":"msg-OuigLUo9Lxefa0EudVrcsnaz"}
0:"Vercel AI"
0:" SDKは、"
0:"AI機能を"
0:"Webアプリ"
0:"ケーションに"
0:"簡単に統"
0:"合するための"
0:"オープンソース"
0:"ライブラリです"
0:"。\n\n##"
0:" 主な特"
0:"徴\n\n**"

// 省略

0:"、"
0:"非"
0:"常に便利"
0:"なツ"
0:"ールです。"
e:{"finishReason":"stop","usage":{"promptTokens":468,"completionTokens":570},"isContinued":false}
d:{"finishReason":"stop","usage":{"promptTokens":468,"completionTokens":570}}

実装してみる

プロダクトに実装する前に、お試し実装をしてみましょう。
公式ドキュメントを参考に実装します。

1. パッケージのインストール

@ai-sdk/amazon-bedrock@aws-sdk/credential-providersは、各自の環境に合わせて変更してください。

pnpm add ai @ai-sdk/amazon-bedrock @aws-sdk/credential-providers

2. バックエンド実装

src/app/api/chat/route.ts
import { streamText } from 'ai'
import { createAmazonBedrock } from '@ai-sdk/amazon-bedrock'
import { fromNodeProviderChain } from '@aws-sdk/credential-providers'

// ストリーミング応答を最大30秒まで許可する
export const maxDuration = 30

export async function POST(req: Request) {
  const { messages } = await req.json()

  // Amazon Bedrock クライアントの設定
  const bedrock = createAmazonBedrock({
    region: 'ap-northeast-1',
    credentialProvider: fromNodeProviderChain({
      profile: process.env.AWS_PROFILE_NAME,
    })
  })

  // ストリーミングでテキストを生成
  const result = streamText({
    model: bedrock('apac.anthropic.claude-sonnet-4-20250514-v1:0'),
    messages,
  })

  // データストリームレスポンスとして返却
  return result.toDataStreamResponse()
}

3. フロントエンド実装

src/app/page.tsx
'use client'

import { useChat } from '@ai-sdk/react'

export default function Chat() {
  // Hooksに複雑な実装が抽象化されている
  const { messages, input, handleInputChange, handleSubmit } = useChat()

  return (
    <div className="flex flex-col w-full max-w-md py-24 mx-auto stretch gap-4">
      {messages.map(message => (
        <div key={message.id} className="whitespace-pre-wrap dark:bg-zinc-900 rounded p-2">
          {message.role === 'user' ? 'User: ' : 'AI: '}
          {message.parts.map((part, i) => {
            switch (part.type) {
              case 'text':
                return <div key={`${message.id}-${i}`}>{part.text}</div>
            }
          })}
        </div>
      ))}

      <form onSubmit={handleSubmit}>
        <input
          className="fixed dark:bg-zinc-900 bottom-0 w-full max-w-md p-2 mb-8 border border-zinc-300 dark:border-zinc-800 rounded shadow-xl"
          value={input}
          placeholder="メッセージを入力してね"
          onChange={handleInputChange}
        />
      </form>
    </div>
  )
}

動かしてみる

できました!🎉

たった数十行のコードで、本格的なストリーミングチャットUIが完成しました。
ユーザは応答の生成過程をリアルタイムで確認することができるので、待機時間のストレスが大幅に軽減されそうです。

Vercel AI SDKの他の便利機能

AI SDKには、ストリーミング以外にも多くの機能が用意されています。

構造化データの生成

出力結果の構造をZodで指定することができます。
自前で実装しようとすると、パースなどの手間がかかりますよね。

import { generateObject } from 'ai'
import { z } from 'zod'

const result = await generateObject({
  model: bedrock('apac.anthropic.claude-sonnet-4-20250514-v1:0'),
  schema: z.object({
    name: z.string(),
    age: z.number(),
  }),
  messages,
})

自動リトライ処理

LLMの処理は様々な理由で失敗する事があります。
失敗時に自動的にリトライ処理を行うことができるため、より信頼性の高いアプリケーションを作ることができます。

const result = await generateText({
  model: bedrock('apac.anthropic.claude-sonnet-4-20250514-v1:0'),
  maxRetries: 3, // 失敗時に3回まで自動リトライ
  messages,
})

まとめ

Vercel AI SDKを使用することで、複雑なストリーミング実装を簡単に実現できました。

プロトタイピング段階では特に、開発速度とユーザ体験の両方を重視する必要があります。Vercel AI SDKは、その両方を満たす優秀なツールだと感じました。

今後も、より良いユーザ体験を提供するためのツールや手法を積極的に取り入れていきたいと思います。

ispec inc.

Discussion