How to Build an AI Agent with Function Calling in Node.js Using Google Gemini
Last year, a client asked me to add a conversational interface to their internal reporting tool. Staff would type a question, and the system would pull a live answer from the database. I had the first version running in a day—single questions worked. But a week in, a tester typed: "What is the weather in Berlin, and how much would 500 EUR convert to in USD right now?" The model called the weather function, returned that answer, and ignored the second half of the question entirely.
That is the gap between a chatbot and an agent. A chatbot works from training data—that's its limit. An agent doesn't have that limit. It calls a tool, reads what came back, and decides whether to keep going. Most questions resolve in one or two tool calls. Multi-step ones take a few more. Remove that loop, and they all break.
This tutorial shows you how to build that loop with Google Gemini's function calling API and Node.js. You'll build an agent that can call real external tools: Open-Meteo for live weather, frankfurter.app for live currency rates, and a math evaluator for calculations. All three are completely free. The only API key you need is Gemini, which is also free on Google AI Studio at 1,500 requests per day as of 2026.
Everything is on GitHub: github.com/ziaongit/nodejs-gemini-agent.
Table of Contents
- How Function Calling Works
- What We're Building
- Prerequisites
- Project Setup
- Defining the Tools
- Implementing the Tool Functions
- Building the Agentic Loop
How Function Calling Works
Function calling allows a language model to request the execution of external functions. Rather than the model generating a final answer on its own, it outputs a structured request that your application fulfills. The model then receives the function's response and uses it to formulate a complete answer. This pattern is central to building AI agents that can interact with real-world data and services.
What We're Building
We will create a Node.js application that uses Google Gemini's function calling to handle multi-step queries. The agent will be capable of calling three external tools:
- Weather Tool: Uses the Open-Meteo API to fetch current weather data for any city.
- Currency Converter Tool: Uses frankfurter.app to convert amounts between currencies using live exchange rates.
- Math Evaluator Tool: Evaluates mathematical expressions (e.g., "500 + 300 * 2").
The agent will parse user queries, determine which tools to call in sequence (if multiple steps are needed), and compile a coherent response.
Prerequisites
Before starting, ensure you have:
- Node.js (version 18 or later) installed.
- A Google AI Studio account to obtain a Gemini API key (get your key here—free tier includes 1,500 requests per day).
- Basic familiarity with Node.js and asynchronous programming.
Project Setup
Initialize your project:
mkdir gemini-agent && cd gemini-agent npm init -yInstall dependencies:
npm install @google-ai/generativelanguage dotenv axiosWe use
@google-ai/generativelanguagefor the Gemini API,dotenvto manage environment variables, andaxiosfor HTTP requests to external APIs.Create a
.envfile:GEMINI_API_KEY=your_actual_key_hereReplace
your_actual_key_herewith the key from Google AI Studio.Create the main file:
touch index.js
Defining the Tools
In index.js, start by importing the required packages and setting up the Gemini model. Then define your tools as part of the Gemini API configuration.
require('dotenv').config();
const { GoogleGenerativeAI } = require('@google-ai/generativelanguage');
const axios = require('axios');
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
const model = genAI.getGenerativeModel({ model: 'gemini-1.5-pro' });
// Define tools in the API-compatible format
const tools = [
{
functionDeclarations: [
{
name: 'get_current_weather',
description: 'Get the current weather in a given city',
parameters: {
type: 'OBJECT',
properties: {
city: { type: 'STRING', description: 'The city name' },
},
required: ['city'],
},
},
{
name: 'convert_currency',
description: 'Convert an amount from one currency to another using live rates',
parameters: {
type: 'OBJECT',
properties: {
amount: { type: 'NUMBER', description: 'Amount to convert' },
from: { type: 'STRING', description: 'Source currency code (e.g., EUR)' },
to: { type: 'STRING', description: 'Target currency code (e.g., USD)' },
},
required: ['amount', 'from', 'to'],
},
},
{
name: 'evaluate_math',
description: 'Evaluate a mathematical expression',
parameters: {
type: 'OBJECT',
properties: {
expression: { type: 'STRING', description: 'Mathematical expression to evaluate' },
},
required: ['expression'],
},
},
],
},
];
Implementing the Tool Functions
Now implement the actual logic for each tool. These functions will be called by your agent when Gemini requests them.
async function getCurrentWeather(city) {
try {
const response = await axios.get(
`https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41¤t_weather=true`
);
// In a full implementation, you would geocode the city.
// For this example, we use Berlin coordinates as a placeholder.
return `Current weather in ${city}: ${response.data.current_weather.temperature}°C, ${response.data.current_weather.weathercode}`;
} catch (error) {
return `Error fetching weather: ${error.message}`;
}
}
async function convertCurrency(amount, from, to) {
try {
const response = await axios.get(
`https://api.frankfurter.app/latest?amount=${amount}&from=${from}&to=${to}`
);
const rate = response.data.rates[to];
return `${amount} ${from} = ${rate} ${to}`;
} catch (error) {
return `Error converting currency: ${error.message}`;
}
}
function evaluateMath(expression) {
try {
// Securely evaluate—consider using a library like mathjs for production
const result = Function(''use strict'; return (' + expression + ')')();
return `Result: ${result}`;
} catch (error) {
return `Error evaluating expression: ${error.message}`;
}
}
Building the Agentic Loop
The core of the agent is a loop that sends the user's query to Gemini, processes any function call requests, executes them, and continues until the model produces a final answer without function calls.
async function agent(query) {
const chat = model.startChat({ tools });
let result = await chat.sendMessage(query);
let response = result.response;
while (response.functionCalls) {
const functionCalls = response.functionCalls;
const functionResponses = await Promise.all(
functionCalls.map(async (fc) => {
let result;
switch (fc.name) {
case 'get_current_weather':
result = await getCurrentWeather(fc.args.city);
break;
case 'convert_currency':
result = await convertCurrency(fc.args.amount, fc.args.from, fc.args.to);
break;
case 'evaluate_math':
result = evaluateMath(fc.args.expression);
break;
default:
result = 'Unknown function';
}
return {
name: fc.name,
response: { result },
};
})
);
// Send function responses back to the model
result = await chat.sendMessage(functionResponses);
response = result.response;
}
return response.text;
}
// Example usage
agent("What's the weather in Berlin and convert 500 EUR to USD?")
.then(response => console.log(response))
.catch(error => console.error(error));
This loop ensures the agent handles multi-step queries, calling tools sequentially as needed until it produces a final, coherent response to the user.
With this foundation, you can expand the agent by adding more tools, improving error handling, or integrating with a web server for a full conversational interface.
via FreeCodeCamp
