Vercel AI SDK 完整教學|2025 建立 AI 應用程式入門指南

Vercel AI SDK 完整教學|2025 建立 AI 應用程式入門指南

想在網站加入 AI 功能嗎?

Vercel AI SDK 是目前最簡單的方式。

它幫你處理了串流、錯誤處理、狀態管理等複雜問題。

這篇文章手把手教你從零開始建立 AI 應用。


什麼是 Vercel AI SDK?

Vercel AI SDK 是一套專為 AI 應用設計的開發工具。

核心功能

功能 說明
串流回應 即時顯示 AI 回覆,不用等全部完成
統一 API 用同一套程式碼串接不同 AI 模型
React Hooks 現成的 useChatuseCompletion
Edge 支援 可在 Edge Functions 執行

支援的 AI 模型

  • OpenAI(GPT-4、GPT-3.5)
  • Anthropic(Claude)
  • Google(Gemini)
  • Mistral
  • Cohere
  • 更多...

為什麼選擇 AI SDK?

沒有 AI SDK:

// 需要自己處理串流
const response = await fetch('/api/chat', {
  method: 'POST',
  body: JSON.stringify({ messages }),
});

const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  const text = decoder.decode(value);
  // 自己解析和更新 UI...
}

有 AI SDK:

// 三行搞定
const { messages, input, handleSubmit } = useChat();
// UI 自動更新,串流自動處理

安裝和設定

建立 Next.js 專案

npx create-next-app@latest my-ai-app
cd my-ai-app

安裝 AI SDK

npm install ai @ai-sdk/openai

說明:

  • ai:AI SDK 核心
  • @ai-sdk/openai:OpenAI 適配器

如果要用其他模型:

# Anthropic Claude
npm install @ai-sdk/anthropic

# Google Gemini
npm install @ai-sdk/google

# Mistral
npm install @ai-sdk/mistral

設定環境變數

建立 .env.local

OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx

如果用其他模型:

# Anthropic
ANTHROPIC_API_KEY=sk-ant-xxxxxxxx

# Google
GOOGLE_GENERATIVE_AI_API_KEY=xxxxxxxx

建立第一個 AI 聊天

步驟一:建立 API Route

// 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 = await streamText({
    model: openai('gpt-4-turbo'),
    messages,
  });

  return result.toDataStreamResponse();
}

步驟二:建立前端頁面

// app/page.tsx
'use client';

import { useChat } from 'ai/react';

export default function Chat() {
  const { messages, input, handleInputChange, handleSubmit } = useChat();

  return (
    <div className="max-w-2xl mx-auto p-4">
      <div className="space-y-4 mb-4">
        {messages.map((m) => (
          <div
            key={m.id}
            className={`p-4 rounded ${
              m.role === 'user' ? 'bg-blue-100' : 'bg-gray-100'
            }`}
          >
            <strong>{m.role === 'user' ? '你' : 'AI'}:</strong>
            <p>{m.content}</p>
          </div>
        ))}
      </div>

      <form onSubmit={handleSubmit} className="flex gap-2">
        <input
          value={input}
          onChange={handleInputChange}
          placeholder="輸入訊息..."
          className="flex-1 border rounded p-2"
        />
        <button
          type="submit"
          className="bg-blue-500 text-white px-4 py-2 rounded"
        >
          送出
        </button>
      </form>
    </div>
  );
}

步驟三:執行

npm run dev

打開 http://localhost:3000,你就有一個可以對話的 AI 聊天了!


useChat Hook 詳解

useChat 是 AI SDK 最常用的 Hook。

基本用法

const {
  messages,      // 對話歷史
  input,         // 輸入框內容
  handleInputChange,  // 輸入變更處理
  handleSubmit,  // 送出處理
  isLoading,     // 是否正在載入
  error,         // 錯誤訊息
} = useChat();

進階選項

const { messages, input, handleSubmit, setMessages, append } = useChat({
  // API 路徑
  api: '/api/chat',

  // 初始訊息
  initialMessages: [
    { id: '1', role: 'assistant', content: '你好!有什麼我可以幫忙的嗎?' },
  ],

  // 送出時的額外資料
  body: {
    userId: 'user-123',
  },

  // 串流完成時的回呼
  onFinish: (message) => {
    console.log('AI 回覆完成:', message);
  },

  // 錯誤處理
  onError: (error) => {
    console.error('錯誤:', error);
  },
});

手動新增訊息

// 程式化新增訊息
await append({
  role: 'user',
  content: '這是一個程式新增的訊息',
});

// 重置對話
setMessages([]);

客製化 AI 行為

設定 System Prompt

