技术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

分享

分享这篇文章。