Learn how to connect your AI applications to the real world using tool calling. This step-by-step guide for TypeScript developers shows you how to use custom functions and provider-native tools like Gemini's url_context with the Vercel AI SDK.
In our last guide, Build Your First AI App in 30 Minutes with the TypeScript AI SDK and Gemini, 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:
In this guide, we’ll upgrade the conversing-bot.ts from the previous article by adding two distinct types of tools:
getCurrentTime function that gives the AI access to your local system timeurl_context tool, which lets the AI fetch and understand content from any URL without you writing a single line of fetching logicBy 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!
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.
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.
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. For getCurrentTime, we don’t need any input (the current time is the current time, after all), so we set this to undefined. 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 new Date object 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. ;)
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 these key changes:
system prompt to inform the model it has tools available. While the AI SDK automatically tells the model about tool descriptions, updating the system prompt helps reinforce its role, if needed.tools property, 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.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.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:
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, recognizes 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.
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.
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.
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 behavior here if needed.
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 summarize this article?” The AI needs to call getCurrentTime, then use url_context, then formulate a response.
We enable this behavior 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.
Run your chatbot again and try these prompts:
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.
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.
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 learned 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—while giving you control over what capabilities your AI possesses.
This is just the beginning. The patterns you’ve learned here scale to far more sophisticated use cases:
The only limit is your creativity. You now have the foundation to build AI applications that can truly interact with the world.
Ready to go deeper? Here are the essential resources:
url_context Tool: The official documentation for Gemini’s URL fetching capabilityThe 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! 🚀
Peter is a software consultant, technical trainer and OSS contributor/maintainer with excellent interpersonal and motivational abilities to develop collaborative relationships among high-functioning teams. He focuses on cloud-native architectures, serverless, continuous deployment/delivery, and developer experience. You can follow him on Twitter.