mcpField
V
vercel-ai-sdk-expert

vercel-ai-sdk-expert

github.com/sickn33開発48.7k53 回インストール今日更新
リポジトリ

vercel-ai-sdk-expert について

Vercel AI SDK で React や Next.js に AI 機能を組み込む開発者向けのスキルです。生成、ストリーミング、ツール呼び出し、構造化出力と、バージョンに応じたトラブルシューティングを扱います。

主な機能

  1. 01テキスト生成とストリーミング
  2. 02React のチャットフック
  3. 03ツール呼び出しのワークフロー
  4. 04generateObject による構造化データ生成
  5. 05SDK バージョンとストリームの指針

活用シーン

  1. 01React や Next.js アプリへのチャット追加
  2. 02LLM の応答を UI にストリーミング表示
  3. 03ツール呼び出しと複数ステップの応答実装
  4. 04useChat や streamText の問題調査

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/sickn33/agentic-awesome-skills --skill vercel-ai-sdk-expert

インストール後、Claude Code で/vercel-ai-sdk-expertを入力して読み込まれたか確認します。

詳細

Vercel AI SDK エキスパート

本番環境向けの Vercel AI SDK エキスパートです。主に Next.js と React を使って、AI 搭載アプリケーション、チャットボット、生成 UI エクスペリエンスを構築する開発者を支援します。ai(AI SDK Core)パッケージと @ai-sdk/react(AI SDK UI)パッケージの両方に精通しています。ストリーミング、言語モデルとの連携、システムプロンプト、ツール呼び出し(関数呼び出し)、構造化データの生成について理解しています。

このスキルを使用する場面

  • React または Next.js アプリに AI チャットやテキスト生成機能を追加するとき
  • LLM の応答をフロントエンド UI にストリーミングするとき
  • LLM を使ったツール呼び出し/関数呼び出しを実装するとき
  • generateObject を使って LLM から構造化データ(JSON)を返すとき
  • AI 搭載の生成 UI(React コンポーネントのストリーミング)を構築するとき
  • OpenAI/Anthropic の API を直接呼び出す方式から、統合 AI SDK に移行するとき
  • useChat または streamText のストリーミングの問題をトラブルシューティングするとき

バージョンと実行範囲

以下のコードでは、旧式の AI SDK API(ai/react、toDataStreamResponse、parameters、maxSteps)を使用しています。これは現行バージョン向けのスターターではありません。インストール済みの ai とプロバイダーパッケージのバージョンを確認し、対応する移行ガイド/リファレンスドキュメントを使用して、サーバーとクライアントのストリームプロトコルに互換性を持たせてください。受信メッセージを検証し、ルートで認証とレート制限を行い、プロバイダーのキーをサーバー側のみに保持し、トークン数とツールの予算に上限を設け、モデルリクエストとは独立してツールの副作用を認可してください。プランごとにタイムアウト制限は異なるため、10~15 秒と決めつけず、実際のデプロイ環境を確認してください。

主な概念

Vercel AI SDK を使う理由

Vercel AI SDK は、プロバイダー固有の API(OpenAI、Anthropic、Google Gemini、Mistral)を抽象化する統合フレームワークです。主に 2 つのレイヤーを提供します。

  1. AI SDK Core(ai):LLM とやり取りするためのサーバー側関数(generateText、streamText、generateObject)。
  2. AI SDK UI(@ai-sdk/react):チャットの状態とストリーミングを管理するためのフロントエンドフック(useChat、useCompletion)。

サーバー側での生成(Core API)

基本的なテキスト生成
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";

// Returns the full string once completion is done (no streaming)
const { text, usage } = await generateText({
  model: openai("gpt-4o"),
  system: "You are a helpful assistant evaluating code.",
  prompt: "Review the following python code...",
});

console.log(text);
console.log(`Tokens used: ${usage.totalTokens}`);
テキストのストリーミング
// app/api/chat/route.ts (Next.js App Router API Route)
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

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

  const result = streamText({
    model: openai('gpt-4o'),
    system: 'You are a friendly customer support bot.',
    messages,
  });

  // Automatically converts the stream to a readable web stream
  return result.toDataStreamResponse();
}
構造化データ(JSON)の生成
import { generateObject } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const { object } = await generateObject({
  model: openai('gpt-4o-2024-08-06'), // Use models good at structured output
  system: 'Extract information from the receipt text.',
  prompt: receiptText,
  // Pass a Zod schema to enforce output structure
  schema: z.object({
    storeName: z.string(),
    totalAmount: z.number(),
    items: z.array(z.object({
      name: z.string(),
      price: z.number(),
    })),
    date: z.string().describe("ISO 8601 date format"),
  }),
});

// `object` is automatically fully typed according to the Zod schema!
console.log(object.totalAmount); 

フロントエンド UI フック

useChat(会話型 UI)
// app/page.tsx (Next.js Client Component)
"use client";

import { useChat } from "ai/react";

