# Building AI agents with LangChain

> Source: <https://dev.to/zsevic/building-ai-agents-with-langchain-5e69>
> Published: 2026-06-16 20:31:35+00:00

[LangChain](https://docs.langchain.com/oss/javascript/langchain/overview) 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](https://docs.langchain.com/oss/javascript/langchain/agents) - 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](https://sevic.dev/notes/ai-agents-vercel-ai-sdk/) and [OpenAI Agents SDK](https://sevic.dev/notes/ai-agents-openai-sdk/) posts so you can compare SDKs on one scenario. It follows the [LangChain overview for Node.js](https://sevic.dev/notes/langchain-overview-nodejs/) and fits as post **#4** in the LangChain series (after loaders/chunking and the [RAG with pgvector](https://sevic.dev/notes/rag-openai-embeddings-pgvector-langchain/) pipeline).

`langchain`

, `@langchain/openai`

, `@langchain/core`

, and `zod`

installed:

```
npm i langchain @langchain/openai @langchain/core zod
```

`OPENAI_API_KEY`

set in the environmentA **turn** is one model generation. In that turn the model either:

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).

Use `tool()`

from `langchain`

with a Zod `schema`

, plus `name`

and `description`

so the model knows when to call each tool:

``` js
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.

Wire the model, tools, and triage instructions:

``` js
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`

.

Pass a `messages`

array and read the final answer from `result.messages`

:

``` js
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.

**Example prompt:**

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

A realistic chain:

`get_customer`

- plan tier, open ticket count`get_invoice`

- amount, status, payment IDs`search_knowledge_base`

- duplicate-charge and refund policy`create_support_ticket`

or `escalate_to_human`

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

Register all triage tools on one agent:

``` js
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.

`agent.stream()`

yields state updates as the graph runs. Use `streamMode: 'values'`

to receive the full message list after each step:

``` js
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](https://docs.langchain.com/oss/javascript/langgraph/streaming)).

LangChain `createAgent`
|
Vercel AI SDK | OpenAI Agents SDK | |
|---|---|---|---|
Best for |
RAG + LCEL + agents in one stack | TypeScript apps already on AI SDK | OpenAI-first agent primitives |
Tool definition |
`tool()` + Zod `schema`
|
`tool()` + `inputSchema`
|
`tool()` + Zod `parameters`
|
Run API |
`agent.invoke` / `agent.stream`
|
`generateText` + `stopWhen`
|
`run()` + `maxTurns`
|
Handoffs / guardrails |
Middleware (advanced) | Limited | Built-in |
Memory |
LangGraph checkpointers | Bring your own | Session 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.

See the [langchain-agents-nodejs-demo](https://github.com/delimitertech/demos/tree/main/ai/langchain-agents-nodejs-demo) folder for runnable scripts: single-tool lookup, full triage, and streaming.
