Tech0 views

From Functions to Tools: Unlocking Tool Calling with Vercel AI SDK

If you're already comfortable writing API Routes in Next.js and calling third-party APIs, you're just half a step away from building a large model assistant that can call its own interfaces: wrap those functions as Tools, and let the model decide when to use them.

In this article, we won't dive into big words like MCP or Agent frameworks. We'll do just one thing:

Help you go from "writing a normal function" to "writing a Tool that can be called by a large model", while explaining the mental model behind it.

After reading this, you should be able to:

1. Why do we need this "Tool" layer?

First, imagine a simple requirement: you want to build a "weather check chatbot".

The most naive approach is:

  1. The user types "Check today's weather in Beijing for me."

  2. You receive this natural language sentence on the server.

  3. You write some if/else or regex to extract the city name.

  4. Call a weather API to get the result.

  5. Stuff the result back into a prompt and have the model compose a reply for the user.

This approach works, but has obvious problems:

The Tool layer elevates that "if/else + API call" into a capability visible to the model.

In other words:

This abstraction is exactly what the Vercel AI SDK's Tool provides.

2. What is a Tool in the Vercel AI SDK?

You can think of a Tool as:

A "function with instructions" — the instructions are for the model, the function body is for your own code.

In the Vercel AI SDK, a Tool typically contains three pieces of information:

  1. description: A one-line explanation of what this tool does.

  2. parameters: Describes the required input using JSON Schema (or Zod).

  3. execute: The actual function to execute, e.g., calling an HTTP API or querying a database.

It looks something like this (pseudocode to give you a feel for the definition):

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

Then, when calling the model, you pass this getWeather to the AI SDK:

You no longer have to write the logic for "parsing the city name" and "deciding when to call the API" yourself — you hand over the initiative to the model.

3. A Minimal Working Example of a Tool

Let's write a truly minimal runnable example: get the current time.

3.1 Define a Simple Tool

Assume you already have the ai package (Vercel AI SDK) installed in your Next.js project. On the server, we create a simple 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(),
    };
  },
});

A few points to note:

3.2 Using the Tool in an API Route

Next, we write a very simple chat API that allows the model to call this Tool during a conversation. The specific API call details may vary slightly by version; here we focus on the "structure" and "approach". The pseudocode looks something like this:

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

Key points here:

3.3 A Simple Frontend Chat Interface

On the frontend, you can use a hook like useChat to build a minimal chat UI. This part isn't the focus of this article, so we won't elaborate. The important thing is: from the frontend's perspective, it's no different from a regular Chat API — except the model can now "look up the time" on its own.

4. The Essential Difference Between a Tool and a Normal Function

At this point you might ask: couldn't I just write new Date().toISOString() directly in the route to get the time? Why go through the trouble of a Tool?

The key difference is who makes the decision:

This brings several practical benefits:

  1. Less if/else

    • You don't have to parse natural language, match keywords, or choose branches.

    • The model decides on its own whether to use a certain Tool based on context.

  2. Supports multiple Tools orchestrated automatically

    • Define multiple tools (check time, check weather, check schedule); the model can call them as needed within a single conversation.

    • As the number of tools grows, you don't need to maintain a bunch of "business routing tables"; the model figures it out itself.

  3. Cross-model consistency

    • Different model providers have different Tool / Function Calling interfaces; the AI SDK unifies these details for you.

    • You only need to maintain one set of Tool definitions to switch between different models.

You can think of Tool as:

Adding a layer of "functions with structured metadata" between the model and your code, making the pipeline both intelligent and controllable.

5. Designing a Good Tool: Parameters and Return Values

Writing a Tool isn't just about "making it work". Good parameter and return value design helps the model use it more reliably and controllably.

5.1 Parameters: Help the Model Be Clear

Follow these small principles when defining parameters:

Example:

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 Return Values: Prefer Structured Data over Raw Strings

While you could directly return something like "Beijing today is 25 degrees, sunny" from execute, doing so loses flexibility:

A better approach is to return a clear object:

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.
  };
}

This way, the model can both compose a natural language response for the user and continue to use these fields in subsequent steps (e.g., calling another Tool).

6. Wiring Up Multiple Tools at Once: Let the Model Choose

Earlier we only used one tool, getCurrentTime. Now let's add another, getWeather, and pass them together to the model.

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

When calling the model:

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

Now if the user says:

"Check tomorrow's weather in Shanghai for me, and if it's sunny, also remind me of the current time."

The model can (logically) do the following:

  1. First call getWeather to check if it's sunny.

  2. Then call getCurrentTime to get the current time.

  3. Generate a final response combining the results of both calls.

You don't need to write any "business flow chart" for this logic; just:

This is the real power of "Tool calling": you move from "writing flows" to "defining capabilities".

7. Wrapping Up: Master Tools First, Then Consider Protocol Layers

In this article, we did just three things:

  1. Broke down the Tool conceptually: it's a "function with instructions" — the instructions are for the model.

  2. Wrote a minimal Tool example and demonstrated how to use it in a Next.js API Route.

  3. Explained how to design parameters and return values so the model can use your tools better.

With just the Vercel AI SDK's Tool, you can already build:

Andat this stage, you can completely ignore protocol layers like MCP — they mainly solve the problem of "reusing tools across hosts and projects", which is better introduced gradually when the number of tools and the calling environment become more complex.

In the next article, based on "just using Tools", we'll specifically discuss: when tools increase and the calling environment gets more complex, what boundaries and pain points arise from relying solely on Tools, and why protocols like MCP emerge.

SHARE

Share

Share this article.