TypeScript AI SDK開發:2026年Vercel AI SDK構建生產級AI應用的7個關鍵模式

前端工程

AI應用開發,為什麼總是這麼痛苦?

大模型API呼叫本身不難,但構建生產級AI應用卻處處是坑:流式輸出在前端展示總是閃爍跳躍,Server Components和AI流式響應的整合讓人抓狂,Edge Runtime的限制導致一半依賴不能用,Tool Calling的錯誤處理幾乎沒有最佳實踐……更別提部署到生產環境後的延遲、穩定性和成本問題。

Vercel AI SDK 在2026年已經演進到4.x版本,提供了從流式渲染到工具呼叫的完整解決方案。本文總結7個關鍵模式,幫你從Demo級別躍升到生產級別。


Vercel AI SDK 核心架構

模組 職責 關鍵API
AI Core 統一多模型呼叫介面 generateText(), streamText(), generateObject()
AI SDK UI 前端流式渲染Hook useChat(), useCompletion(), useObject()
AI SDK RSC Server Components整合 streamUI(), createAI()
Tool Calling 函數呼叫與工具編排 tools, execute, maxSteps

核心依賴版本:

{
  "ai": "^4.2.0",
  "@ai-sdk/openai": "^1.3.0",
  "@ai-sdk/anthropic": "^1.2.0",
  "next": "^15.2.0",
  "react": "^19.0.0",
  "zod": "^3.24.0"
}

問題深入分析:AI應用的7個核心挑戰

挑戰 傳統方案 AI SDK方案 優勢
流式渲染 手動SSE解析 useChat() Hook 自動重連、狀態管理
型別安全 any型別 Zod Schema 編譯期驗證
Server整合 API Route + useEffect RSC + streamUI() 零客戶端JS
Edge部署 Node.js Runtime Edge Runtime 全球低延遲
工具呼叫 手動JSON解析 tools + execute 自動編排
錯誤恢復 try/catch maxSteps + 重試 自動重試鏈
成本控制 Token計數 + 快取 精確計費

模式1:流式聊天UI

// app/api/chat/route.ts
import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';

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

  const result = streamText({
    model: openai('gpt-4o'),
    system: '你是一個專業的技術助手,回答要簡潔準確。',
    messages,
    maxTokens: 4096,
    temperature: 0.7,
  });

  return result.toDataStreamResponse();
}
// app/chat/page.tsx
'use client';

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

export default function ChatPage() {
  const { messages, input, handleInputChange, handleSubmit, isLoading, error, reload } = useChat({
    api: '/api/chat',
    onError: (err) => console.error('Chat error:', err),
  });

  return (
    <div className="flex flex-col h-screen max-w-3xl mx-auto p-4">
      <div className="flex-1 overflow-y-auto space-y-4 mb-4">
        {messages.map((message) => (
          <div
            key={message.id}
            className={`p-4 rounded-lg ${
              message.role === 'user'
                ? 'bg-blue-500 text-white ml-auto max-w-[80%]'
                : 'bg-gray-100 dark:bg-gray-800 mr-auto max-w-[80%]'
            }`}
          >
            <div className="whitespace-pre-wrap">{message.content}</div>
          </div>
        ))}
        {isLoading && <div className="animate-pulse p-4">思考中...</div>}
      </div>

      {error && (
        <div className="mb-2 p-2 bg-red-100 text-red-700 rounded text-sm">
          錯誤: {error.message}
          <button onClick={() => reload()} className="ml-2 underline">重試</button>
        </div>
      )}

      <form onSubmit={handleSubmit} className="flex gap-2">
        <input
          value={input}
          onChange={handleInputChange}
          placeholder="輸入你的問題..."
          className="flex-1 p-3 border rounded-lg"
          disabled={isLoading}
        />
        <button type="submit" disabled={isLoading || !input.trim()}
          className="px-6 py-3 bg-blue-500 text-white rounded-lg disabled:opacity-50">
          發送
        </button>
      </form>
    </div>
  );
}

模式2:Server Components流式渲染

// app/api/generate/route.ts
import { openai } from '@ai-sdk/openai';
import { streamUI } from 'ai/rsc';
import { z } from 'zod';