// 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 = await streamText({
    model: openai('gpt-4-turbo'),
    system: `你是一個專業的客服助理。
請遵循以下規則:
1. 使用繁體中文回答
2. 保持友善和專業
3. 如果不確定答案,請誠實說不知道
4. 回答要簡潔明瞭`,
    messages,
  });

  return result.toDataStreamResponse();
}

控制回應參數

const result = await streamText({
  model: openai('gpt-4-turbo'),
  messages,
  temperature: 0.7,     // 創意程度 (0-2)
  maxTokens: 1000,      // 最大回應長度
  topP: 0.9,            // 核心採樣
});

使用不同模型

import { openai } from '@ai-sdk/openai';
import { anthropic } from '@ai-sdk/anthropic';

// OpenAI
const result = await streamText({
  model: openai('gpt-4-turbo'),
  messages,
});

// Anthropic Claude
const result = await streamText({
  model: anthropic('claude-3-opus-20240229'),
  messages,
});

實作:智能客服機器人

完整的客服機器人範例。

API Route

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

const SYSTEM_PROMPT = `你是 VibeFix 的智能客服助理。

關於 VibeFix:
- 我們提供 AI 程式開發和部署服務
- 主要服務:網站部署問題排解、AI 整合開發
- 聯繫方式:vibefix.dev

回答規則:
1. 使用繁體中文
2. 保持專業友善
3. 不確定的問題,引導用戶聯繫真人客服
4. 回答簡潔,每次不超過 150 字`;

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

  const result = await streamText({
    model: openai('gpt-4-turbo'),
    system: SYSTEM_PROMPT,
    messages,
    temperature: 0.3, // 較低的溫度,回答更一致
  });

  return result.toDataStreamResponse();
}

前端元件

// components/CustomerServiceChat.tsx
'use client';

import { useChat } from 'ai/react';
import { useState } from 'react';

export function CustomerServiceChat() {
  const [isOpen, setIsOpen] = useState(false);

  const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
    api: '/api/customer-service',
    initialMessages: [
      {
        id: 'welcome',
        role: 'assistant',
        content: '您好!我是 VibeFix 智能客服,有什麼可以幫您的嗎?',
      },
    ],
  });

  if (!isOpen) {
    return (
      <button
        onClick={() => setIsOpen(true)}
        className="fixed bottom-4 right-4 bg-blue-500 text-white p-4 rounded-full shadow-lg"
      >
        💬 客服
      </button>
    );
  }

  return (
    <div className="fixed bottom-4 right-4 w-80 h-96 bg-white rounded-lg shadow-xl flex flex-col">
      {/* 標題列 */}
      <div className="bg-blue-500 text-white p-3 rounded-t-lg flex justify-between">
        <span>智能客服</span>
        <button onClick={() => setIsOpen(false)}>✕</button>
      </div>

      {/* 訊息區 */}
      <div className="flex-1 overflow-y-auto p-3 space-y-2">
        {messages.map((m) => (
          <div
            key={m.id}
            className={`p-2 rounded text-sm ${
              m.role === 'user'
                ? 'bg-blue-100 ml-8'
                : 'bg-gray-100 mr-8'
            }`}
          >
            {m.content}
          </div>
        ))}
        {isLoading && (
          <div className="bg-gray-100 p-2 rounded text-sm mr-8">
            正在輸入...
          </div>
        )}
      </div>

      {/* 輸入區 */}
      <form onSubmit={handleSubmit} className="p-3 border-t flex gap-2">
        <input
          value={input}
          onChange={handleInputChange}
          placeholder="輸入問題..."
          className="flex-1 border rounded p-2 text-sm"
        />
        <button
          type="submit"
          disabled={isLoading}
          className="bg-blue-500 text-white px-3 py-2 rounded text-sm"
        >
          送出
        </button>
      </form>
    </div>
  );
}

進階:Tool Calling(函數呼叫)

讓 AI 可以執行特定功能。

定義 Tools

// app/api/chat-with-tools/route.ts
import { openai } from '@ai-sdk/openai';
import { streamText, tool } from 'ai';
import { z } from 'zod';

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

  const result = await streamText({
    model: openai('gpt-4-turbo'),
    messages,
    tools: {
      // 查詢天氣
      getWeather: tool({
        description: '查詢指定城市的天氣',
        parameters: z.object({
          city: z.string().describe('城市名稱'),
        }),
        execute: async ({ city }) => {
          // 實際應該呼叫天氣 API
          return {
            city,
            temperature: 25,
            condition: '晴天',
          };
        },
      }),

      // 搜尋產品
      searchProducts: tool({
        description: '搜尋產品',
        parameters: z.object({
          query: z.string().describe('搜尋關鍵字'),
          maxResults: z.number().default(5),
        }),
        execute: async ({ query, maxResults }) => {
          // 實際應該查詢資料庫
          return {
            products: [
              { name: '產品 A', price: 100 },
              { name: '產品 B', price: 200 },
            ],
          };
        },
      }),
    },
  });

  return result.toDataStreamResponse();
}

