A Guide to Tool Calling with the Vercel AI SDK
In our last guide, we built an AI chat app that runs in the terminal. It can remember conversations and even generate structured data. But it has a limitation: it’s living in the past, completely disconnected from the real world.
Ask it “What time is it right now?” and it can’t tell you. Request a summary of a breaking news article, and it’s helpless. Your sophisticated AI assistant, for all its intelligence, is like a brilliant scholar locked in a library with books from January 1700. It can reason about what it knows, but it can’t reach out and grab today’s newspaper.
This is where tool calling changes everything.
Tool calling, sometimes called function calling, is the bridge between your AI’s reasoning capabilities and the outside world. It transforms your chatbot from an isolated knowledge base into an agent that can:
- Augment its knowledge with real-time data (like getting the current time or weather)
 - Extend its capabilities by accessing external services (like summarising web pages or querying databases)
 - Take actions on behalf of users (like sending emails or booking appointments)
 
In this guide, we’ll upgrade the conversing-bot.ts from the previous article by adding two distinct types of tools:
- A custom tool: A simple 
getCurrentTimefunction that gives the AI access to your local system time - A provider-native tool: Gemini’s built-in 
url_contexttool, which lets the AI fetch and understand content from any URL without you writing a single line of fetching logic 
By the end, you’ll understand how to connect your AI to virtually anything—APIs, databases, local functions, or third-party services. Let’s get you to building truly capable AI applications.
Note that we won’t cover Model Context Protocol (MCP) in this article. So keep an eye out for a future guide on that topic!
How Does Tool Calling Work?
Before we start coding, let’s understand what happens under the hood. Tool calling might seem like magic, but the mechanics are surprisingly elegant.
When you provide tools to the AI, you’re not just handing it functions to execute blindly. You’re giving it a menu of superpowers that it can intelligently choose to use based on the user’s request.
Here’s the complete flow:
Step 1: The model evaluates the situation. When a user sends a message like “What time is it?”, the LLM examines both the prompt and the descriptions of all available tools. It’s making a decision: “Do I need external help to answer this, and if so, which tool should I use?”
Step 2: The model requests a tool call. Instead of generating text directly, the LLM generates a structured request: “I need to call the getCurrentTime tool with no parameters.” This isn’t text meant for the user, it’s an instruction for the system.
Step 3: The AI SDK orchestrates the execution. The Vercel AI SDK intercepts this request, finds your getCurrentTime function, executes it, and captures the result.
Step 4: The model formulates the final answer. The tool’s result is sent back to the LLM as additional context. The model then uses this fresh information to craft its final, user-facing response: “The current time is 14:32.”
The beauty of the AI SDK is that it handles this entire orchestration automatically. You define the tools, and the SDK manages the complex back-and-forth. Now, let’s build this in practice.
Part 1: Your First Tool – Accessing Current Time
We’ll start with something delightfully simple: teaching the model to tell the time. This might seem trivial, but it demonstrates the fundamental pattern you’ll use for all custom tools.
Step 1: Defining the Tool
First, we need to import the tool helper from the AI SDK. Add this to your imports at the top of your file alongside your existing imports:
import { generateText, stepCountIs, tool } from "ai";
Now, let’s create the tool. The tool() function takes an object that defines everything the AI needs to know about this capability. Add this code before the main() function:
const timeTool = tool({
  description: "Get the current time to answer user questions about time.",
  inputSchema: undefined,
  execute: async () => {
    const now = new Date();
    return `The current time is ${now.toLocaleTimeString()}.`;
  },
});
Let’s break down what each property does:
- 
description: This is what the AI reads to decide when to use the tool. Think of it as the tool’s documentation written for an AI, not a human. A clear, specific description is crucial because the better you describe the tool’s purpose, the better the AI will know when to invoke it. - 
inputSchema: This defines what parameters the tool expects. ForgetCurrentTime, we don’t need any input (the current time is the current time, after all), so we set this toundefined. In a future article, we’ll explore tools that require parameters—for example, a weather tool that needs a city name. - 
execute: This is your actual TypeScript function. The AI SDK will call this when the AI decides to use the tool. Here, we’re simply creating a newDateobject and formatting it. You could just as easily query a database, call an API, or read from a file. 
The execute function is just JavaScript, and you have the full power of Node.js at your disposal. Need to connect to PostgreSQL? Import your database client. Want to call a REST API? Use fetch(). The sky’s the limit ;)
Step 2: Integrating the Tool into Your Chatbot
Now we need to tell the model about this new capability. Open the conversing-bot.ts file and locate the generateText call inside the while loop. We’ll modify it to include the tool.
Update your generateText call to look like this:
const { text } = await generateText({
  model: google("gemini-2.5-flash-lite"),
  messages: messages,
  system:
    "You are a helpful and friendly AI assistant who uses the tools to help the user achieve various tasks. Keep your responses concise and conversational.",
  tools: {
    getCurrentTime: timeTool,
  },
  stopWhen: stepCountIs(2),
});
Notice two key changes:
- 
We’ve updated the
systemprompt to inform the model it has tools available. Whilst the AI SDK automatically tells the model about tool descriptions, updating the system prompt helps reinforce its role, if needed. - 
We’ve added the
toolsproperty, which is an object mapping tool names to tool definitions. The key (getCurrentTime) is what the AI “sees” as the tool’s name, so make it descriptive. - 
We’ve added
stopWhen: stepCountIs(2), allowing the AI SDK to make up to 2 steps (1 tool call + 1 response generation). This gives it the flexibility to call the tool and then respond. The default is 1, which only allows direct responses without tool calls. 
See It in Action
That’s it. No additional configuration, no complex setup. Run your chatbot:
node --env-file=.env conversing-bot.ts
Now try asking questions that require real-time information:
- “What time is it?”
 - “Can you tell me the current time?”
 - “What’s the time right now?”
 
