RORK LABEN
TOOLING — Rorkの開発者向けリポジトリが動き続けています。rork-xcodeが7月16日、rork-deviceが7月15日、rork-plistが7月13日に更新されましたOPUS46 — RorkでClaude Opus 4.6が稼働しています。Rork MaxはClaude Codeを土台にアプリを組み立てる設計ですSIM — ブラウザ上で動くクラウドのiOSシミュレータを備え、実機へのインストールは1クリック、App Storeへの提出は2クリックと案内されていますMAX — Rork MaxはReact Nativeではなく純粋なSwiftを出力します。iPhone・iPad・Apple Watch・Apple TV・Vision Pro、そしてiMessageまでが射程ですNATIVE — HealthKit、ARKitとLiDAR、NFC、Dynamic Island、Live Activities、Metalによる3D、Core MLのオンデバイス推論まで扱えますSEED — RorkはLeft Lane Capitalが主導する1,500万ドルのシードラウンドを実施し、Peak XVとa16z Speedrunが参加しましたTOOLING — Rorkの開発者向けリポジトリが動き続けています。rork-xcodeが7月16日、rork-deviceが7月15日、rork-plistが7月13日に更新されましたOPUS46 — RorkでClaude Opus 4.6が稼働しています。Rork MaxはClaude Codeを土台にアプリを組み立てる設計ですSIM — ブラウザ上で動くクラウドのiOSシミュレータを備え、実機へのインストールは1クリック、App Storeへの提出は2クリックと案内されていますMAX — Rork MaxはReact Nativeではなく純粋なSwiftを出力します。iPhone・iPad・Apple Watch・Apple TV・Vision Pro、そしてiMessageまでが射程ですNATIVE — HealthKit、ARKitとLiDAR、NFC、Dynamic Island、Live Activities、Metalによる3D、Core MLのオンデバイス推論まで扱えますSEED — RorkはLeft Lane Capitalが主導する1,500万ドルのシードラウンドを実施し、Peak XVとa16z Speedrunが参加しました
記事一覧/AIモデル
AIモデル/2026-04-15中級

Rork × Vercel AI SDK:ストリーミング・ツール呼び出し・構造化出力を型安全に実装する

Vercel AI SDK を使って Rork アプリにストリーミングチャット・ツール呼び出し・構造化出力を型安全に実装する方法を、Cloudflare Workers + Hono のバックエンド構成で解説します。

Vercel AI SDKストリーミング3ツール呼び出しTool CallingReact Native209Cloudflare Workers24OpenAI5TypeScript8

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 app

streamText() の戻り値を toDataStreamResponse() でラップするだけで、クライアントの useChat フックと互換性のあるストリームを返せます。SSEのデルタ結合や接続管理といった低レベルな処理が完全に隠蔽されており、ビジネスロジックに集中できます。

APIキーは必ずバックエンドの環境変数に保管してください。Cloudflare Workers では wrangler secret put OPENAI_API_KEY で設定します。フロントエンドコードに直接書くことは、GitHub Secret Scanning で検出される危険性があるため絶対に避けてください。

Rork アプリでのストリーミングチャット実装

Rork プロジェクト側に SDK を追加します:

npm install ai

useChat フックを使ったチャット画面の実装です:

// 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.sentimentswitch 文を書いても補完が効き、存在しないプロパティへのアクセスはコンパイル時に検出されます。従来の JSON.parse() + as any パターンと比べて、ランタイムエラーのリスクを大幅に下げられます。


Vercel AI SDK を使うことで、ストリーミング実装の複雑さをゼロに近づけながら、型安全なAI機能を Rork アプリに統合できます。まず useChat フックから試してみて、ユーザーとのインタラクションに慣れてきたらツール呼び出しや構造化出力へ拡張していく段階的なアプローチが、実装コストを抑えながら機能を育てる現実的な方法です。Cloudflare Workers はコールドスタートが数ミリ秒と高速なため、AIアプリのバックエンドとして特に相性が良いです。ぜひ実際のプロジェクトで試してみてください。

シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

AI モデル2026-04-07
Rork Max × OpenAI Responses API:ステートフルAIエージェントをモバイルアプリに実装する2026
OpenAI Responses APIを使ってステートフルなAIエージェントをRork Maxアプリに実装する完全ガイド。Web検索・ファイル検索・コードインタプリタなどの組み込みツールをCloudflare Workers経由でモバイルに統合する実践的な設計手法を解説します。
AI モデル2026-04-17
RorkアプリでGroq APIを使って効率的にAI応答を実現する方法
Rorkで作ったアプリにGroq APIを組み込み、超高速なAIチャット機能を実装する方法を解説。ストリーミング応答やエラーハンドリングまで、動作するコードで丁寧に紹介します。
開発ツール2026-04-13
Rork アプリで LLM ストリーミングを実装する — ChatGPT 風リアルタイム AI 応答 UI を Expo × SSE で本番構築する
React Native / Expo で LLM ストリーミング(SSE)を本番実装する完全ガイド。Anthropic・OpenAI の streaming API 接続から AbortController・エラーリトライ・Cloudflare Workers プロキシ・マルチプロバイダー抽象化まで動作済みコード付きで解説。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →