Back to Blog
AIFeatured

Building AI Agents with LangChain and Node.js: A Practical Guide

February 16, 202615 min read

Go beyond basic LLM calls. Learn how to build autonomous AI agents with tool use, memory, and multi-step reasoning using LangChain.js and Node.js.

Building AI Agents with LangChain and Node.js: A Practical Guide

Introduction

A chatbot answers questions. An AI agent takes actions. The difference is fundamental: agents can call tools (search the web, query a database, send emails, run code), remember context across sessions, and autonomously chain multiple steps to complete complex goals.

This guide walks through building a production-ready AI agent with LangChain.js, OpenAI, and Node.js — covering tool definition, memory, and multi-agent orchestration.


Core Concepts

The ReAct Pattern

The most common agent architecture is ReAct (Reasoning + Acting):

Thought → Action → Observation → Thought → Action → ...

The LLM reasons about what to do, calls a tool, observes the result, then reasons again until the task is complete.


Setting Up Your First Agent

npm install langchain @langchain/openai @langchain/core
// agents/researchAgent.ts
import { ChatOpenAI } from '@langchain/openai';
import { createReactAgent } from 'langchain/agents';
import { TavilySearchResults } from '@langchain/community/tools/tavily_search';
import { Calculator } from '@langchain/community/tools/calculator';

const model = new ChatOpenAI({
  model: 'gpt-4o',
  temperature: 0,
});

const tools = [
  new TavilySearchResults({ maxResults: 3 }),
  new Calculator(),
];

const agent = await createReactAgent({ llm: model, tools });

const result = await agent.invoke({
  input: 'What is the market cap of Apple divided by the number of countries in the EU?',
});

console.log(result.output);
// Agent will: search Apple market cap → search EU country count → calculate ratio

Defining Custom Tools

The real power is custom tools that connect your agent to your systems:

import { tool } from '@langchain/core/tools';
import { z } from 'zod';
import { db } from '../lib/db';

const getUserOrders = tool(
  async ({ userId, limit }) => {
    const orders = await db.order.findMany({
      where: { userId },
      take: limit,
      orderBy: { createdAt: 'desc' },
      select: { id: true, total: true, status: true, createdAt: true },
    });
    return JSON.stringify(orders);
  },
  {
    name: 'get_user_orders',
    description: 'Retrieve recent orders for a specific user ID. Returns order ID, total, status, and date.',
    schema: z.object({
      userId: z.string().describe('The UUID of the user'),
      limit: z.number().min(1).max(20).default(5).describe('Number of orders to retrieve'),
    }),
  }
);

const cancelOrder = tool(
  async ({ orderId, reason }) => {
    await db.order.update({
      where: { id: orderId },
      data: { status: 'cancelled', cancelReason: reason },
    });
    return `Order ${orderId} successfully cancelled.`;
  },
  {
    name: 'cancel_order',
    description: 'Cancel an order by its ID. Only use after confirming with the user.',
    schema: z.object({
      orderId: z.string().describe('The order ID to cancel'),
      reason: z.string().describe('Reason for cancellation'),
    }),
  }
);

Adding Persistent Memory

Without memory, every conversation starts fresh. Use LangChain's message history:

import { createReactAgent } from 'langchain/agents';
import { RunnableWithMessageHistory } from '@langchain/core/runnables';
import { ChatMessageHistory } from 'langchain/memory';

const agent = await createReactAgent({ llm: model, tools });

// In-memory store (replace with Redis in production)
const sessionStore: Record<string, ChatMessageHistory> = {};

const agentWithHistory = new RunnableWithMessageHistory({
  runnable: agent,
  getMessageHistory: (sessionId) => {
    if (!sessionStore[sessionId]) {
      sessionStore[sessionId] = new ChatMessageHistory();
    }
    return sessionStore[sessionId];
  },
  inputMessagesKey: 'input',
  historyMessagesKey: 'chat_history',
});

// First turn
await agentWithHistory.invoke(
  { input: 'My user ID is usr_123. What are my recent orders?' },
  { configurable: { sessionId: 'session_abc' } }
);

// Second turn — agent remembers user ID and previous context
await agentWithHistory.invoke(
  { input: 'Cancel the most recent one.' },
  { configurable: { sessionId: 'session_abc' } }
);

Production Pattern: Express API with Streaming

// routes/agent.ts
import express from 'express';

const router = express.Router();

router.post('/chat', async (req, res) => {
  const { message, sessionId } = req.body;

  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  try {
    const stream = await agentWithHistory.stream(
      { input: message },
      { configurable: { sessionId } }
    );

    for await (const chunk of stream) {
      if (chunk.output) {
        res.write(`data: ${JSON.stringify({ type: 'output', text: chunk.output })}

`);
      }
      if (chunk.intermediateSteps) {
        for (const step of chunk.intermediateSteps) {
          res.write(`data: ${JSON.stringify({ type: 'tool_call', tool: step.action.tool })}

`);
        }
      }
    }

    res.write('data: [DONE]

');
    res.end();
  } catch (error) {
    res.write(`data: ${JSON.stringify({ type: 'error', message: 'Agent failed' })}

`);
    res.end();
  }
});

Multi-Agent Pattern: Supervisor + Workers

For complex tasks, use a supervisor agent that delegates to specialised workers:

const supervisorPrompt = `You are a supervisor managing these specialised agents:
- research_agent: searches the web and summarises information
- data_agent: queries the database and analyses data
- email_agent: drafts and sends emails

Given the user's request, decide which agent to call and in what order.`;

Conclusion

AI agents unlock a new category of software — applications that reason, plan, and act autonomously. Start with simple tool-use, add memory, then graduate to multi-agent patterns as complexity grows.

Key takeaways:

  • Tools are the bridge between LLMs and your system
  • Memory turns a chatbot into an assistant
  • Stream responses for a responsive UX
  • Always validate and sandbox tool inputs in production

Related: See the post on building RAG systems for how to ground your agents in your own knowledge base.

Tags

AI AgentsLangChainNode.jsOpenAILLMBackend