How to Create an AI Agent Step-by-Step in NodeJS
As we transition from basic chatbots to autonomous systems, knowing how to build an actively "doing" system is becoming an essential skill. While you could utilize complex frameworks like LangChain or AutoGen, it's incredibly valuable to understand how to build a basic agent from scratch.
In this tutorial, we are going to build an AI agent step-by-step using NodeJS and the OpenAI API.
Our goal is to build a simple "Weather & News" agent. Rather than just making up answers, our agent will securely access external tools (functions we define) to fetch real-world data before constructing a response.
Step 1: Setting Up the Environment
First, let's set up a new NodeJS project and install the necessary dependencies. Open your terminal and run the following commands:
1# Create a new directory and enter it 2mkdir my-first-ai-agent 3cd my-first-ai-agent 4 5# Initialize a new Node project 6npm init -y 7 8# Install the official OpenAI SDK and dotenv to manage your API key 9npm install openai dotenv
Next, in the root of your project directory, create two files: index.js and .env.
In your .env file, add your OpenAI API Key:
1OPENAI_API_KEY=sk-your-super-secret-api-key
Don’t have an API key? You can grab one by creating an account on the OpenAI Developer Platform.
Step 2: The Logic Behind an AI Agent
Before we write code, we need to understand the fundamental architecture of what we're building. A standard agent operates on a ReAct (Reasoning + Acting) loop:
- User Input: The human provides a goal or asks a question.
- Thought/Planning: The LLM decides what information it needs to achieve the goal.
- Action: The LLM requests to use a specific "Tool" (a function).
- Observation: Your code executes the function and feeds the data back into the LLM.
- Repeat or Output: The LLM looks at the data. If it has all the info it needs, it formulates a final response. If not, it loops back to step 2.
Let's begin writing index.js piece by piece.
Step 3: Initializing the OpenAI Client
Open index.js and set up your imports and the OpenAI client. We will also add a simple prompt that governs the agent's behavior.
1import { OpenAI } from "openai"; 2import dotenv from "dotenv"; 3 4// Load environment variables 5dotenv.config(); 6 7// Initialize the OpenAI client 8const openai = new OpenAI({ 9 apiKey: process.env.OPENAI_API_KEY, 10}); 11 12// The System Prompt gives the agent its identity and rules 13const SYSTEM_PROMPT = ` 14You are a helpful, autonomous assistant. 15You can use external tools to answer the user's questions. 16If you don't know the answer, try to use a tool to find out. 17Do not guess data like the weather or current news, always use tools. 18`;
(Note: To use import, ensure you add "type": "module" in your package.json, or use require() statements instead).
Step 4: Defining the Tools (Functions)
An agent is useless without tools to interact with the outside world. To allow an LLM to call a function, we must provide it with a JSON Schema describing the tool's name, description, and required arguments.
Let's define two mock tools: one for the weather, and one for stock prices.
1// 1. The definitions we send to OpenAI so it knows what tools exist 2const availableTools = [ 3 { 4 type: "function", 5 function: { 6 name: "getWeather", 7 description: "Gets the current weather for a specific city.", 8 parameters: { 9 type: "object", 10 properties: { 11 city: { type: "string", description: "The city to check the weather in." } 12 }, 13 required: ["city"] 14 } 15 } 16 }, 17 { 18 type: "function", 19 function: { 20 name: "getStockPrice", 21 description: "Gets the current stock price for a company ticker symbol.", 22 parameters: { 23 type: "object", 24 properties: { 25 ticker: { type: "string", description: "The stock ticker symbol, e.g., AAPL or MSFT." } 26 }, 27 required: ["ticker"] 28 } 29 } 30 } 31]; 32 33// 2. The actual Javascript functions that execute the work 34async function getWeather({ city }) { 35 console.log(`[Tool] Fetching weather for ${city}...`); 36 // In a real app, you would use fetch() to call a weather API here. 37 return { temperature: 24, conditions: "Partly Cloudy", unit: "Celsius" }; 38} 39 40async function getStockPrice({ ticker }) { 41 console.log(`[Tool] Fetching stock price for ${ticker}...`); 42 // In a real app, you would use fetch() to call a financial API here. 43 // Mocking realistic responses: 44 const mockPrices = { "AAPL": 175.50, "MSFT": 410.20, "GOOGL": 140.10 }; 45 const price = mockPrices[ticker.toUpperCase()] || 100.00; 46 return { ticker: ticker.toUpperCase(), current_price: price, currency: "USD" }; 47}
Step 5: Creating the Agent Loop (The Heart of the Agent)
Now for the complex part. We need a function that handles the conversational history and loops through the Thought -> Action -> Observation cycle.
1async function runAgent(userPrompt) { 2 console.log(`\nUser: "${userPrompt}"\n`); 3 4 // We start the conversation with the System Rules and the User's goal 5 const messages = [ 6 { role: "system", content: SYSTEM_PROMPT }, 7 { role: "user", content: userPrompt } 8 ]; 9 10 // We will loop until the agent decides it has everything it needs 11 let isTaskComplete = false; 12 13 while (!isTaskComplete) { 14 // Tell OpenAI to "Think" based on current messages and available tools 15 const response = await openai.chat.completions.create({ 16 model: "gpt-4o", // You can use gpt-4-turbo or gpt-3.5-turbo as well 17 messages: messages, 18 tools: availableTools, 19 tool_choice: "auto", // The model decides if it needs a tool or can just answer 20 }); 21 22 const agentMessage = response.choices[0].message; 23 24 // Append the agent's thought process to the conversation history 25 messages.push(agentMessage); 26 27 // ACTION Check: Did the agent request to use a tool? 28 if (agentMessage.tool_calls && agentMessage.tool_calls.length > 0) { 29 30 // The agent might want to use multiple tools at once, so we loop through its requests 31 for (const toolCall of agentMessage.tool_calls) { 32 const functionName = toolCall.function.name; 33 const functionArgs = JSON.parse(toolCall.function.arguments); 34 35 let toolResult; 36 37 // Route the requested function to our actual Javascript code 38 if (functionName === "getWeather") { 39 toolResult = await getWeather(functionArgs); 40 } else if (functionName === "getStockPrice") { 41 toolResult = await getStockPrice(functionArgs); 42 } else { 43 toolResult = { error: "Unknown tool called." }; 44 } 45 46 // OBSERVATION: Feed the result back into the message array 47 messages.push({ 48 role: "tool", 49 tool_call_id: toolCall.id, // You MUST provide the ID so OpenAI knows which tool this links to 50 content: JSON.stringify(toolResult) // The result must be passed as a string 51 }); 52 } 53 54 // Important: We do NOT set isTaskComplete = true here. 55 // We want the loop to run again so the LLM can read the tool observation! 56 57 } else { 58 // If there are no tool_calls, the agent is providing its final answer to the user. 59 console.log(`Agent:\n${agentMessage.content}\n`); 60 isTaskComplete = true; // Break the loop 61 } 62 } 63}
Step 6: Testing the Agent
Finally, let's call our agent with a complex prompt that forces it to make multiple tool calls. Add this to the very bottom of your index.js file:
1// A prompt that requires two separate tool calls 2runAgent("I'm heading to London tomorrow. Can you tell me what the weather will be like, and also check the current stock price for Apple?");
If you run this in your terminal using node index.js, you should see output similar to this:
1User: "I'm heading to London tomorrow. Can you tell me what the weather will be like, and also check the current stock price for Apple?" 2 3[Tool] Fetching weather for London... 4[Tool] Fetching stock price for Apple... 5 6Agent: 7The weather in London tomorrow will be partly cloudy with a temperature of around 24°C. 8 9As for Apple's stock, it is currently priced at $175.50 USD. 10 11Let me know if you need anything else!
Congratulations! You've just built an autonomous AI agent.
When you gave it a prompt, the agent independently realized that to answer correctly, it needed to fetch both London's weather and Apple's stock. It called both of your functions, waited for the data, analyzed the results, and phrased a perfect final response.
Where to go from here?
This simple loop is the foundation of every major agent framework, including the wildly popular Devin or AutoGen agents. To make this agent more powerful, you can:
- Give it tools that modify databases (like createUser or updateOrderStatus).
- Give it access to the puppeteer library to browse real websites and scrape data.
- Attach an older conversation history so it "remembers" what you talked about yesterday.
Building AI agents isn't just a trend; it's a paradigm shift in how we approach automation. Start small, give your agent custom tools relevant to your business, and watch as your productivity skyrockets.




