技術0 閱讀

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

如果你已經習慣在 Next.js 裡寫 API Route、呼叫第三方介面,那你現在離「做一個能自己調介面的大模型助手」只差半步:把這些函式包裝成 Tool,交給模型自己決定什麼時候用。

這篇文章,我們先不聊 MCP、Agent 框架這些「大詞」,只做一件事:

幫你從「寫一個普通函式」走到「寫一個可以被大模型呼叫的 Tool」,順便把背後的心智模型講清楚。

讀完之後,你應該能做到:

1. 為什麼需要「工具」這一層?

先想像一個簡單需求:你想做一個「查天氣的聊天機器人」。

最 naive 的做法是:

  1. 用戶輸入「幫我查一下北京今天的天氣」。

  2. 你在伺服器上拿到這句自然語言。

  3. 自己寫一段 if/else 或正則,把城市名摳出來。

  4. 調天氣 API,拿到結果。

  5. 把結果塞回 prompt,讓模型組織成一段話發給用戶。

這個方案能用,但有明顯問題:

Tool 這一層,就是把「你那段 if/else + 調介面」上升為一個模型可見的能力。

換個說法:

這一步的抽象,正是 Vercel AI SDK 的 Tool 在幫你做的。

2. Vercel AI SDK 裡的 Tool 是什麼?

你可以先把 Tool 理解為:

一個「帶說明書的函式」,說明書是給模型看的,函式體是給你自己的程式碼看的。

在 Vercel AI SDK 裡,一個 Tool 一般包含三塊資訊:

  1. description:一句話說明這個工具是幹嘛的。

  2. parameters:用 JSON Schema(或者 Zod)描述需要什麼入參。

  3. execute:真正執行的函式,比如呼叫 HTTP API、查資料庫。

它長相大概是這樣(偽程式碼,先感受定義):

ts
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:

你不用再自己寫「解析城市名」「決定什麼時候調介面」這一坨邏輯,把「主動權」交給了模型。

3. 一個最小可用的 Tool 範例

我們來寫一個真正能跑起來的最小例子:查當前時間

3.1 定義一個簡單 Tool

假設你已經在 Next.js 專案裡裝好了 ai 這個套件(Vercel AI SDK)。在伺服端我們建立一個簡單 Tool:

ts
// 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(),
    };
  },
});

這裡有幾個點值得注意:

3.2 在 API Route 裡使用 Tool

接下來,我們寫一個最簡單的聊天 API,允許模型在對話中呼叫這個 Tool。具體呼叫細節會隨版本略有差異,這裡只強調「結構」和「思路」,偽程式碼類似這樣:

ts
// 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();
}

這裡有幾個核心點:

3.3 一個簡單的前端聊天介面

前端可以用 useChat 這類 Hook 來做一個最簡聊天 UI,這部分不是這篇文章的重點,就先不展開了。重要的是:從前端視角看,和普通 Chat API 沒什麼差別,只是模型現在「會自己查時間」了。

4. Tool 和普通函式的本質區別

到這裡你可能會問:那我直接在路由裡寫 new Date().toISOString() 不也能查時間嗎?為什麼要繞一圈 Tool?

關鍵區別在於 誰負責「決策」

這帶來幾個實際好處:

  1. 少寫 if/else

    • 不用你來解析自然語言、比對關鍵字、選擇分支。

    • 模型根據上下文自己判斷要不要用某個 Tool。

  2. 支援多個 Tool 自動編排

    • 定義多個工具(查時間、查天氣、查日程),模型可以在一次對話中按需呼叫多個。

    • 隨著工具增多,你不需要維護一堆「業務路由表」,模型自己「想辦法」。

  3. 跨模型統一

    • 不同模型廠商的 Tool / Function Calling 介面各不相同,而 AI SDK 幫你統一掉了這些細節。

    • 你只需要維護一套 Tool 定義,就可以在不同模型之間切換。

你可以把 Tool 看成是:

在「模型 ↔ 你的程式碼」之間加了一層「帶結構化元資料的函式」,讓這條鏈路既智慧,又可控制。

5. 設計一個好用的 Tool:參數與回傳值

寫 Tool 不是「能跑就行」,參數和回傳值設計好了,模型才能更穩定、可控地使用它。

5.1 參數:幫模型把事情說清楚

參數定義建議遵循幾個小原則:

範例:

ts
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 度,晴」,但這會損失很多靈活性:

更推薦的做法是回傳一個清晰的物件:

ts
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,一起丟給模型。

ts
// app/api/chat/tools.ts
export const tools = {
  getCurrentTime,
  getWeather,
};

在呼叫模型時:

ts
const response = await streamText({
  model: openai('gpt-4o'),
  messages,
  tools,
});

此時,如果用戶說:

「幫我看下明天上海的天氣,如果是晴天順便提醒一下現在的時間。」

模型有機會做這樣一件事(邏輯層面):

  1. 先呼叫 getWeather,判斷「是不是晴天」。

  2. 再呼叫 getCurrentTime,拿到當前時間。

  3. 生成一段最終回覆,融合兩次呼叫結果。

你不需要為這串邏輯寫任何「業務流程圖」,只要:

這就是「工具呼叫」的真正威力所在:你從「寫流程」變成「定義能力」。

7. 這一篇的收尾:先把 Tool 玩熟,再考慮協定層

這篇我們只做了三件事:

  1. 把 Tool 從概念上拆開:它就是「帶說明書的函式」,說明書是給模型看的。

  2. 寫了一個最小的 Tool 範例,並展示了如何在 Next.js API Route 裡用它。

  3. 講了如何設計參數和回傳值,讓模型更好用你的工具。

只靠 Vercel AI SDK 的 Tool,你已經可以做出:

而且在這個階段,你完全可以不關心 MCP 等協定層的東西——它們主要解決的是「跨宿主、跨專案複用工具」的問題,更適合在工具數量和呼叫環境都變複雜之後再慢慢引入。

下一篇,我們會在「只用 Tool」的基礎上,專門聊聊:當工具越來越多、呼叫環境越來越複雜時,單靠 Tool 會遇到什麼邊界和痛點,以及為什麼會出現像 MCP 這樣的協定。

SHARE

分享

分享這篇文章。