fetch() で OpenAI API を叩くだけなら難しくありません。しかし、ユーザーが送信ボタンを押してから画面に最初の文字が現れるまでの2〜3秒の空白は、アプリの印象を大きく左右します。ストリーミングを自前で実装しようとすると、SSE(Server-Sent Events)のパース処理、デルタテキストの結合、エラーハンドリング、状態管理とUIの同期……と、本来のアプリロジックとは無関係な実装コストが積み上がります。
Vercel AI SDK は、こうした定型的な実装をすべて抽象化し、Rork(React Native / Expo)アプリに型安全なAI機能を最短で組み込めるライブラリです。ここではストリーミング・ツール呼び出し・構造化出力の3つの実装パターンを、実際に動作するコードとともに紹介します。
Vercel AI SDK を選ぶ理由:他の実装との比較
市販のAI統合ライブラリはいくつかありますが、Vercel AI SDK が React Native 開発者に支持される理由は主に3つあります。
型安全性の高さ — TypeScript ファーストで設計されており、AIのレスポンス型が完全に推論されます。generateObject() で取得したデータは追加のバリデーションなしで型安全に扱えます。バックエンドからフロントエンドまで型が一貫するため、any キャストによるバグを未然に防げます。
マルチプロバイダー対応 — OpenAI、Anthropic、Google Gemini、Mistral など複数のLLMプロバイダーを同一のAPIで切り替えられます。バックエンドコードをほぼ変えずにモデルを比較できるのは、コストと品質を素早く検証したい個人開発のスピード感と相性が良いです。
React Native 実績 — useChat フックは Expo / React Native 環境でも動作が確認されています。ただし、バックエンド側(APIルート)は Node.js 環境か Edge Runtime が必要です。Cloudflare Workers との組み合わせが実績豊富です。
LLM ストリーミング実装ガイドで手動実装を経験したことがある方は、その複雑さとの対比でSDKの価値が特に実感できるはずです。
バックエンドのセットアップ:Cloudflare Workers + Hono
Vercel AI SDK はフロントエンドライブラリですが、ストリーミングAPIを提供するバックエンドが別途必要です。ここでは Cloudflare Workers + Hono を使う構成を例に示します。Hono のセットアップ方法は Hono × Cloudflare Workers REST API ガイドを参照してください。
# バックエンドプロジェクトに追加
npm install ai @ai-sdk/openai hono zodストリーミングエンドポイントの実装です:
// src/index.ts(Cloudflare Worker + Hono)
import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { streamText } from 'ai'
import { openai } from '@ai-sdk/openai'
const app = new Hono<{ Bindings: { OPENAI_API_KEY: string } }>()
app.use('/*', cors())
app.post('/api/chat', async (c) => {
const { messages } = await c.req.json()
// streamText がSSEのデルタ送信をすべて管理する
const result = streamText({
model: openai('gpt-4o-mini'),
system: 'あなたは親切なアシスタントです。',
messages,
// コスト管理のため上限を必ず設定する
maxTokens: 1000,
})
// toDataStreamResponse() でRork側のuseChat()と自動同期できる
return result.toDataStreamResponse()
})
export default appstreamText() の戻り値を toDataStreamResponse() でラップするだけで、クライアントの useChat フックと互換性のあるストリームを返せます。SSEのデルタ結合や接続管理といった低レベルな処理が完全に隠蔽されており、ビジネスロジックに集中できます。
APIキーは必ずバックエンドの環境変数に保管してください。Cloudflare Workers では wrangler secret put OPENAI_API_KEY で設定します。フロントエンドコードに直接書くことは、GitHub Secret Scanning で検出される危険性があるため絶対に避けてください。
Rork アプリでのストリーミングチャット実装
Rork プロジェクト側に SDK を追加します:
npm install aiuseChat フックを使ったチャット画面の実装です:
// screens/ChatScreen.tsx
import { useChat } from 'ai/react'
import {
View, TextInput, FlatList, Text,
TouchableOpacity, StyleSheet, KeyboardAvoidingView, Platform
} from 'react-native'
const API_URL = 'https://your-worker.workers.dev/api/chat'
export default function ChatScreen() {
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
api: API_URL,
onError: (error) => {
// エラー時はユーザーに通知する(自動リトライは避ける)
console.error('Chat error:', error.message)
},
})
return (
<KeyboardAvoidingView
style={styles.container}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
>
<FlatList
data={messages}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<View style={[
styles.bubble,
item.role === 'user' ? styles.userBubble : styles.aiBubble,
]}>
{/* ストリーミング中もリアルタイムでテキストが更新される */}
<Text style={styles.messageText}>{item.content}</Text>
</View>
)}
/>
<View style={styles.inputRow}>
<TextInput
style={styles.input}
value={input}
// React Native の onChangeText は string を返す。
// useChat の handleInputChange は Web の onChange を想定しているため
// { target: { value: text } } でラップして橋渡しする。
onChangeText={(text) =>
handleInputChange({ target: { value: text } } as any)
}
placeholder="メッセージを入力..."
editable={\!isLoading}
multiline
/>
<TouchableOpacity
style={[styles.sendButton, isLoading && styles.disabled]}
onPress={() => handleSubmit()}
disabled={isLoading}
>
<Text style={styles.sendLabel}>{isLoading ? '…' : '送信'}</Text>
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
)
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#fff' },
bubble: { margin: 8, padding: 12, borderRadius: 16, maxWidth: '80%' },
userBubble: { alignSelf: 'flex-end', backgroundColor: '#007AFF' },
aiBubble: { alignSelf: 'flex-start', backgroundColor: '#F2F2F7' },
messageText: { fontSize: 15, color: '#000' },
inputRow: {
flexDirection: 'row', padding: 8,
borderTopWidth: 1, borderColor: '#E5E5EA'
},
input: {
flex: 1, borderWidth: 1, borderColor: '#C7C7CC',
borderRadius: 20, paddingHorizontal: 16, paddingVertical: 8, marginRight: 8
},
sendButton: {
backgroundColor: '#007AFF', borderRadius: 20,
paddingHorizontal: 20, justifyContent: 'center'
},
disabled: { opacity: 0.5 },
sendLabel: { color: '#fff', fontWeight: '600' },
})handleSubmit() を呼ぶだけで、メッセージ履歴の管理、ストリーミングレスポンスの受信、UIの更新がすべて自動処理されます。従来は100行以上かかっていた実装がフック1つに集約されています。
handleInputChange のラップは、SDK の公式ドキュメントでは触れられていませんが、React Native での実装時に必ずぶつかるポイントです。Web 向けに設計されたフックを React Native で使う際の典型的なパターンとして覚えておいてください。
ツール呼び出し(Tool Calling)で外部データと連携する
ツール呼び出しとは、AIが必要なタイミングで外部APIを自律的に呼び出す機能です。「東京の天気は?」という質問に対して、AIが天気APIを叩いて最新データを取得し、それをもとに回答を生成します。プロンプトに全情報を詰め込む必要がなくなるため、コストと精度の両面で有利です。
バックエンドにツール定義を追加します:
// src/index.ts に追記
import { streamText, tool } from 'ai'
import { z } from 'zod'
app.post('/api/chat-with-tools', async (c) => {
const { messages } = await c.req.json()
const result = streamText({
model: openai('gpt-4o'),
messages,
tools: {
getWeather: tool({
description: '指定した都市の現在の天気情報を取得する',
// Zodでパラメータを定義するとAIが不正な型の引数を渡すのを防げる
parameters: z.object({
city: z.string().describe('都市名(例: 東京、大阪)'),
}),
execute: async ({ city }) => {
// 本番では実際のAPIに置き換える
// const res = await fetch(`https://api.openweathermap.org/...`)
return { city, temperature: 22, condition: '晴れ', humidity: 55 }
},
}),
},
// ツール呼び出し→結果解釈→回答生成の多段階推論を許可する
// 設定しないとツールの結果がユーザーに返らず応答が途切れる
maxSteps: 3,
})
return result.toDataStreamResponse()
})maxSteps の設定を忘れると、AIがツールを呼び出した後に応答が途切れてしまいます。これはツール呼び出しを実装した際に多くの開発者が最初に直面するバグです。詳細な実装パターンは AI ツール呼び出し実装ガイドも参照してください。
構造化出力で型安全なデータを生成する
チャット形式ではなく「AIに特定のデータ構造を生成させたい」場合は generateObject() が最適です。レビューのセンチメント分析、フォーム入力からの情報抽出、コンテンツの自動タグ付けなど、バックエンドの定型処理に広く応用できます。
// src/index.ts
import { generateObject } from 'ai'
app.post('/api/analyze-review', async (c) => {
const { reviewText } = await c.req.json()
const { object } = await generateObject({
model: openai('gpt-4o-mini'),
schema: z.object({
sentiment: z.enum(['positive', 'neutral', 'negative']),
score: z.number().min(1).max(5).describe('1〜5の評価スコア'),
summary: z.string().max(50).describe('50文字以内の要約'),
keywords: z.array(z.string()).max(3).describe('主要キーワード(最大3件)'),
}),
prompt: `以下のレビューを分析してください:\n\n${reviewText}`,
})
// object は Zod スキーマから完全に型推論される
// object.sentiment は 'positive' | 'neutral' | 'negative' 型
return c.json(object)
})戻り値の object は Zod スキーマから TypeScript の型が完全に推論されます。フロントエンドで受け取った後、object.sentiment に switch 文を書いても補完が効き、存在しないプロパティへのアクセスはコンパイル時に検出されます。従来の JSON.parse() + as any パターンと比べて、ランタイムエラーのリスクを大幅に下げられます。
Vercel AI SDK を使うことで、ストリーミング実装の複雑さをゼロに近づけながら、型安全なAI機能を Rork アプリに統合できます。まず useChat フックから試してみて、ユーザーとのインタラクションに慣れてきたらツール呼び出しや構造化出力へ拡張していく段階的なアプローチが、実装コストを抑えながら機能を育てる現実的な方法です。Cloudflare Workers はコールドスタートが数ミリ秒と高速なため、AIアプリのバックエンドとして特に相性が良いです。ぜひ実際のプロジェクトで試してみてください。