前端處理 Tool 結果

'use client';

import { useChat } from 'ai/react';

export default function ChatWithTools() {
  const { messages, input, handleSubmit, handleInputChange } = useChat({
    api: '/api/chat-with-tools',
    maxToolRoundtrips: 5, // 允許多次 tool 呼叫
  });

  return (
    <div>
      {messages.map((m) => (
        <div key={m.id}>
          {m.role === 'user' ? '你' : 'AI'}: {m.content}

          {/* 顯示 tool 呼叫結果 */}
          {m.toolInvocations?.map((tool, i) => (
            <div key={i} className="bg-gray-50 p-2 mt-2 rounded">
              <strong>呼叫工具:{tool.toolName}</strong>
              <pre>{JSON.stringify(tool.result, null, 2)}</pre>
            </div>
          ))}
        </div>
      ))}

      <form onSubmit={handleSubmit}>
        <input value={input} onChange={handleInputChange} />
        <button type="submit">送出</button>
      </form>
    </div>
  );
}

錯誤處理

API Route 錯誤處理

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

    const result = await streamText({
      model: openai('gpt-4-turbo'),
      messages,
    });

    return result.toDataStreamResponse();
  } catch (error) {
    console.error('AI API Error:', error);

    if (error.code === 'insufficient_quota') {
      return Response.json(
        { error: 'API 額度不足' },
        { status: 402 }
      );
    }

    if (error.code === 'rate_limit_exceeded') {
      return Response.json(
        { error: '請求過於頻繁,請稍後再試' },
        { status: 429 }
      );
    }

    return Response.json(
      { error: '發生錯誤,請稍後再試' },
      { status: 500 }
    );
  }
}

前端錯誤處理

const { messages, error, reload } = useChat({
  onError: (error) => {
    console.error('Chat error:', error);
    // 可以顯示通知或 toast
  },
});

// 顯示錯誤並提供重試
{error && (
  <div className="bg-red-100 p-3 rounded">
    <p>發生錯誤:{error.message}</p>
    <button onClick={reload}>重試</button>
  </div>
)}

部署到 Vercel

設定環境變數

  1. 進入 Vercel Dashboard
  2. 選擇專案 → Settings → Environment Variables
  3. 新增 OPENAI_API_KEY

部署

vercel

或透過 GitHub 自動部署。

注意事項

函數超時:

Hobby 方案有 10 秒限制。如果 AI 回覆需要更長時間,考慮:

  • 升級到 Pro 方案(60 秒)
  • 使用 Edge Functions
  • 減少 maxTokens
// 使用 Edge Runtime
export const runtime = 'edge';

export async function POST(req: Request) {
  // Edge Functions 有 30 秒限制
}

常見問題 FAQ

Q1:API Key 被盜用怎麼辦?

  1. 立即在 OpenAI/Anthropic Dashboard 刪除舊 Key
  2. 建立新 Key
  3. 更新 Vercel 環境變數
  4. 設定 API 使用量限制

Q2:串流回應很慢?

可能原因:

  • 模型選擇太大(GPT-4 比 GPT-3.5 慢)
  • 網路問題
  • API 負載高

解決方法:

// 使用較快的模型
model: openai('gpt-3.5-turbo'),

// 或限制輸出長度
maxTokens: 500,

Q3:如何控制成本?

  1. 設定 maxTokens 限制
  2. 使用較便宜的模型
  3. 在 OpenAI Dashboard 設定用量上限
  4. 加入用戶級別的限制
// 限制每個用戶的請求次數
const rateLimiter = new RateLimiter();
if (!rateLimiter.isAllowed(userId)) {
  return Response.json({ error: '請求過於頻繁' }, { status: 429 });
}

Q4:可以同時支援多個 AI 模型嗎?

可以:

const modelProviders = {
  openai: openai('gpt-4-turbo'),
  anthropic: anthropic('claude-3-opus-20240229'),
};

const model = modelProviders[selectedModel];

Vercel 部署失敗?

Build Error、環境變數、自訂網域,我們幫你快速排除問題。

解決 Vercel 問題


延伸閱讀

分享文章:
V

VibeFix

專門解決 AI Vibe Coding 後的疑難雜症,讓你的專案順利上線。

這篇文章有幫到你嗎?

如果還有問題,讓我們直接幫你解決!

聯繫我們