Without the tool, the model would either refuse (saying it doesn’t have access to real-time information) or make an incorrect guess based on context clues in your conversation. With the tool, it confidently executes your function and provides the accurate answer.
Here’s what’s happening behind the scenes: The model reads your prompt, scans the tool descriptions, recognises that getCurrentTime is relevant, generates a tool call request, the SDK executes your function, and the model uses the result to craft its response. All of this happens in milliseconds.
Part 2: Using Provider-Native Tools – Accessing the Web
Custom tools are powerful, but they require you to write all the logic yourself. What if you want your AI to fetch and understand web content? You could write a tool that uses fetch and parses HTML, but it’s simpler if your model provider already has that capability built-in.
AI providers like Google and OpenAI are building sophisticated tools directly into their models. These provider-native tools handle complex operations—web searching, code interpretation, image analysis—without you writing a single line of code. Your job is simply to enable them. I built a tool for fetching URLs in the past, but now Gemini has it natively and it’s much better!
Let’s add Gemini’s url_context tool, which allows the model to fetch and comprehend content from any URL.
Understanding Provider-Native Tools
The url_context tool is different from our custom getCurrentTime in one crucial way: you don’t define the execute function. Google’s infrastructure handles the entire process of fetching the URL, parsing the content, and extracting the relevant information. The AI SDK just needs to know you want to enable this capability.
This is immensely powerful. Web scraping is notoriously fragile because HTML structures change, JavaScript-rendered content requires headless browsers, rate limiting is complex. Provider-native tools abstract all of this away. You enable the tool, and the provider’s infrastructure does the heavy lifting.
Enable Gemini’s url_context Tool
The integration is remarkably simple. You just need to add one line to your tools object. Update your generateText call like this:
const { text } = await generateText({
  model: google("gemini-2.5-flash-lite"),
  messages: messages,
  system:
    "You are a helpful and friendly AI assistant who uses the tools to help the user achieve various tasks beyond the tools capability. Keep your responses concise and conversational.",
  tools: {
    getCurrentTime: timeTool,
    // Add the provider-native url_context tool
    url_context: google.tools.urlContext({}),
  },
  stopWhen: stepCountIs(5),
});
That’s it. The google.tools.urlContext({}) call enables Gemini’s native URL fetching capability. The empty object {} means we’re using the default configuration, but you could customize behaviour here if needed.
Step 3: Multi-Step Tool Calling
There’s one more important addition we should make. Some queries might require the AI to use multiple tools in sequence. For example, a user might ask, “What time is it, and can you summarise this article?” The AI needs to call getCurrentTime, then use url_context, then formulate a response.
We enable this behaviour with the stopWhen parameter, and specifically the stepCountIs helper so that it doesn’t run into an infinite loop if there’s an error. The stopWhen: stepCountIs(5) parameter allows the SDK to chain up to 5 tool calls before generating its final response.
Side Note: This was previously called
maxStepsin earlier versions of the SDK.
Test the App
Run your chatbot again and try these prompts:
- “Can you summarise this page for me? https://ai-sdk.dev/docs/foundations/tools”
 - “What’s on this URL: https://github.com/vercel/ai”
 - “Tell me about https://news.ycombinator.com”
 