async function WeatherComponent({ city }: { city: string }) {
  const weatherData = {
    city,
    temperature: 22 + Math.floor(Math.random() * 10),
    condition: ['晴天', '多雲', '小雨'][Math.floor(Math.random() * 3)],
    humidity: 45 + Math.floor(Math.random() * 30),
  };

  return (
    <div className="p-4 bg-gradient-to-r from-blue-50 to-cyan-50 rounded-lg border">
      <h3 className="font-bold text-lg">{weatherData.city} 天氣</h3>
      <div className="flex gap-4 mt-2">
        <span className="text-3xl">{weatherData.temperature}°C</span>
        <div>
          <div>{weatherData.condition}</div>
          <div className="text-sm text-gray-500">濕度 {weatherData.humidity}%</div>
        </div>
      </div>
    </div>
  );
}

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

  const result = streamUI({
    model: openai('gpt-4o'),
    prompt,
    tools: {
      showWeather: {
        description: '顯示指定城市的天氣資訊',
        parameters: z.object({ city: z.string().describe('城市名稱') }),
        generate: async ({ city }) => <WeatherComponent city={city} />,
      },
    },
  });

  return result.toUIStreamResponse();
}

模式3:Tool Calling工具呼叫

// app/api/agent/route.ts
import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';
import { z } from 'zod';

const tools = {
  searchProducts: {
    description: '搜尋商品資料庫',
    parameters: z.object({
      query: z.string().describe('搜尋關鍵詞'),
      category: z.string().optional().describe('商品分類'),
      maxPrice: z.number().optional().describe('最高價格'),
    }),
    execute: async ({ query, category, maxPrice }) => {
      const products = [
        { id: 1, name: '機械鍵盤 Pro', price: 599, category: '外設' },
        { id: 2, name: '4K顯示器', price: 2999, category: '顯示器' },
        { id: 3, name: '無線滑鼠', price: 199, category: '外設' },
      ];
      let results = products.filter(p => p.name.includes(query) || p.category === category);
      if (maxPrice) results = results.filter(p => p.price <= maxPrice);
      return results;
    },
  },
  calculateTotal: {
    description: '計算訂單總價',
    parameters: z.object({
      items: z.array(z.object({ productId: z.number(), quantity: z.number(), unitPrice: z.number() })),
      discount: z.number().optional().describe('折扣百分比'),
    }),
    execute: async ({ items, discount = 0 }) => {
      const subtotal = items.reduce((sum, item) => sum + item.unitPrice * item.quantity, 0);
      const total = subtotal - subtotal * (discount / 100);
      return { subtotal: subtotal.toFixed(2), discount: (subtotal * discount / 100).toFixed(2), total: total.toFixed(2) };
    },
  },
};

export async function POST(req: Request) {
  const { messages } = await req.json();
  const result = streamText({
    model: openai('gpt-4o'),
    system: '你是一個電商助手,幫助用戶搜尋商品、計算價格和建立訂單。',
    messages,
    tools,
    maxSteps: 5,
  });
  return result.toDataStreamResponse();
}

模式4:結構化輸出

import { z } from 'zod';

export const analysisSchema = z.object({
  summary: z.string().describe('文章摘要'),
  sentiment: z.enum(['positive', 'neutral', 'negative']).describe('情感傾向'),
  keywords: z.array(z.string()).describe('關鍵詞列表'),
  categories: z.array(z.object({ name: z.string(), confidence: z.number().min(0).max(1) })),
  entities: z.array(z.object({
    name: z.string(),
    type: z.enum(['person', 'organization', 'location', 'product', 'event']),
    mentions: z.number(),
  })),
});

模式5:Edge Runtime部署

// app/api/edge-chat/route.ts
import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';

export const runtime = 'edge';

export async function POST(req: Request) {
  const { messages } = await req.json();
  const result = streamText({
    model: openai('gpt-4o-mini'),
    system: '簡潔回答用戶問題。',
    messages,
    maxTokens: 2048,
  });
  return result.toDataStreamResponse();
}

模式6:多模型切換

// lib/models.ts
import { openai } from '@ai-sdk/openai';
import { anthropic } from '@ai-sdk/anthropic';
import { google } from '@ai-sdk/google';

export const models = {
  'gpt-4o': openai('gpt-4o'),
  'gpt-4o-mini': openai('gpt-4o-mini'),
  'claude-sonnet-4-20250514': anthropic('claude-sonnet-4-20250514'),
  'gemini-2.0-flash': google('gemini-2.0-flash'),
} as const;

export type ModelId = keyof typeof models;
export function getModel(modelId: ModelId) { return models[modelId]; }

模式7:中介軟體與鑑權

// middleware.ts
import { NextRequest, NextResponse } from 'next/server';

const rateLimitMap = new Map<string, { count: number; resetAt: number }>();

export function middleware(req: NextRequest) {
  if (req.nextUrl.pathname.startsWith('/api/')) {
    const ip = req.headers.get('x-forwarded-for') || 'unknown';
    const now = Date.now();
    const record = rateLimitMap.get(ip);
    if (!record || now > record.resetAt) {
      rateLimitMap.set(ip, { count: 1, resetAt: now + 60000 });
    } else if (record.count >= 20) {
      return NextResponse.json({ error: '請求過於頻繁' }, { status: 429 });
    } else {
      record.count++;
    }
  }
  return NextResponse.next();
}

