RemixでVercel AI SDKを使う時はzodを添える

この記事では、RemixアプリケーションでVercel AI SDKを使用する際に発生する「z8.string(…).base64 is not a function」エラーの原因と解決策を解説しています。エラーの原因はzodライブラリの依存関係が明示的にインストールされていないことでした。zodをインストールすることで問題が解決し、Anthropicの言語モデルを使ったストリーミングレスポンスの実装例も紹介されています。

広告ここから
広告ここまで

目次

    最近のプロジェクトでRemixとVercel AI SDKを組み合わせて実装していたところ、一見シンプルなコードなのに「z8.string(…).base64 is not a function」というエラーが発生しました。この記事では、Remix環境でVercel AI SDKを使用する際に必要な依存関係とエラー解決方法について解説します。

    前提知識と環境構築

    この記事では以下の環境を前提としています:

    • Remix v2系(@remix-run/node)
    • Vercel AI SDK v3系
    • Node.js 18以上

    まず、必要なパッケージをインストールします:

    npm install @vercel/ai @ai-sdk/anthropic
    

    エラー詳細と原因

    さっそく以下のようなコードを実装してみました:

    import { getAuth } from '@clerk/remix/ssr.server'
    import type { ActionFunctionArgs } from '@remix-run/node'
    import { streamText } from 'ai';
    import { createAnthropic } from '@ai-sdk/anthropic';
    
    export async function action(args: ActionFunctionArgs) {
        const { CLAUDE_API_KEY } = args.context.cloudflare.env
        const { userId } = await getAuth(args)
        console.log(userId)
        
        const model = createAnthropic({
            apiKey: CLAUDE_API_KEY
        })('claude-3-5-sonnet-20241022')
        
        const result = streamText({
          model,
          messages: [
            {
                role: 'user',
                content: 'Hello, how are you?'
            }
          ],
        })
        
        return result.toDataStreamResponse();
    }
    

    しかし実行すると、以下のエラーが発生しました:

    13:14:26 [vite] Internal server error: z8.string(...).base64 is not a function
    

    このエラーメッセージからは直感的に原因を把握しづらいですが、実はVercel AI SDKはzodという型検証ライブラリに依存しており、この依存関係が明示的にインストールされていないことが原因でした。

    解決策:zodのインストール

    解決策は非常にシンプルです。zodパッケージをインストールするだけです:

    npm install zod
    

    このコマンドを実行した後、先ほどと同じコードを実行すると、エラーが解消されます。

    なぜこのエラーが発生するのか?

    Vercel AI SDKは内部でzodを使用して型の検証を行っています。特に、ストリーミングレスポンスの処理やメッセージの検証などでzodが活用されています。npmやyarnなどのパッケージマネージャは、依存関係を自動的にインストールしますが、peerDependencyとして宣言されているパッケージは明示的にインストールする必要があります。

    Vercel AI SDKではzodがこのpeerDependencyとして扱われているため、明示的なインストールが必要になります。

    完全な実装例

    では、zodを追加した上での完全な実装例を見ていきましょう。この例では、Cloudflare Workersを使ったRemixアプリケーションでClerk認証とAnthropicのAPIを使用しています:

    import { getAuth } from '@clerk/remix/ssr.server'
    import type { ActionFunctionArgs } from '@remix-run/node'
    import { streamText } from 'ai';
    import { createAnthropic } from '@ai-sdk/anthropic';
    import { z } from 'zod'; // zodをインポート
    
    // 入力データのバリデーションスキーマ
    const inputSchema = z.object({
      message: z.string().min(1).max(1000)
    });
    
    export async function action(args: ActionFunctionArgs) {
        const { CLAUDE_API_KEY } = args.context.cloudflare.env
        const { userId } = await getAuth(args)
        
        // 未認証ユーザーのチェック
        if (!userId) {
          return new Response('Unauthorized', { status: 401 });
        }
        
        // フォームデータの取得とバリデーション
        const formData = await args.request.formData();
        const userMessage = formData.get('message')?.toString() || '';
        
        try {
          // 入力データの検証
          const { message } = inputSchema.parse({ message: userMessage });
          
          // Anthropicモデルの初期化
          const model = createAnthropic({
              apiKey: CLAUDE_API_KEY
          })('claude-3-5-sonnet-20241022')
          
          // ストリーミングレスポンスの作成
          const result = streamText({
            model,
            messages: [
              {
                  role: 'user',
                  content: message
              }
            ],
          })
          
          return result.toDataStreamResponse();
        } catch (error) {
          console.error('Error processing AI request:', error);
          return new Response('Error processing request', { status: 500 });
        }
    }
    

    フロントエンド側では、以下のようにストリーミングレスポンスを受け取って表示できます:

    import { useAIStream } from 'ai/react';
    import { Form, useActionData } from '@remix-run/react';
    import { useEffect, useState } from 'react';
    
    export default function AIChat() {
      const actionData = useActionData<typeof action>();
      const [message, setMessage] = useState('');
      const [streamedText, setStreamedText] = useState('');
      
      // ストリーミングデータの処理
      useEffect(() => {
        if (actionData?.stream) {
          const reader = actionData.stream.getReader();
          
          const readStream = async () => {
            let done = false;
            let accumulatedText = '';
            
            while (!done) {
              const { value, done: doneReading } = await reader.read();
              done = doneReading;
              
              if (value) {
                const text = new TextDecoder().decode(value);
                accumulatedText += text;
                setStreamedText(accumulatedText);
              }
            }
          };
          
          readStream();
        }
      }, [actionData]);
      
      return (
        <div className="p-4 max-w-3xl mx-auto">
          <h1 className="text-2xl font-bold mb-4">AIチャット</h1>
          
          <Form method="post" className="mb-4">
            <div className="flex gap-2">
              <input
                type="text"
                name="message"
                value={message}
                onChange={(e) => setMessage(e.target.value)}
                className="flex-1 p-2 border rounded"
                placeholder="メッセージを入力..."
              />
              <button
                type="submit"
                className="px-4 py-2 bg-blue-500 text-white rounded"
              >
                送信
              </button>
            </div>
          </Form>
          
          {streamedText && (
            <div className="p-4 border rounded bg-gray-50">
              <h2 className="font-bold mb-2">AI応答:</h2>
              <div className="whitespace-pre-wrap">{streamedText}</div>
            </div>
          )}
        </div>
      );
    }
    

    まとめと発展

    RemixとVercel AI SDKを組み合わせることで、インタラクティブなAIアプリケーションを効率的に開発できます。今回のエラー「z8.string(…).base64 is not a function」はzodをインストールするだけで解決しましたが、このようなエラーメッセージは直感的に原因を理解しづらいことがあります。

    エラーメッセージから原因を特定できない場合は、ライブラリの依存関係を確認することが重要です。また、GitHubのIssuesも参考になります:

    今後の発展として、以下のような取り組みも検討してみてください:

    1. Remixのロードの最適化: AIレスポンスが生成される間のローディング状態の改善
    2. エラーハンドリングの強化: AIモデルからのエラーを適切に処理する仕組み
    3. 複数のモデルの切り替え: ユーザーが異なるAIモデルを選択できる機能の実装
    4. メッセージ履歴の管理: チャット履歴を保存して文脈を維持する機能

    今回のような小さなトラブルは開発過程では頻繁に発生しますが、原因を理解して適切に対処することで、より堅牢なアプリケーション開発につながります。Remixの強力なサーバーサイド機能とVercel AI SDKの柔軟性を組み合わせて、ぜひ素晴らしいAIアプリケーションを開発してみてください。

    広告ここから
    広告ここまで
    Home
    Search
    Bookmark