{"slug": "building-ai-agents-with-langchain", "title": "Building AI agents with LangChain", "summary": "LangChain agents built on LangGraph use a tool-calling loop until a final answer is returned. The high-level createAgent function accepts a model, tools defined with tool(), and an optional system prompt. A support triage agent demonstrates parallel tool calls and recursion limits.", "body_md": "[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\n\n`tool()`\n\n, and an optional `systemPrompt`\n\n.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).\n\n`langchain`\n\n, `@langchain/openai`\n\n, `@langchain/core`\n\n, and `zod`\n\ninstalled:\n\n```\nnpm i langchain @langchain/openai @langchain/core zod\n```\n\n`OPENAI_API_KEY`\n\nset in the environmentA **turn** is one model generation. In that turn the model either:\n\nTypical flow for the support triage agent: user question → model calls lookup tools (`get_customer`\n\n, `get_invoice`\n\n, `search_knowledge_base`\n\n) → model creates a ticket or escalates → final answer.\n\nA single turn can include **multiple parallel tool calls**. Set `recursionLimit`\n\non `invoke`\n\nor `stream`\n\nto cap how many graph steps run (each model generation and tool batch counts toward the limit).\n\nUse `tool()`\n\nfrom `langchain`\n\nwith a Zod `schema`\n\n, plus `name`\n\nand `description`\n\nso the model knows when to call each tool:\n\n``` js\nimport { tool } from 'langchain';\nimport { z } from 'zod';\n\nconst getInvoice = tool(\n  async ({ invoiceId }) => {\n    const invoice = invoices.find((item) => item.id === invoiceId);\n    if (!invoice) {\n      return { found: false, invoiceId, error: 'Invoice not found' };\n    }\n    return { found: true, invoice };\n  },\n  {\n    name: 'get_invoice',\n    description: 'Look up an invoice by ID, including payment IDs and status',\n    schema: z.object({\n      invoiceId: z.string().describe('Invoice ID, e.g. inv_8891'),\n    }),\n  },\n);\n```\n\nLangChain uses `schema`\n\n(not Vercel's `inputSchema`\n\nor OpenAI Agents' `parameters`\n\n). The handler receives validated input as the first argument.\n\nWire the model, tools, and triage instructions:\n\n``` js\nimport { createAgent } from 'langchain';\n\nconst agent = createAgent({\n  model: 'gpt-5.5',\n  tools: [getInvoice],\n  systemPrompt: `You are a billing support triage agent.\nLook up records before recommending refunds or creating tickets.`,\n});\n```\n\n`model`\n\ncan be a provider string (`'gpt-5.5'`\n\n, `'openai:gpt-5.5'`\n\n) or a chat model instance from `@langchain/openai`\n\n.\n\nPass a `messages`\n\narray and read the final answer from `result.messages`\n\n:\n\n``` js\nconst result = await agent.invoke({\n  messages: [\n    {\n      role: 'user',\n      content: 'What is the status of invoice inv_8891? Reply in one sentence.',\n    },\n  ],\n});\n\nconst lastAi = [...result.messages]\n  .reverse()\n  .find((message) => message.type === 'ai');\n\nconsole.log(lastAi?.content);\n```\n\nThe last AI message is the agent's final reply after any tool calls complete.\n\n**Example prompt:**\n\nCustomer cus_1042 says they were charged twice for invoice inv_8891. What should we do?\n\nA realistic chain:\n\n`get_customer`\n\n- plan tier, open ticket count`get_invoice`\n\n- amount, status, payment IDs`search_knowledge_base`\n\n- duplicate-charge and refund policy`create_support_ticket`\n\nor `escalate_to_human`\n\n- write action or escalationThe demo uses in-memory fixtures (customers, invoices, knowledge-base articles) so scripts run without a database.\n\nRegister all triage tools on one agent:\n\n``` js\nimport { createAgent } from 'langchain';\nimport {\n  getCustomer,\n  getInvoice,\n  searchKnowledgeBase,\n  createSupportTicket,\n  escalateToHuman,\n  TRIAGE_INSTRUCTIONS,\n} from './tools/index.js';\n\nconst agent = createAgent({\n  model: 'gpt-5.5',\n  tools: [\n    getCustomer,\n    getInvoice,\n    searchKnowledgeBase,\n    createSupportTicket,\n    escalateToHuman,\n  ],\n  systemPrompt: TRIAGE_INSTRUCTIONS,\n});\n\nconst result = await agent.invoke({\n  messages: [\n    {\n      role: 'user',\n      content:\n        'Customer cus_1042 says they were charged twice for invoice inv_8891. What should we do?',\n    },\n  ],\n  recursionLimit: 15,\n});\n\nconst answer = [...result.messages]\n  .reverse()\n  .find((message) => message.type === 'ai');\n\nconsole.log(answer?.content);\n```\n\nInspect `result.messages`\n\nfor the full trace: human input, AI tool-call messages, tool results, and the final AI reply.\n\n`agent.stream()`\n\nyields state updates as the graph runs. Use `streamMode: 'values'`\n\nto receive the full message list after each step:\n\n``` js\nconst stream = await agent.stream(\n  {\n    messages: [\n      {\n        role: 'user',\n        content:\n          'Customer cus_1042 says they were charged twice for invoice inv_8891. What should we do?',\n      },\n    ],\n  },\n  { streamMode: 'values', recursionLimit: 15 },\n);\n\nlet finalMessages = [];\n\nfor await (const state of stream) {\n  if (state.messages) {\n    finalMessages = state.messages;\n  }\n}\n\nconst answer = [...finalMessages]\n  .reverse()\n  .find((message) => message.type === 'ai');\n\nconsole.log(answer?.content);\n```\n\nFor token-level streaming, use `streamMode: 'messages'`\n\nor `streamEvents`\n\n(see [LangGraph streaming](https://docs.langchain.com/oss/javascript/langgraph/streaming)).\n\nLangChain `createAgent`\n|\nVercel AI SDK | OpenAI Agents SDK | |\n|---|---|---|---|\nBest for |\nRAG + LCEL + agents in one stack | TypeScript apps already on AI SDK | OpenAI-first agent primitives |\nTool definition |\n`tool()` + Zod `schema`\n|\n`tool()` + `inputSchema`\n|\n`tool()` + Zod `parameters`\n|\nRun API |\n`agent.invoke` / `agent.stream`\n|\n`generateText` + `stopWhen`\n|\n`run()` + `maxTurns`\n|\nHandoffs / guardrails |\nMiddleware (advanced) | Limited | Built-in |\nMemory |\nLangGraph checkpointers | Bring your own | Session helpers |\n\nPick 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.\n\nSee 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.", "url": "https://wpnews.pro/news/building-ai-agents-with-langchain", "canonical_source": "https://dev.to/zsevic/building-ai-agents-with-langchain-5e69", "published_at": "2026-06-16 20:31:35+00:00", "updated_at": "2026-06-16 20:47:00.696705+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "large-language-models"], "entities": ["LangChain", "LangGraph", "OpenAI", "Vercel AI SDK", "OpenAI Agents SDK", "Node.js", "pgvector", "Zod"], "alternates": {"html": "https://wpnews.pro/news/building-ai-agents-with-langchain", "markdown": "https://wpnews.pro/news/building-ai-agents-with-langchain.md", "text": "https://wpnews.pro/news/building-ai-agents-with-langchain.txt", "jsonld": "https://wpnews.pro/news/building-ai-agents-with-langchain.jsonld"}}