Here’s a sample interaction:
❯ node --env-file=.env conversing-bot.ts
┌  🤖 Conversational CLI Bot with Tools
Welcome! I'm your AI assistant. Type your messages and I'll respond.
Type "exit" or "quit" to end the conversation.
│
◇  You:
│  list the trending topics in https://news.ycombinator.com
│
◇  AI response received
│
◇  AI Response ──────────────────────────────────────────────────────────────────────────────────╮
│                                                                                                │
│  Here are the trending topics on Hacker News:                                                  │
│  *   Uv as a beneficial tool for the Python ecosystem.                                         │
│  *   An Azure outage.                                                                          │
│  *   A discussion on how ancient people perceived themselves.                                  │
│  *   Minecraft's decision to remove obfuscation in Java Edition.                               │
│  *   Carlo Rovelli's perspective on reality.                                                   │
│  *   Raspberry Pi Pico's capability to handle 100 Mbit/S Ethernet.                             │
│  *   Creating an iOS app in Assembly.                                                          │
│  *   China's reforestation efforts, adding forest area equivalent to the size of Texas.        │
│  *   Details about IRCd service.                                                               │
│  *   The ease of setting up an Onion mirror.                                                   │
│  *   A piece questioning the speed of Kafka and suggesting Postgres.                           │
│  *   An exploration of dithering in images.                                                    │
│  *   Information on OS/2 Warp, PowerPC Edition.                                                │
│  *   An update on Tailscale Peer Relays.                                                       │
│  *   A new game console called Board that uses physical pieces and has an open SDK.            │
│  *   The emerging role of GLP-1 therapeutics in treating alcohol and substance use disorders.  │
│  *   A call to action to "Keep Android Open."                                                  │
│  *   OpenAI's agreement to remain in California to facilitate its IPO.                         │
│  *   The sale of AOL to Bending Spoons for $1.5 billion.                                       │
│  *   The role of free and open-source software in powering the internet and DNS.               │
│  *   An introduction to Iommi, a tool for Django development.                                  │
│  *   The U.S. National Science Foundation's contribution to Software-Defined Networking.       │
│  *   An article discussing imperfection as a potential key to Turing patterns in nature.       │
│  *   Learnings from a 14-hour AWS outage.                                                      │
│  *   A bionic eye prosthesis that restores sight lost to macular degeneration.                 │
│  *   A critique of Crunchyroll's subtitle practices.                                           │
│  *   Extropic's development of thermodynamic computing hardware.                               │
│  *   The impact of reforestation on cooling the eastern US.                                    │
│  *   A guide on tuning WezTerm.                                                                │
│  *   Information on Composer: building a fast frontier model with RL.                          │
│                                                                                                │
├────────────────────────────────────────────────────────────────────────────────────────────────╯
│
The AI implicitly understands that when you provide a URL, it should use the url_context tool. You don’t need to explicitly say “use the URL tool”. The model’s reasoning capabilities combined with the tool description are enough. This is the power of modern AI: it can understand intent and select the right tool for the job.
Putting It All Together: The Upgraded Conversational Bot
Let’s see the complete, upgraded version of our conversational bot with both custom and provider-native tools working in harmony.
Here’s the full code for conversing-bot.ts:
import { generateText, stepCountIs, tool } from "ai";
import type { UserModelMessage, AssistantModelMessage } from "ai";
import { google } from "@ai-sdk/google";
import * as prompts from "@clack/prompts";
import process from "node:process";
// Custom tool: Get the current time
const timeTool = tool({
  description: "Get the current time to answer user questions about time.",
  inputSchema: undefined,
  execute: async () => {
    const now = new Date();
    return `The current time is ${now.toLocaleTimeString()}.`;
  },
});
const messages: Array<UserModelMessage | AssistantModelMessage> = [];
async function main() {
  prompts.intro("🤖 Conversational CLI Bot with Tools");
  console.log(
    "\nWelcome! I'm your AI assistant. Type your messages and I'll respond.",
  );
  console.log('Type "exit" or "quit" to end the conversation.\n');
  while (true) {
    const userMessage = await prompts.text({
      message: "You:",
      placeholder: "Type your message here...",
      validate: (value) => {
        if (!value) return "Please enter a message";
      },
    });
    if (prompts.isCancel(userMessage)) {
      prompts.cancel("Conversation ended.");
      process.exit(0);
    }
    const messageText = userMessage;
    if (
      messageText === "exit" ||
      messageText === "quit" ||
      messageText === "bye"
    ) {
      prompts.outro("👋 Goodbye! Thanks for chatting!");
      break;
    }
    messages.push({
      role: "user",
      content: messageText,
    });
    const spinner = prompts.spinner();
    spinner.start("AI is thinking...");
    try {
      const { text } = await generateText({
        model: google("gemini-2.5-flash-lite"),
        messages: messages,
        stopWhen: stepCountIs(5),
        system:
          "You are a helpful and friendly AI assistant who uses the tools to help the user achieve various tasks beyond the tools capability. Keep your responses concise and conversational.",
        tools: {
          getCurrentTime: timeTool,
          url_context: google.tools.urlContext({}),
        },
      });
      spinner.stop("AI response received");
      prompts.note(text, "AI Response");
      messages.push({
        role: "assistant",
        content: text,
      });
    } catch (error) {
      spinner.stop("Error occurred");
      console.error("\n❌ Error generating response:", error);
      console.log("Let's try again...\n");
      messages.pop();
    }
  }
}
main().catch((error) => {
  console.error("Fatal error:", error);
  process.exit(1);
});
Run the complete bot:
node --env-file=.env conversing-bot.ts
Now your AI can seamlessly blend conversation, time queries, and web research.
You can find the full source code on GitHub.
That’s A Wrap!
In just a few minutes, you’ve transformed your chatbot from an isolated knowledge base into an agent capable of reaching into the real world. You’ve learnt the fundamental pattern of tool calling and seen how it works with both custom functions and provider-native tools.
The Vercel AI SDK handles the complex orchestration. Detecting when tools are needed, executing them in the right order, and weaving results back into the conversation—whilst giving you complete control over what capabilities your AI possesses.
This is just the beginning. The patterns you’ve learnt here scale to far more sophisticated use cases:
- Tools with parameters: Build a weather tool that accepts a city name, or a database query tool that takes search criteria
 - Multi-step reasoning: Let your AI chain multiple tool calls together to solve complex problems, like “research this topic, summarise it, and email me the summary”
 - Interactive tools: Create tools that prompt the user for additional information mid-conversation
 - External API integration: Connect your AI to Stripe, SendGrid, Google Calendar, or any API you can imagine
 
The only limit is your creativity. You now have the foundation to build AI applications that can truly interact with the world.
Further Reading and Exploration
Ready to go deeper? Here are the essential resources:
- Advanced Tool Calling Guide: Dive deeper into multi-step calls, tool choice, and advanced patterns
 - Gemini’s 
url_contextTool: The official documentation for Gemini’s URL fetching capability 
The world of AI engineering is evolving rapidly, and tool calling is at the heart of it. The models are becoming more capable, the tools are becoming more sophisticated, and the applications you can build are becoming genuinely transformative.
Go build something incredible. Connect your AI to a database, integrate it with your company’s internal APIs, or create a personal assistant that can actually do things on your behalf. The toolbox is open, and the possibilities are endless.
If you found this guide helpful, please share it with your developer friends and colleagues. If you have questions or want to share what you’ve built, feel free to reach out on Twitter or GitHub. Happy coding! 🚀