Enhancing AI Capabilities with Tool Calling
by Nitturu Baba, System Analyst
Introduction
Imagine an AI-powered chatbot that not only answers questions but also retrieves real-time data from external sources like GitHub or Asana. Traditional AI models struggle with this—but tool calling bridges the gap by enabling AI to interact with APIs dynamically. In this guide, we’ll explore how to implement tool calling in NestJS using AI-SDK.
AI models are powerful at generating text-based responses but have a fundamental limitation—they cannot retrieve real-time data or execute specific actions unless explicitly programmed to do so. For example, if you ask an AI model:


In the first scenario when the user asks about the first T20 world cup winner, AI can answer this question because the AI model has been trained on past and publicly available data. However, when asked to fetch open PRs from a repository, the AI cannot provide an accurate answer because it does not have direct access to GitHub’s API. This is where tool calling comes into play—allowing AI models to dynamically invoke external tools to fetch real-time data and execute functions.
Sample code in NestJS using ai-sdk
chat.controller.ts
import { Controller, Post, Body } from '@nestjs/common'
import { ChatService } from './chat.service'
@Controller('chat')
export class ChatController {
constructor(private readonly chatService: ChatService) {}
@Post('completion')
async chat(@Body() payload: Object) {
const response = await this.chatService.handleRequest(payload)
return response
}
}
chat.service.ts
import { Injectable } from '@nestjs/common'
import { openai } from '@ai-sdk/openai'
import { streamText, CoreMessage } from 'ai'
@Injectable()
export class ChatService {
async handleRequest(payload: Object) {
const messages = [
{
role: 'system',
content:
'You are a helpful assistant that can answer questions and help with tasks.', // this is the prompt to the ai model
},
{
role: 'user',
content: payload.question, // this is the question from the user
},
]
const { textStream } = streamText({
model: openai('gpt-4o'),
messages: messages as Array<CoreMessage>,
maxSteps: 10,
})
// If you want to handle streaming responses
let fullResponse = ''
for await (const textPart of textStream) {
fullResponse += textPart
// You can emit this part through WebSocket if needed
// this.emitStreamResponse(textPart);
console.log(textPart)
}
return fullResponse
}
}
What is a Tool?
A tool is essentially a function that performs a specific task, accompanied by a description and parameters. This description helps the AI determine the appropriate tool to use when responding to a user query.
Example of a Tool
Let's define a tool that fetches open pull requests (PRs) from a GitHub repository:
getOpenPullRequests: tool({
description: 'Fetches all the open PRs from the repository.',
parameters: z.object({
owner: z.string().describe('The owner of the repository'),
repo: z.string().describe('The name of the repository'),
}),
execute: async ({ owner, repo }) => {
return getOpenPRs(owner, repo) // this function will fetch open PRs from the repository.
},
})
This tool retrieves open PRs when called by the AI model.
Fetching Open PRs from GitHub
To execute the getOpenPullRequests
tool, we need a function that interacts with GitHub’s API:
async getOpenPRs(owner: string, repoName: string): Promise<string> {
try {
const baseUrl = `https://api.github.com/repos/${owner}/${repoName}/pulls`;
const response = await axios.get(baseUrl, { headers: {
Authorization: `Bearer ${process.env.GITHUB_ACCESS_TOKEN}`,
Accept: 'application/vnd.github.v3+json',
'X-GitHub-Api-Version': '2022-11-28',
},
});
if (response.status === 200) {
const prs = response.data;
const userPrs = prs.filter(pr => pr.user.type !== 'Bot');
return `Open pull requests: ${JSON.stringify(userPrs)}`;
} else {
const errorMessage = response.data?.errors || response.data;
return `Error retrieving open pull requests: ${errorMessage}`;
}
} catch (error) {
throw new HttpException(`Error getting open pull requests: ${error.message}`, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
This function uses Axios to fetch open PRs from GitHub and returns the results as a JSON string.
Implementing Tool Calling in AI
We now integrate tool calling into an AI using NestJS and @ai-sdk/openai
:
chat.service.ts
import { Injectable, HttpException, HttpStatus } from '@nestjs/common'
import { openai } from '@ai-sdk/openai'
import { streamText, tool, CoreMessage } from 'ai'
import axios from 'axios'
import { z } from 'zod'
@Injectable()
export class ChatService {
private githubHeaders: Record<string, string>
constructor() {
this.githubHeaders = {
Authorization: `Bearer ${process.env.GITHUB_ACCESS_TOKEN}`,
Accept: 'application/vnd.github.v3+json',
'X-GitHub-Api-Version': '2022-11-28',
}
}
async getOpenPRs(owner: string, repoName: string): Promise<string> {
try {
const baseUrl = `https://api.github.com/repos/${owner}/${repoName}/pulls`
const response = await axios.get(baseUrl, {
headers: this.githubHeaders,
})
if (response.status === 200) {
const prs = response.data
const userPrs = prs.filter((pr) => pr.user.type !== 'Bot')
return `Open pull requests: ${JSON.stringify(userPrs)}`
} else {
const errorMessage = response.data?.errors || response.data
return `Error retrieving open pull requests: ${errorMessage}`
}
} catch (error) {
throw new HttpException(
`Error getting open pull requests: ${error.message}`,
HttpStatus.INTERNAL_SERVER_ERROR,
)
}
}
async handleRequest(payload: Object) {
const messages = [
{
role: 'system',
content:
'You are a helpful assistant that can answer questions and help with tasks.',
},
{
role: 'user',
content: payload['question'],
},
]
const tools = {
getOpenPullRequests: tool({
description: 'Fetches all the open PRs from the repository.',
parameters: z.object({
owner: z.string().describe('The owner of the repository'),
repo: z.string().describe('The name of the repository'),
}),
execute: async ({ owner, repo }) => {
return this.getOpenPRs(owner, repo)
},
}),
}
const { textStream } = streamText({
model: openai('gpt-4o'),
messages: messages as Array<CoreMessage>,
tools: tools,
maxSteps: 10,
})
// If you want to handle streaming responses
let fullResponse = ''
for await (const textPart of textStream) {
fullResponse += textPart
// You can emit this part through WebSocket if needed
// this.emitStreamResponse(textPart);
}
return fullResponse
}
}
Here, the AI can dynamically call the getOpenPullRequests
tool when a user requests GitHub PR information.
Now, AI can answer to the question fetch open PRs from a repository:

Advantages of Tool Calling
✅ Real-time Data Access AI can fetch live data instead of relying solely on pre-trained knowledge.
✅ Improved Accuracy Using tools eliminates AI hallucinations, ensuring responses are accurate and reliable.
✅ Automating Workflows By combining multiple tools, AI can automate complex workflows.
✅ Extensibility New tools can be easily added without retraining the AI model.
Extending the Workflow with Multiple Tools
In a real-world scenario, you may want AI to perform multiple actions within a workflow. Here’s an example of integrating additional tools:
const tools = {
getOpenPullRequests: tool({
description: 'Fetches all open PRs from a GitHub repository.',
parameters: z.object({ owner: z.string(), repo: z.string() }),
execute: async ({ owner, repo }) => getOpenPRs(owner, repo),
}),
createAsanaTask: tool({
description: 'Creates a new task in Asana.',
parameters: z.object({ projectId: z.string(), taskName: z.string() }),
execute: async ({ projectId, taskName }) =>
createTaskInAsana(projectId, taskName),
}),
}
With this setup, the AI can fetch open PRs and create an Asana task for review—all within a single conversation!
Conclusion
Tool calling transforms AI from a simple text generator into a powerful automation engine. By integrating external APIs and tools, AI can fetch real-time data, automate tasks, and enhance user interactions. Whether you're managing GitHub repositories, tracking Asana tasks, or executing API calls, tool calling enables AI to become an intelligent, action-driven assistant.