Vercel AI SDK 完整教學|2025 建立 AI 應用程式入門指南
想在網站加入 AI 功能嗎?
Vercel AI SDK 是目前最簡單的方式。
它幫你處理了串流、錯誤處理、狀態管理等複雜問題。
這篇文章手把手教你從零開始建立 AI 應用。
什麼是 Vercel AI SDK?
Vercel AI SDK 是一套專為 AI 應用設計的開發工具。
核心功能
| 功能 | 說明 |
|---|---|
| 串流回應 | 即時顯示 AI 回覆,不用等全部完成 |
| 統一 API | 用同一套程式碼串接不同 AI 模型 |
| React Hooks | 現成的 useChat、useCompletion 等 |
| 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
設定環境變數
- 進入 Vercel Dashboard
- 選擇專案 → Settings → Environment Variables
- 新增
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 被盜用怎麼辦?
- 立即在 OpenAI/Anthropic Dashboard 刪除舊 Key
- 建立新 Key
- 更新 Vercel 環境變數
- 設定 API 使用量限制
Q2:串流回應很慢?
可能原因:
- 模型選擇太大(GPT-4 比 GPT-3.5 慢)
- 網路問題
- API 負載高
解決方法:
// 使用較快的模型
model: openai('gpt-3.5-turbo'),
// 或限制輸出長度
maxTokens: 500,
Q3:如何控制成本?
- 設定
maxTokens限制 - 使用較便宜的模型
- 在 OpenAI Dashboard 設定用量上限
- 加入用戶級別的限制
// 限制每個用戶的請求次數
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、環境變數、自訂網域,我們幫你快速排除問題。