export const config = { matcher: '/api/:path*' };

避坑指南

坑1:useChat流式輸出閃爍

React 19的併發渲染可能導致流式輸出時UI閃爍跳躍。

解決方案:使用 onFinish 回調做最終狀態同步,使用 whitespace-pre-wrap,使用 key={message.id}

坑2:Edge Runtime依賴不相容

許多npm包使用Node.js API,在Edge Runtime下無法運行。

解決方案:檢查依賴是否Edge相容,不相容的依賴降級到Node.js Runtime。

坑3:Tool Calling無限迴圈

maxSteps 設置過大,工具呼叫可能陷入迴圈。

解決方案:設置合理的 maxSteps(推薦3-5),在工具 execute 中添加終止條件。

坑4:streamUI組件序列化失敗

RSC流式傳輸的組件必須可序列化,閉包和不可序列化的props會報錯。

解決方案:組件中不要使用閉包捕獲外部變數,所有資料通過props傳遞。

坑5:generateObject Schema不匹配

模型輸出不符合Zod Schema定義,導致解析失敗。

解決方案:Schema欄位添加詳細的 describe() 說明,使用 enum 替代自由文字欄位。


報錯排查

序號 報錯信息 原因 解決方法
1 AI_APICallError: 429 Rate limit exceeded API呼叫頻率超限 添加指數退避重試
2 AI_InvalidPromptError: messages must not be empty messages陣列為空 發送前校驗
3 AI_NoOutputSpecifiedError streamText未指定輸出方式 呼叫 toDataStreamResponse()
4 AI_ToolExecutionError 工具execute函數拋出異常 在execute中添加try/catch
5 AI_JSONParseError 模型輸出非有效JSON 使用 generateObject 替代
6 EdgeRuntime: Dynamic require of "fs" Edge中使用了Node.js API 切換Runtime
7 Hydration mismatch SSR和客戶端渲染不一致 確保流式組件不依賴客戶端狀態
8 AI_InvalidArgumentError: schema validation failed Zod Schema驗證失敗 檢查Schema定義
9 AI_ReadonlyStreamError: stream already consumed 流被重複消費 確保每個流只讀取一次
10 Maximum call stack size exceeded maxSteps導致無限遞迴 降低maxSteps

進階優化

1. 流式響應快取

import { cache } from 'react';

export const generateAnalysis = cache(async (text: string) => {
  const { object } = await generateObject({
    model: openai('gpt-4o-mini'),
    schema: analysisSchema,
    prompt: text,
  });
  return object;
});

2. Token用量追蹤

const result = streamText({
  model: openai('gpt-4o'),
  messages,
  onFinish: ({ usage }) => {
    console.log(`Tokens: prompt=${usage.promptTokens}, completion=${usage.completionTokens}`);
  },
});

3. 流式UI載入骨架

const result = streamUI({
  model: openai('gpt-4o'),
  prompt,
  loading: (
    <div className="animate-pulse space-y-3 p-4">
      <div className="h-4 bg-gray-200 rounded w-3/4" />
      <div className="h-32 bg-gray-200 rounded" />
    </div>
  ),
});

對比分析

維度 Vercel AI SDK LangChain.js OpenAI SDK LlamaIndex.ts
流式UI 原生React Hook 需手動整合 需手動整合 需手動整合
Server Components 原生支持 不支持 不支持 不支持
多模型統一 Provider抽象 Callback抽象 僅OpenAI Provider抽象
Tool Calling 聲明式 鏈式 手動 聲明式
結構化輸出 Zod Schema Zod/Dynamic JSON Mode Zod Schema
Edge Runtime 原生支持 部分相容 相容 部分相容
包體積 ~30KB ~200KB+ ~50KB ~150KB+
學習曲線
Next.js整合 深度整合 需適配 需適配 需適配

總結:Vercel AI SDK 是2026年構建Next.js AI應用的最佳選擇。7個關鍵模式涵蓋了從流式渲染、Server Components整合、Tool Calling到Edge部署的完整鏈路。核心優勢在於與React/Next.js的深度整合——useChat Hook讓流式聊天只需幾行代碼,streamUI 讓AI直接生成React組件,Zod Schema讓結構化輸出型別安全。建議從模式1開始,逐步引入Tool Calling和RSC,最後最佳化Edge部署和快取策略。


線上工具推薦

本站提供瀏覽器本地工具,免註冊即可試用 →

#TypeScript#Vercel AI SDK#Next.js#流式UI#大模型#AI应用#Server Components#Edge Runtime