從函式到 Tool:用 Vercel AI SDK 開啟工具呼叫的大門

如果你已經習慣在 Next.js 裡寫 API Route、呼叫第三方介面,那你現在離「做一個能自己調介面的大模型助手」只差半步:把這些函式包裝成 Tool,交給模型自己決定什麼時候用。
這篇文章,我們先不聊 MCP、Agent 框架這些「大詞」,只做一件事:
幫你從「寫一個普通函式」走到「寫一個可以被大模型呼叫的 Tool」,順便把背後的心智模型講清楚。
讀完之後,你應該能做到:
知道什麼是 Vercel AI SDK 裡的 Tool,和普通函式有什麼區別。
會寫一個最小可用的 Tool,並在 Next.js 裡跑起來。
了解如何設計 Tool 的入參/回傳值,讓模型更好用你的程式碼。
大致知道:今天先只用 Tool 就能搞定很多應用,MCP 這種協定可以留到後面再說。
1. 為什麼需要「工具」這一層?
先想像一個簡單需求:你想做一個「查天氣的聊天機器人」。
最 naive 的做法是:
用戶輸入「幫我查一下北京今天的天氣」。
你在伺服器上拿到這句自然語言。
自己寫一段 if/else 或正則,把城市名摳出來。
調天氣 API,拿到結果。
把結果塞回 prompt,讓模型組織成一段話發給用戶。
這個方案能用,但有明顯問題:
邏輯寫死在你的程式碼裡,模型只是個「高級模板引擎」。
每加一個功能(查匯率、查日程)都要多寫一段解析 + 呼叫邏輯。
模型完全不知道「有哪些能力可以用」,你得手把手「開道」。
Tool 這一層,就是把「你那段 if/else + 調介面」上升為一個模型可見的能力。
換個說法:
你寫函式,定義好「我能做什麼」「需要什麼參數」「會回傳什麼」。
你告訴模型:這些是你可以呼叫的工具。
模型根據用戶輸入,自行決定要不要呼叫哪個工具,以及傳什麼參數。
這一步的抽象,正是 Vercel AI SDK 的 Tool 在幫你做的。
2. Vercel AI SDK 裡的 Tool 是什麼?
你可以先把 Tool 理解為:
一個「帶說明書的函式」,說明書是給模型看的,函式體是給你自己的程式碼看的。
在 Vercel AI SDK 裡,一個 Tool 一般包含三塊資訊:
description:一句話說明這個工具是幹嘛的。parameters:用 JSON Schema(或者 Zod)描述需要什麼入參。execute:真正執行的函式,比如呼叫 HTTP API、查資料庫。
它長相大概是這樣(偽程式碼,先感受定義):
import { tool } from 'ai';
const getWeather = tool({
description: '查詢指定城市的即時天氣',
parameters: {
type: 'object',
properties: {
city: { type: 'string', description: '城市名稱,比如 Beijing' },
},
required: ['city'],
},
execute: async ({ city }) => {
// 這裡就是你平時會寫的業務程式碼
const res = await fetch(`https://api.example.com/weather?city=${city}`);
return await res.json();
},
});之後,在呼叫模型的時候,你把這個 getWeather 傳給 AI SDK:
SDK 會負責把
description和parameters轉成模型能理解的「工具列表」。模型在理解用戶意圖時,會思考:「要不要用 getWeather?如果用,該傳什麼
city?」一旦模型決定呼叫,SDK 會把參數餵給
execute,拿到回傳結果,再繼續讓模型生成最終回覆。
你不用再自己寫「解析城市名」「決定什麼時候調介面」這一坨邏輯,把「主動權」交給了模型。
3. 一個最小可用的 Tool 範例
我們來寫一個真正能跑起來的最小例子:查當前時間。
3.1 定義一個簡單 Tool
假設你已經在 Next.js 專案裡裝好了 ai 這個套件(Vercel AI SDK)。在伺服端我們建立一個簡單 Tool:
// app/api/chat/tools.ts
import { tool } from 'ai';
export const getCurrentTime = tool({
description: '取得目前伺服器時間(ISO 字串)',
parameters: {
type: 'object',
properties: {},
},
async execute() {
return {
now: new Date().toISOString(),
};
},
});這裡有幾個點值得注意:
parameters是一個空物件:表示這個工具不需要入參。execute回傳一個物件{ now: string },而不是直接回傳字串,這樣方便後面做結構化處理(比如前端展示、後續計算)。
3.2 在 API Route 裡使用 Tool
接下來,我們寫一個最簡單的聊天 API,允許模型在對話中呼叫這個 Tool。具體呼叫細節會隨版本略有差異,這裡只強調「結構」和「思路」,偽程式碼類似這樣:
// app/api/chat/route.ts
import { NextRequest } from 'next/server';
import { streamText } from 'ai'; // 假設使用 AI SDK 的某個呼叫方法
import { getCurrentTime } from './tools';
import { openai } from '@ai-sdk/openai';
export async function POST(req: NextRequest) {
const { messages } = await req.json();
const response = await streamText({
model: openai('gpt-4o'), // 你選的模型
messages,
tools: {
getCurrentTime,
},
});
return response.toAIStreamResponse();
}這裡有幾個核心點:
tools這個欄位,把你定義的 Tool 暴露給了模型。模型收到用戶訊息後,如果判斷需要當前時間,就會「發起一次對 getCurrentTime 的呼叫」,SDK 會自動幫你執行
execute。Tool 的呼叫結果會被餵回模型,讓它根據時間繼續生成最終回覆。
3.3 一個簡單的前端聊天介面
前端可以用 useChat 這類 Hook 來做一個最簡聊天 UI,這部分不是這篇文章的重點,就先不展開了。重要的是:從前端視角看,和普通 Chat API 沒什麼差別,只是模型現在「會自己查時間」了。
4. Tool 和普通函式的本質區別
到這裡你可能會問:那我直接在路由裡寫 new Date().toISOString() 不也能查時間嗎?為什麼要繞一圈 Tool?
關鍵區別在於 誰負責「決策」:
普通函式:你在業務程式碼裡顯式呼叫,完全由你來決定「什麼時候調」。
Tool:你只是把「這個能力」掛出來,並告訴模型「如何使用」,至於「用不用」「怎麼用」,交給模型決策。
這帶來幾個實際好處:
少寫 if/else
不用你來解析自然語言、比對關鍵字、選擇分支。
模型根據上下文自己判斷要不要用某個 Tool。
支援多個 Tool 自動編排
定義多個工具(查時間、查天氣、查日程),模型可以在一次對話中按需呼叫多個。
隨著工具增多,你不需要維護一堆「業務路由表」,模型自己「想辦法」。
跨模型統一
不同模型廠商的 Tool / Function Calling 介面各不相同,而 AI SDK 幫你統一掉了這些細節。
你只需要維護一套 Tool 定義,就可以在不同模型之間切換。
你可以把 Tool 看成是:
在「模型 ↔ 你的程式碼」之間加了一層「帶結構化元資料的函式」,讓這條鏈路既智慧,又可控制。
5. 設計一個好用的 Tool:參數與回傳值
寫 Tool 不是「能跑就行」,參數和回傳值設計好了,模型才能更穩定、可控地使用它。
5.1 參數:幫模型把事情說清楚
參數定義建議遵循幾個小原則:
能列舉的就列舉:比如
unit: 'celsius' | 'fahrenheit'。能拆成欄位的就拆開:不要把「城市+日期」合成一個字串,讓模型自己拆。
用
description給模型一些例子:它真的會看(或者說,訓練時看過類似模式)。
範例:
const getWeather = tool({
description: '查詢指定城市在指定日期的天氣',
parameters: {
type: 'object',
properties: {
city: {
type: 'string',
description: '城市名稱,比如 Beijing、Shanghai',
},
date: {
type: 'string',
description: '日期,格式為 YYYY-MM-DD,比如 2025-05-17',
},
unit: {
type: 'string',
enum: ['celsius', 'fahrenheit'],
description: '溫度單位,預設 celsius',
},
},
required: ['city', 'date'],
},
async execute({ city, date, unit = 'celsius' }) {
// ...
},
});5.2 回傳值:盡量結構化,而不是一坨字串
雖然你可以直接在 execute 裡回傳一句「北京今天 25 度,晴」,但這會損失很多靈活性:
前端沒法直接拿這個結果做後續展示或計算。
模型也沒法方便地在後續呼叫中複用結構化資訊。
更推薦的做法是回傳一個清晰的物件:
async execute({ city, date, unit = 'celsius' }) {
const data = await fetchWeather(city, date, unit);
return {
city,
date,
unit,
temperature: data.temp,
condition: data.condition, // sunny, cloudy, etc.
};
}這樣模型既可以自己組織語言回覆用戶,也可以在後續步驟中繼續使用這些欄位(比如再呼叫別的 Tool)。
6. 一次性接多個 Tool:讓模型自己選
前面我們只用一個 getCurrentTime,現在可以再加一個 getWeather,一起丟給模型。
// app/api/chat/tools.ts
export const tools = {
getCurrentTime,
getWeather,
};在呼叫模型時:
const response = await streamText({
model: openai('gpt-4o'),
messages,
tools,
});此時,如果用戶說:
「幫我看下明天上海的天氣,如果是晴天順便提醒一下現在的時間。」
模型有機會做這樣一件事(邏輯層面):
先呼叫
getWeather,判斷「是不是晴天」。再呼叫
getCurrentTime,拿到當前時間。生成一段最終回覆,融合兩次呼叫結果。
你不需要為這串邏輯寫任何「業務流程圖」,只要:
把工具列好,參數定義清楚。
在系統 Prompt 裡大致告訴模型「遇到這種需求要善用工具」。
這就是「工具呼叫」的真正威力所在:你從「寫流程」變成「定義能力」。
7. 這一篇的收尾:先把 Tool 玩熟,再考慮協定層
這篇我們只做了三件事:
把 Tool 從概念上拆開:它就是「帶說明書的函式」,說明書是給模型看的。
寫了一個最小的 Tool 範例,並展示了如何在 Next.js API Route 裡用它。
講了如何設計參數和回傳值,讓模型更好用你的工具。
只靠 Vercel AI SDK 的 Tool,你已經可以做出:
內部的客服助手 / 工單助手。
SaaS 產品裡的智慧查詢 / 智慧生成。
各種「幫我查」「幫我算」「幫我記」的業務助手。
而且在這個階段,你完全可以不關心 MCP 等協定層的東西——它們主要解決的是「跨宿主、跨專案複用工具」的問題,更適合在工具數量和呼叫環境都變複雜之後再慢慢引入。
下一篇,我們會在「只用 Tool」的基礎上,專門聊聊:當工具越來越多、呼叫環境越來越複雜時,單靠 Tool 會遇到什麼邊界和痛點,以及為什麼會出現像 MCP 這樣的協定。
在 Google 上持續關注
把 HeyBinyang 加入 Google 首選來源
如果你希望之後在 Google 上更容易看到我的更新,可以把這個站點加入 preferred source,讓它在相關閱讀情境裡更容易被找到。
SHARE
分享
分享這篇文章。