homeresume
 
   

Building AI agents with LangChain

Published June 17, 2026Last updated June 17, 20265 min read

LangChain agents are built on LangGraph: the model calls tools in a loop until it returns a final answer. The high-level entry point is createAgent - pass a model, tools defined with tool(), and an optional systemPrompt.

This post builds the same support triage agent as the Vercel AI SDK agents and OpenAI Agents SDK posts so you can compare SDKs on one scenario. It follows the LangChain overview for Node.js and fits as post #4 in the LangChain series (after loaders/chunking and the RAG with pgvector pipeline).

Prerequisites

  • OpenAI account
  • Generated API key
  • Enabled billing
  • Node.js version 26
  • langchain, @langchain/openai, @langchain/core, and zod installed:
npm i langchain @langchain/openai @langchain/core zod
  • OPENAI_API_KEY set in the environment

Mental model - turns and the agent loop

A turn is one model generation. In that turn the model either:

  • returns final text (the run ends), or
  • returns tool calls (LangChain executes them and starts another turn with the results)

Typical flow for the support triage agent: user question → model calls lookup tools (get_customer, get_invoice, search_knowledge_base) → model creates a ticket or escalates → final answer.

A single turn can include multiple parallel tool calls. Set recursionLimit on invoke or stream to cap how many graph steps run (each model generation and tool batch counts toward the limit).

Defining tools

Use tool() from langchain with a Zod schema, plus name and description so the model knows when to call each tool:

import { tool } from 'langchain';
import { z } from 'zod';
const getInvoice = tool(
async ({ invoiceId }) => {
const invoice = invoices.find((item) => item.id === invoiceId);
if (!invoice) {
return { found: false, invoiceId, error: 'Invoice not found' };
}
return { found: true, invoice };
},
{
name: 'get_invoice',
description: 'Look up an invoice by ID, including payment IDs and status',
schema: z.object({
invoiceId: z.string().describe('Invoice ID, e.g. inv_8891'),
}),
},
);

LangChain uses schema (not Vercel's inputSchema or OpenAI Agents' parameters). The handler receives validated input as the first argument.

createAgent

Wire the model, tools, and triage instructions:

import { createAgent } from 'langchain';
const agent = createAgent({
model: 'gpt-5.5',
tools: [getInvoice],
systemPrompt: `You are a billing support triage agent.
Look up records before recommending refunds or creating tickets.`,
});

model can be a provider string ('gpt-5.5', 'openai:gpt-5.5') or a chat model instance from @langchain/openai.

Invoke

Pass a messages array and read the final answer from result.messages:

const result = await agent.invoke({
messages: [
{
role: 'user',
content: 'What is the status of invoice inv_8891? Reply in one sentence.',
},
],
});
const lastAi = [...result.messages]
.reverse()
.find((message) => message.type === 'ai');
console.log(lastAi?.content);

The last AI message is the agent's final reply after any tool calls complete.

Support triage scenario

Example prompt:

Customer cus_1042 says they were charged twice for invoice inv_8891. What should we do?

A realistic chain:

  1. get_customer - plan tier, open ticket count
  2. get_invoice - amount, status, payment IDs
  3. search_knowledge_base - duplicate-charge and refund policy
  4. create_support_ticket or escalate_to_human - write action or escalation

The demo uses in-memory fixtures (customers, invoices, knowledge-base articles) so scripts run without a database.

Multi-tool agent

Register all triage tools on one agent:

import { createAgent } from 'langchain';
import {
getCustomer,
getInvoice,
searchKnowledgeBase,
createSupportTicket,
escalateToHuman,
TRIAGE_INSTRUCTIONS,
} from './tools/index.js';
const agent = createAgent({
model: 'gpt-5.5',
tools: [
getCustomer,
getInvoice,
searchKnowledgeBase,
createSupportTicket,
escalateToHuman,
],
systemPrompt: TRIAGE_INSTRUCTIONS,
});
const result = await agent.invoke({
messages: [
{
role: 'user',
content:
'Customer cus_1042 says they were charged twice for invoice inv_8891. What should we do?',
},
],
recursionLimit: 15,
});
const answer = [...result.messages]
.reverse()
.find((message) => message.type === 'ai');
console.log(answer?.content);

Inspect result.messages for the full trace: human input, AI tool-call messages, tool results, and the final AI reply.

Streaming

agent.stream() yields state updates as the graph runs. Use streamMode: 'values' to receive the full message list after each step:

const stream = await agent.stream(
{
messages: [
{
role: 'user',
content:
'Customer cus_1042 says they were charged twice for invoice inv_8891. What should we do?',
},
],
},
{ streamMode: 'values', recursionLimit: 15 },
);
let finalMessages = [];
for await (const state of stream) {
if (state.messages) {
finalMessages = state.messages;
}
}
const answer = [...finalMessages]
.reverse()
.find((message) => message.type === 'ai');
console.log(answer?.content);

For token-level streaming, use streamMode: 'messages' or streamEvents (see LangGraph streaming).

When to pick LangChain

LangChain createAgentVercel AI SDKOpenAI Agents SDK
Best forRAG + LCEL + agents in one stackTypeScript apps already on AI SDKOpenAI-first agent primitives
Tool definitiontool() + Zod schematool() + inputSchematool() + Zod parameters
Run APIagent.invoke / agent.streamgenerateText + stopWhenrun() + maxTurns
Handoffs / guardrailsMiddleware (advanced)LimitedBuilt-in
MemoryLangGraph checkpointersBring your ownSession helpers

Pick LangChain when document loaders, retrievers, and agents should share one ecosystem. Pick Vercel AI SDK or OpenAI Agents SDK when you want a focused agent layer without the broader LangChain surface.

Demo

See the langchain-agents-nodejs-demo folder for runnable scripts: single-tool lookup, full triage, and streaming.