export default function Chat() {
  const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
    api: "/api/chat", // Points to the streamText route created above
    // Optional callbacks
    onFinish: (message) => console.log("Done streaming:", message),
    onError: (error) => console.error(error)
  });

  return (
    <div className="flex flex-col h-screen max-w-md mx-auto p-4">
      <div className="flex-1 overflow-y-auto mb-4">
        {messages.map((m) => (
          <div key={m.id} className={`mb-4 ${m.role === 'user' ? 'text-right' : 'text-left'}`}>
            <span className={`p-2 rounded-lg inline-block ${m.role === 'user' ? 'bg-blue-500 text-white' : 'bg-gray-200'}`}>
              {m.content}
            </span>
          </div>
        ))}
      </div>
      
      <form onSubmit={handleSubmit} className="flex gap-2">
        <input
          value={input}
          onChange={handleInputChange}
          placeholder="Say something..."
          className="flex-1 p-2 border rounded"
          disabled={isLoading}
        />
        <button type="submit" disabled={isLoading} className="bg-black text-white p-2 rounded">
          Send
        </button>
      </form>
    </div>
  );
}

ツール呼び出し(関数呼び出し)

ツールを使うと、LLM はユーザーに応答する前に、外部データを取得したりアクションを実行したりするためにコードとやり取りできます。

サーバー側でのツール定義
// app/api/chat/route.ts
import { streamText, tool } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

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

  const result = streamText({
    model: openai('gpt-4o'),
    messages,
    tools: {
      getWeather: tool({
        description: 'Get the current weather in a given location',
        parameters: z.object({
          location: z.string().describe('The city and state, e.g. San Francisco, CA'),
          unit: z.enum(['celsius', 'fahrenheit']).optional(),
        }),
        // Execute runs when the LLM decides to call this tool
        execute: async ({ location, unit = 'celsius' }) => {
          // Demo only: replace with an authorized real data integration.
          return `No live weather lookup was performed for ${location}.`;
        },
      }),
    },
    // Allows the LLM to call tools automatically in a loop until it has the answer
    maxSteps: 5, 
  });

  return result.toDataStreamResponse();
}
複数ステップのツール呼び出し用 UI

maxSteps を使う場合、UI で処理すれば、useChat フックに中間のツール呼び出しを表示できます。

// Inside the `useChat` messages.map loop
{m.role === 'assistant' && m.toolInvocations?.map((toolInvocation) => (
  <div key={toolInvocation.toolCallId} className="text-sm text-gray-500">
    {toolInvocation.state === 'result' ? (
      <p>✅ Fetched weather for {toolInvocation.args.location}</p>
    ) : (
      <p>⏳ Fetching weather for {toolInvocation.args.location}...</p>
    )}
  </div>
))}

ベストプラクティス

  • ✅ 推奨: 以前のエッジランタイムラッパーではなく、@ai-sdk/openai などの専用プロバイダーパッケージで openai('gpt-4o') または anthropic('claude-3-5-sonnet-20240620') 形式を使用してください。
  • ✅ 推奨: generateObject() を使用する場合は、厳密な Zod schema と明確な system プロンプトを指定してください。
  • ✅ 推奨: streamText を使う Next.js API ルートでは、maxDuration = 30(Pro の場合はそれ以上)を設定してください。LLM の応答のストリーミングには時間がかかり、Vercel のデフォルトは 10~15 秒です。
  • ✅ 推奨: Zod のパラメーターに包括的な description タグを付けて tool() を使用してください。LLM は、ツールをいつ、どのように呼び出すかを理解する際、これらの文字列だけを頼りにします。
  • ✅ 推奨: ツールを提供する場合は maxSteps: 5(または同程度の値)を有効にしてください。そうしないと、LLM はツールの結果を確認した後でユーザーに返信できません。
  • ❌ 禁止: streamText を使う Next.js App Router API ルートで result.toDataStreamResponse() を返し忘れないでください。通常の JSON レスポンスではチャンク処理が壊れます。
  • ❌ 禁止: Zod によってデータの形状が強制される場合でも、generateObject の出力を盲信しないでください。必ず try/catch を使って失敗状態を処理してください。

トラブルシューティング

問題: ストリーミングチャットが10~15秒後に突然途切れます。 解決策: サーバーレス関数がタイムアウトしています。Next.js API ルートファイルに export const maxDuration = 30;(またはご利用のプランの上限値)を追加してください。

問題: 「ツールの実行に失敗しました」と表示される、またはツールの使用後にLLMが回答を返しません。 解決策: maxSteps を指定しない場合、streamText はツールの呼び出しが完了するとすぐに停止します。LLMがツールの結果を確認し、最終的なテキスト回答を作成できるよう、maxSteps: 2(またはそれ以上)を設定してください。

制限事項

  • このスキルは、タスクが上記の説明範囲に明確に合致する場合にのみ使用してください。
  • 出力を、環境固有の検証、テスト、専門家によるレビューの代わりとして扱わないでください。
  • 必要な入力、権限、安全上の境界、または成功基準が不足している場合は、作業を中断して確認を求めてください。

コメント