Building a Smart AI Agent with the OpenAI Node.js SDK
OpenAI functions empower your applications to perform real-world actions based on user prompts, such as web searches, email sending, or ticket bookings—elevating it beyond a basic chatbot.
This guide walks you through constructing an app leveraging OpenAI functions and the newest Node.js SDK. It operates directly in the browser, requiring just a code editor and something like VS Code Live Server for local testing. Or, dive in instantly via this Scrimba code playground.
What you will build
You'll craft a straightforward agent that recommends activities nearby. It utilizes two functions: getLocation() and getCurrentWeather(), allowing it to determine your position and current conditions.
Key point: OpenAI doesn't run code for you—it simply instructs your app on which functions to invoke, leaving execution to your code.
With location and weather in hand, the agent draws on GPT's knowledge to propose fitting activities in your area.
Importing the SDK and authenticating with OpenAI
Begin by importing the OpenAI SDK in your JavaScript file and setting up authentication via an environment variable for your API key.
import OpenAI from "openai";
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
dangerouslyAllowBrowser: true,
});
For browser execution like in Scrimba, include dangerouslyAllowBrowser: true to acknowledge client-side request risks. In production, shift these to a Node server.
Creating our two functions
Now, define the functions. getLocation leverages the IP API to fetch user location details.
async function getLocation() {
const response = await fetch("https://ipapi.co/json/");
const locationData = await response.json();
return locationData;
}
The IP API provides extensive location info, including latitude and longitude, which feed into getCurrentWeather. This one queries the Open Meteo API for weather details:
async function getCurrentWeather(latitude, longitude) {
const url = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&hourly=apparent_temperature`;
const response = await fetch(url);
const weatherData = await response.json();
return weatherData;
}
Describing our functions for OpenAI
To inform OpenAI about these functions, define them in a tools array. Each entry is an object with type: "function" and a function sub-object containing name, description, and parameters.
const tools = [
{
type: "function",
function: {
name: "getCurrentWeather",
description: "Get the current weather in a given location",
parameters: {
type: "object",
properties: {
latitude: {
type: "string",
},
longitude: {
type: "string",
},
},
required: ["longitude", "latitude"],
},
}
},
{
type: "function",
function: {
name: "getLocation",
description: "Get the user's location based on their IP address",
parameters: {
type: "object",
properties: {},
},
}
},
];
Setting up the messages array
Prepare a messages array to track the conversation history between your app and OpenAI.
The initial entry must have role: "system" to set the AI's behavior guidelines.
const messages = [
{
role: "system",
content:
"You are a helpful assistant. Only use the functions you have been provided with.",
},
];
Creating the agent function
The core logic resides in the async agent function, which accepts userInput.
First, append the user input to messages with role: "user".
async function agent(userInput) {
messages.push({
role: "user",
content: userInput,
});
const response = await openai.chat.completions.create({
model: "gpt-4",
messages: messages,
tools: tools,
});
console.log(response);
}
This invokes the Chat Completions endpoint using chat.completions.create(), passing a config object with:
model: The AI model, here "gpt-4".messages: Full conversation history.tools: Available functions from ourtoolsarray.
Running our app with a simple input
Test with an input needing a function:
agent("Where am I located right now?");
The console shows OpenAI's response:
{
id: "chatcmpl-84ojoEJtyGnR6jRHK2Dl4zTtwsa7O",
object: "chat.completion",
created: 1696159040,
model: "gpt-4-0613",
choices: [{
index: 0,
message: {
role: "assistant",
content: null,
tool_calls: [
id: "call_CBwbo9qoXUn1kTR5pPuv6vR1",
type: "function",
function: {
name: "getLocation",
arguments: "{}"
}
]
},
logprobs: null,
finish_reason: "tool_calls" // OpenAI wants us to call a function
}],
usage: {
prompt_tokens: 134,
completion_tokens: 6,
total_tokens: 140
}
system_fingerprint: null
}
finish_reason: "tool_calls" signals a function call. The function name is at response.choices[0].message.tool_calls[0].function.name: "getLocation".
Turning the OpenAI response into a function call
Map the string function name to execution by grouping functions in availableTools:
const availableTools = {
getCurrentWeather,
getLocation,
};
Access via bracket notation: availableTools["getLocation"]().
const { finish_reason, message } = response.choices[0];
if (finish_reason === "tool_calls" && message.tool_calls) {
const functionName = message.tool_calls[0].function.name;
const functionToCall = availableTools[functionName];
const functionArgs = JSON.parse(message.tool_calls[0].function.arguments);
const functionArgsArr = Object.values(functionArgs);
const functionResponse = await functionToCall.apply(null, functionArgsArr);
console.log(functionResponse);
}
Parse arguments from message.tool_calls[0].function.arguments (none needed here).
Running yields location data, e.g., for Oslo, Norway:
{ip: "193.212.60.170", network: "193.212.60.0/23", version: "IPv4", city: "Oslo", region: "Oslo County", region_code: "03", country: "NO", country_name: "Norway", country_code: "NO", country_code_iso3: "NOR", country_capital: "Oslo", country_tld: ".no", continent_code: "EU", in_eu: false, postal: "0026", latitude: 59.955, longitude: 10.859, timezone: "Europe/Oslo", utc_offset: "+0200", country_calling_code: "+47", currency: "NOK", currency_name: "Krone", languages: "no,nb,nn,se,fi", country_area: 324220, country_population: 5314336, asn: "AS2119", org: "Telenor Norge AS"}
Add this result to messages with role: "function":
messages.push({
role: "function",
name: functionName,
content: `The result of the last function was this: ${JSON.stringify(
functionResponse
)}
`,
});
role: "function" indicates function output in content.
Send an updated request to OpenAI. To handle multiple rounds, use a loop (not hardcoded).
Creating the loop
In agent, add a for-loop (max 5 iterations):
for (let i = 0; i < 5; i++) {
const response = await openai.chat.completions.create({
model: "gpt-4",
messages: messages,
tools: tools,
});
const { finish_reason, message } = response.choices[0];
if (finish_reason === "tool_calls" && message.tool_calls) {
const functionName = message.tool_calls[0].function.name;
const functionToCall = availableTools[functionName];
const functionArgs = JSON.parse(message.tool_calls[0].function.arguments);
const functionArgsArr = Object.values(functionArgs);
const functionResponse = await functionToCall.apply(null, functionArgsArr);
messages.push({
role: "function",
name: functionName,
content: `
The result of the last function was this: ${JSON.stringify(
functionResponse
)}
`,
});
} else if (finish_reason === "stop") {
messages.push(message);
return message.content;
}
}
return "The maximum number of iterations has been met without a suitable answer. Please try again with a more specific input.";
On tool_calls, append result and continue. On stop, return answer. Otherwise, after 5 loops, error message.
Running the final app
Test with a full query:
const response = await agent(
"Please suggest some activities based on my location and the current weather."
);
console.log(response);
Console output (formatted):
Based on your current location in Oslo, Norway and the weather (15°C and snowy),
here are some activity suggestions:
1. A visit to the Oslo Winter Park for skiing or snowboarding.
2. Enjoy a cosy day at a local café or restaurant.
3. Visit one of Oslo's many museums. The Fram Museum or Viking Ship Museum offer interesting insights into Norway’s seafaring history.
4. Take a stroll in the snowy streets and enjoy the beautiful winter landscape.
5. Enjoy a nice book by the fireplace in a local library.
6. Take a fjord sightseeing cruise to enjoy the snowy landscapes.
Always remember to bundle up and stay warm. Enjoy your day!
Logging messages reveals sequence: First getLocation, then getCurrentWeather with extracted lat/long.
{"role":"assistant","content":null,"tool_calls":[{"id":"call_Cn1KH8mtHQ2AMbyNwNJTweEP","type":"function","function":{"name":"getLocation","arguments":"{}"}}]}
{"role":"assistant","content":null,"tool_calls":[{"id":"call_uc1oozJfGTvYEfIzzcsfXfOl","type":"function","function":{"name":"getCurrentWeather","arguments":"{\n\"latitude\": \"10.859\",\n\"longitude\": \"59.955\"\n}"}}]}
You've built an AI agent with OpenAI functions and Node.js SDK! Challenge: Add a function for local events.
Happy coding!
Complete code
import OpenAI from "openai";
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
dangerouslyAllowBrowser: true,
});
async function getLocation() {
const response = await fetch("https://ipapi.co/json/");
const locationData = await response.json();
return locationData;
}
async function getCurrentWeather(latitude, longitude) {
const url = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&hourly=apparent_temperature`;
const response = await fetch(url);
const weatherData = await response.json();
return weatherData;
}
const tools = [
{
type: "function",
function: {
name: "getCurrentWeather",
description: "Get the current weather in a given location",
parameters: {
type: "object",
properties: {
latitude: {
type: "string",
},
longitude: {
type: "string",
},
},
required: ["longitude", "latitude"],
},
}
},
{
type: "function",
function: {
name: "getLocation",
description: "Get the user's location based on their IP address",
parameters: {
type: "object",
properties: {},
},
}
},
];
const availableTools = {
getCurrentWeather,
getLocation,
};
const messages = [
{
role: "system",
content:
"You are a helpful assistant. Only use the functions you have been provided with.",
},
];
async function agent(userInput) {
messages.push({
role: "user",
content: userInput,
});
for (let i = 0; i < 5; i++) {
const response = await openai.chat.completions.create({
model: "gpt-4",
messages: messages,
tools: tools,
});
const { finish_reason, message } = response.choices[0];
if (finish_reason === "tool_calls" && message.tool_calls) {
const functionName = message.tool_calls[0].function.name;
const functionToCall = availableTools[functionName];
const functionArgs = JSON.parse(message.tool_calls[0].function.arguments);
const functionArgsArr = Object.values(functionArgs);
const functionResponse = await functionToCall.apply(null, functionArgsArr);
messages.push({
role: "function",
name: functionName,
content: `
The result of the last function was this: ${JSON.stringify(
functionResponse
)}
`,
});
} else if (finish_reason === "stop") {
messages.push(message);
return message.content;
}
}
return "The maximum number of iterations has been met without a suitable answer. Please try again with a more specific input.";
}
Stay ahead of the AI curve
The most important updates, news, and content — delivered in one weekly newsletter.