{"id":80296,"date":"2026-07-10T12:37:19","date_gmt":"2026-07-10T07:07:19","guid":{"rendered":"https:\/\/www.tothenew.com\/blog\/?p=80296"},"modified":"2026-08-07T22:36:21","modified_gmt":"2026-08-07T17:06:21","slug":"how-to-build-a-tool-calling-ai-agent-using-langchain-and-typescript","status":"publish","type":"post","link":"https:\/\/www.tothenew.com\/blog\/how-to-build-a-tool-calling-ai-agent-using-langchain-and-typescript\/","title":{"rendered":"How to Build a Tool-Calling AI Agent Using LangChain and TypeScript"},"content":{"rendered":"<h2>Introduction<\/h2>\n<p>Large Language Models (LLMs) are incredibly powerful, but they have a major limitation: they are cut off from the real world.<\/p>\n<p>Imagine building a customer support bot. A user asks, &#8220;<em>Where is my order #12345?<\/em>&#8221; On its own, an LLM cannot fetch live database records, call a shipping provider&#8217;s API, or check inventory levels to answer this. It might guess or politely apologize.<\/p>\n<p>Agents\u00a0solve this problem. An agent is a system that uses an LLM as a reasoning engine to make\u00a0<strong>dynamic decisions at runtime<\/strong>\u00a0about which tools (functions you write) to call based on the user&#8217;s input. Instead of running a fixed, hardcoded sequence of steps, the agent evaluates the problem, selects the right tool, and decides what to do next based on the result. In our support bot example, the agent would recognize it needs the\u00a0OrderLookupTool, generate a tool call with\u00a0#12345, and formulate a helpful response based on the actual delivery status.<\/p>\n<p>In this post, we will build a simple, working AI agent in LangChain.js using TypeScript. You&#8217;ll learn how to define custom tools using Zod schemas and run them inside an agent execution loop.<\/p>\n<h2><strong>What You&#8217;ll Build<\/strong><\/h2>\n<p>By the end of this guide, you will have:<\/p>\n<ul>\n<li><strong>A custom tool\u00a0<\/strong>(order_lookup) defined with a validated Zod schema that an LLM can reliably call.<\/li>\n<li><strong>A working AI agent<\/strong>\u00a0powered by Google Gemini that dynamically decides when and how to use your tool.<\/li>\n<li><strong>A complete agent execution loop\u00a0<\/strong>that sends user input to the LLM, executes tool calls, and returns a final natural-language response.<\/li>\n<li><strong>A solid mental model\u00a0<\/strong>of how Chains, Agents, Tools, and the AgentExecutor fit together \u2014 giving you the foundation to build more complex multi-tool agents.<\/li>\n<\/ul>\n<h2>Prerequisites &amp; Setup<\/h2>\n<p>You&#8217;ll need\u00a0Node.js, basic TypeScript knowledge, and a free\u00a0Google AI Studio API key.<\/p>\n<ul>\n<li>First, install the dependencies:\n<pre>npm install @langchain\/core @langchain\/classic @langchain\/google-genai zod<\/pre>\n<\/li>\n<li>Then export your API key as an environment variable:\n<pre>export GOOGLE_API_KEY=\"your-key-here\"<\/pre>\n<\/li>\n<li>To run any .ts file in this post, the quickest way is tsx:\n<pre>npx tsx agent.ts<\/pre>\n<\/li>\n<\/ul>\n<h2>Why Not Just Use Chains?<\/h2>\n<p>If you&#8217;ve worked with LangChain&#8217;s LCEL (LangChain Expression Language), you&#8217;ve built Chains. Chains are\u00a0deterministic\u00a0\u2014 the execution path is fixed. If you pipe\u00a0Prompt \u2192 Model \u2192 Parser, it runs exactly in that order, regardless of input.<br \/>\n<strong><br \/>\n<\/strong><strong>Agents,<\/strong> however, make\u00a0<strong>dynamic decisions at runtime<\/strong>. Instead of hardcoding steps, you give an Agent a goal and a toolbox. It uses the LLM as a &#8220;reasoning engine&#8221; to decide\u00a0on every turn:<\/p>\n<ol>\n<li>Which tool should I use?<\/li>\n<li>What arguments do I pass?<\/li>\n<li>Based on the result, should I call another tool or respond?<\/li>\n<\/ol>\n<p>If you ask an Agent to\u00a0&#8220;Cancel order #12345 and email a receipt&#8221;, it dynamically decides to call the Cancellation Tool, inspects the result, then calls the Email Tool. A standard Chain cannot do this.<\/p>\n<div id=\"attachment_80658\" style=\"width: 825px\" class=\"wp-caption alignnone\"><img aria-describedby=\"caption-attachment-80658\" decoding=\"async\" loading=\"lazy\" class=\"wp-image-80658\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2026\/07\/chains_vs_agents.jpg\" alt=\"chains_vs_agents\" width=\"815\" height=\"815\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2026\/07\/chains_vs_agents.jpg 1024w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/chains_vs_agents-300x300.jpg 300w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/chains_vs_agents-150x150.jpg 150w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/chains_vs_agents-768x768.jpg 768w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/chains_vs_agents-624x624.jpg 624w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/chains_vs_agents-120x120.jpg 120w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/chains_vs_agents-24x24.jpg 24w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/chains_vs_agents-48x48.jpg 48w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/chains_vs_agents-96x96.jpg 96w\" sizes=\"(max-width: 815px) 100vw, 815px\" \/><p id=\"caption-attachment-80658\" class=\"wp-caption-text\">chains vs agents<\/p><\/div>\n<h2>What Exactly is a Tool?<\/h2>\n<p>A Tool is simply a JavaScript\/TypeScript function that the LLM can\u00a0request\u00a0to be executed on its behalf. The LLM never runs the tool directly \u2014 <strong>it\u00a0generates a structured tool<\/strong> call\u00a0that your application then executes. However, for the LLM to know\u00a0when and how\u00a0to generate the right call, we must describe the tool clearly.<\/p>\n<p>In LangChain, we use\u00a0<strong>DynamicStructuredTool<\/strong>. It requires three things:<\/p>\n<ol>\n<li><strong>Name\u00a0<\/strong>\u2014 a unique identifier (e.g.,\u00a0order_lookup).<\/li>\n<li><strong>Description\u00a0<\/strong>\u2014 \u00a0tells the LLM\u00a0when\u00a0to use this tool and\u00a0what it does. This is arguably the most important part. The LLM reads this description to decide which tool fits the user&#8217;s request, so a vague description like\u00a0&#8220;does stuff with orders&#8221;\u00a0will lead to unreliable behavior. A good description is specific and action-oriented:\u00a0&#8220;Use this tool to check the current delivery status of a customer&#8217;s order given an order ID.&#8221;\u00a0The clearer you are, the more accurately the LLM will choose the correct tool and provide the right arguments.<\/li>\n<li><strong>Schema (Zod)<\/strong> \u2014 \u00a0a strict, validated schema (using Zod, a popular TypeScript validation library) defining exactly what arguments the function expects.<\/li>\n<\/ol>\n<h2>Why Zod Schemas Matter<\/h2>\n<p>Zod is not just a type annotation \u2014 it provides runtime validation of the arguments the LLM generates. This matters because LLMs can hallucinate or produce malformed arguments. A Zod schema:<\/p>\n<ul>\n<li><strong>Validates inputs<\/strong> before your function ever runs, catching bad data early.<\/li>\n<li><strong>Describes each field<\/strong>\u00a0(via\u00a0.describe()) so the LLM knows what values to provide.<\/li>\n<li><strong>Makes function calling more reliable<\/strong>\u00a0by giving the model a precise contract to follow, rather than guessing at a free-form structure.<\/li>\n<\/ul>\n<p>Because models like Gemini 2.5 and GPT-4 are fine-tuned for &#8220;Function Calling,&#8221; they can reliably format their output to match your Zod schema \u2014 but only if the schema is well-defined and descriptive.<\/p>\n<div id=\"attachment_80682\" style=\"width: 825px\" class=\"wp-caption alignnone\"><img aria-describedby=\"caption-attachment-80682\" decoding=\"async\" loading=\"lazy\" class=\"wp-image-80682\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2026\/07\/carbon-1024x686.png\" alt=\"Tool creation\" width=\"815\" height=\"546\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2026\/07\/carbon-1024x686.png 1024w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/carbon-300x201.png 300w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/carbon-768x514.png 768w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/carbon-1536x1028.png 1536w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/carbon-624x418.png 624w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/carbon.png 1834w\" sizes=\"(max-width: 815px) 100vw, 815px\" \/><p id=\"caption-attachment-80682\" class=\"wp-caption-text\">tool creation<\/p><\/div>\n<p>When you bind this tool to a model and ask\u00a0&#8220;Where is my order #12345?&#8221;, instead of answering directly, the LLM\u00a0generates a tool call\u00a0\u2014 a structured request to invoke your function:<\/p>\n<pre>{ \"name\": \"order_lookup\", \"args\": { \"orderId\": \"12345\" } }<\/pre>\n<p>The LLM is saying\u00a0&#8220;I need to call this function with these arguments.&#8221;\u00a0It doesn&#8217;t actually execute anything \u2014 it only produces the structured call. Your application is responsible for running the actual function. So who orchestrates this? That&#8217;s the Agent&#8217;s job.<\/p>\n<h2>How Does the Agent Actually Work?<\/h2>\n<p>The LLM does not execute tools directly. It only\u00a0<strong>generates tool calls<\/strong>\u00a0\u2014 structured requests specifying which function to invoke and with what arguments. Your application is responsible for actually running the code.<\/p>\n<p>All tool-calling agents \u2014 regardless of the LLM provider or framework \u2014 follow the same fundamental execution cycle. LangChain automates this with the\u00a0AgentExecutor, a continuous loop that runs on your machine:<\/p>\n<ol>\n<li>Sends the user&#8217;s prompt to the LLM.<\/li>\n<li>The LLM generates a tool call:\u00a0order_lookup({ orderId: &#8220;12345&#8221; }).<\/li>\n<li>The AgentExecutor intercepts this, pauses the LLM, and executes your TypeScript function.<\/li>\n<li>The function returns\u00a0Order 12345 status: Shipped &#8211; Arriving Tomorrow.<\/li>\n<li>The AgentExecutor feeds the tool result back to the LLM as a new message:\u00a0&#8220;The tool returned &#8216;Shipped &#8211; Arriving Tomorrow&#8217;. What next?&#8221;<\/li>\n<li>The LLM decides no more tools are needed and responds:\u00a0&#8220;Your order has been shipped and is arriving tomorrow!&#8221;\u00a0\u2014 the loop terminates.<\/li>\n<li style=\"list-style-type: none;\">This is the Thought \u2192 Action \u2192 Observation loop.<\/li>\n<\/ol>\n<h2>Expected Execution Flow<\/h2>\n<p>Here is the full sequence visualized for a single request:<\/p>\n<div id=\"attachment_80683\" style=\"width: 825px\" class=\"wp-caption alignnone\"><img aria-describedby=\"caption-attachment-80683\" decoding=\"async\" loading=\"lazy\" class=\"wp-image-80683\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2026\/07\/agent_loop-1.jpg\" alt=\"agent_loop\" width=\"815\" height=\"815\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2026\/07\/agent_loop-1.jpg 1024w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/agent_loop-1-300x300.jpg 300w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/agent_loop-1-150x150.jpg 150w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/agent_loop-1-768x768.jpg 768w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/agent_loop-1-624x624.jpg 624w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/agent_loop-1-120x120.jpg 120w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/agent_loop-1-24x24.jpg 24w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/agent_loop-1-48x48.jpg 48w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/agent_loop-1-96x96.jpg 96w\" sizes=\"(max-width: 815px) 100vw, 815px\" \/><p id=\"caption-attachment-80683\" class=\"wp-caption-text\">agent loop<\/p><\/div>\n<div id=\"attachment_80685\" style=\"width: 825px\" class=\"wp-caption alignnone\"><img aria-describedby=\"caption-attachment-80685\" decoding=\"async\" loading=\"lazy\" class=\"wp-image-80685\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2026\/07\/carbon-1.png\" alt=\"agentts\" width=\"815\" height=\"638\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2026\/07\/carbon-1.png 2048w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/carbon-1-300x235.png 300w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/carbon-1-1024x801.png 1024w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/carbon-1-768x601.png 768w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/carbon-1-1536x1202.png 1536w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/carbon-1-624x488.png 624w\" sizes=\"(max-width: 815px) 100vw, 815px\" \/><p id=\"caption-attachment-80685\" class=\"wp-caption-text\">agent.ts<\/p><\/div>\n<div id=\"attachment_81245\" style=\"width: 825px\" class=\"wp-caption alignnone\"><img aria-describedby=\"caption-attachment-81245\" decoding=\"async\" loading=\"lazy\" class=\"wp-image-81245\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2026\/08\/Screenshot-2026-08-06-163254-1024x613.png\" alt=\"cursor IDE implementation\" width=\"815\" height=\"488\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2026\/08\/Screenshot-2026-08-06-163254-1024x613.png 1024w, \/blog\/wp-ttn-blog\/uploads\/2026\/08\/Screenshot-2026-08-06-163254-300x180.png 300w, \/blog\/wp-ttn-blog\/uploads\/2026\/08\/Screenshot-2026-08-06-163254-768x460.png 768w, \/blog\/wp-ttn-blog\/uploads\/2026\/08\/Screenshot-2026-08-06-163254-1536x920.png 1536w, \/blog\/wp-ttn-blog\/uploads\/2026\/08\/Screenshot-2026-08-06-163254-624x374.png 624w, \/blog\/wp-ttn-blog\/uploads\/2026\/08\/Screenshot-2026-08-06-163254.png 1916w\" sizes=\"(max-width: 815px) 100vw, 815px\" \/><p id=\"caption-attachment-81245\" class=\"wp-caption-text\">cursor IDE implementation<\/p><\/div>\n<h2>Understanding &#8216; agent_scratchpad &#8216;<\/h2>\n<p>The <em>agent_scratchpad<\/em> placeholder is critical. It acts as the agent&#8217;s <strong>working memory within a single request<\/strong>. As the AgentExecutor loop runs, it injects the history of all intermediate tool calls and their results into this placeholder. This allows the LLM to see what tools it has already called, what results they returned, and reason about what to do next \u2014 all within the same invocation. Without it, the LLM would have no context about previous steps and would be unable to chain multiple tool calls together or decide when to stop.<\/p>\n<h2>Common Pitfalls<\/h2>\n<p>Before wrapping up, avoid these frequent mistakes:<\/p>\n<table style=\"border-collapse: collapse; width: 100%; height: 145px;\">\n<tbody>\n<tr style=\"height: 49px;\">\n<td style=\"width: 28.2471%; text-align: center; height: 49px;\"><span style=\"color: #000000;\"><strong><br \/>\nPitfall<\/strong><\/span><\/td>\n<td style=\"width: 38.4195%; text-align: center; height: 49px;\"><span style=\"color: #000000;\"><strong>Why It Matters<\/strong><\/span><\/td>\n<td style=\"width: 33.3333%; text-align: center; height: 49px;\"><span style=\"color: #000000;\"><strong>Fix<\/strong><\/span><\/td>\n<\/tr>\n<tr style=\"height: 24px;\">\n<td style=\"width: 28.2471%; text-align: left; height: 24px;\"><span style=\"color: #000000;\"><strong>\u00a0 \u00a0Vague tool descriptions<\/strong><\/span><\/td>\n<td style=\"width: 38.4195%; height: 24px;\"><span style=\"color: #000000;\">\u00a0 \u00a0LLMs rely on this to pick tools. Ambiguity causes errors.<\/span><\/td>\n<td style=\"width: 33.3333%; height: 24px;\"><span style=\"color: #000000;\">\u00a0 \u00a0Write specific, action-oriented descriptions.<\/span><\/td>\n<\/tr>\n<tr style=\"height: 24px;\">\n<td style=\"width: 28.2471%; text-align: left; height: 24px;\"><span style=\"color: #000000;\"><strong>\u00a0 \u00a0Forgetting agent_scratchpad<\/strong><\/span><\/td>\n<td style=\"width: 38.4195%; height: 24px;\"><span style=\"color: #000000;\">\u00a0 \u00a0The LLM forgets intermediate steps, causing infinite loops.<\/span><\/td>\n<td style=\"width: 33.3333%; height: 24px;\"><span style=\"color: #000000;\">\u00a0 \u00a0Include [&#8220;placeholder&#8221;, &#8220;{agent_scratchpad}&#8221;] in prompts.<\/span><\/td>\n<\/tr>\n<tr style=\"height: 24px;\">\n<td style=\"width: 28.2471%; text-align: left; height: 24px;\"><span style=\"color: #000000;\"><strong>\u00a0 \u00a0Overly permissive schemas<\/strong><\/span><\/td>\n<td style=\"width: 38.4195%; height: 24px;\"><span style=\"color: #000000;\">\u00a0 \u00a0Broad schemas (z.any()) allow hallucinated arguments.<\/span><\/td>\n<td style=\"width: 33.3333%; height: 24px;\"><span style=\"color: #000000;\">\u00a0 \u00a0Use precise Zod schemas and .describe() fields.<\/span><\/td>\n<\/tr>\n<tr style=\"height: 24px;\">\n<td style=\"width: 28.2471%; text-align: left; height: 24px;\"><span style=\"color: #000000;\"><strong>\u00a0 \u00a0Assuming the LLM executes tools directly<\/strong><\/span><\/td>\n<td style=\"width: 38.4195%; height: 24px;\"><span style=\"color: #000000;\">\u00a0 \u00a0The LLM only formats the request; it doesn&#8217;t run code.<\/span><\/td>\n<td style=\"width: 33.3333%; height: 24px;\"><span style=\"color: #000000;\">\u00a0 \u00a0Handle execution entirely on your server-side.<\/span><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2>Conclusion<\/h2>\n<p>The main takeaway is simple:\u00a0<strong>Chains follow a fixed, deterministic path decided at design time, while Agents make dynamic decisions at runtime based on context and intermediate results.<\/strong><\/p>\n<p>By turning external capabilities \u2014 database queries, shipping APIs, payment processors \u2014 into well-described tools with strict Zod schemas, you give the LLM the ability to interact with the real world reliably. The AgentExecutor handles the orchestration loop so you can focus on building great tools.<\/p>\n<p>This pattern unlocks powerful practical applications: agents that\u00a0<strong>coordinate multiple APIs<\/strong>\u00a0in a single request,\u00a0<strong>retain conversation context<\/strong>\u00a0across turns with memory, and\u00a0<strong>automate multi-step workflows<\/strong>\u00a0that would otherwise require complex, brittle if\/else trees. While building true production-grade agents requires additional layers like observability, advanced error handling, and evaluation, mastering the fundamentals in this post gives you the exact foundation needed to start tackling real-world complexity.<\/p>\n<h2>Next Steps<\/h2>\n<ul>\n<li><strong>Multiple Tools:<\/strong> \u00a0Right now, our agent only has one tool. But an agent&#8217;s true power comes from its toolbox. Try adding an\u00a0InventoryCheckerTool, a\u00a0ProcessRefundTool, or a\u00a0ShippingCostCalculator. The LLM will dynamically decide which tool \u2014 or sequence of tools \u2014 to call based on the user&#8217;s request.<\/li>\n<li><strong>Memory (BufferMemory):<\/strong> Our current loop is stateless; it forgets everything after one message. By injecting LangChain&#8217;s\u00a0BufferMemory\u00a0into the prompt, your agent can retain conversation context across turns. This allows the bot to remember the customer&#8217;s email or order ID without asking twice.<\/li>\n<li><strong>Error Handling:<\/strong> What happens if the shipping API goes down, or the LLM generates a call to a tool that doesn&#8217;t exist? You can implement fallback logic using LangChain&#8217;s\u00a0handleParsingErrors\u00a0flag on the AgentExecutor to gracefully inform the LLM that the tool call failed, prompting it to try a different approach instead of crashing the app.<\/li>\n<\/ul>\n<p>The possibilities are endless. <strong>Happy building!<\/strong><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduction Large Language Models (LLMs) are incredibly powerful, but they have a major limitation: they are cut off from the real world. Imagine building a customer support bot. A user asks, &#8220;Where is my order #12345?&#8221; On its own, an LLM cannot fetch live database records, call a shipping provider&#8217;s API, or check inventory levels [&hellip;]<\/p>\n","protected":false},"author":2230,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"iawp_total_views":6},"categories":[5876],"tags":[8540,5733,8666,4117],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/80296"}],"collection":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/users\/2230"}],"replies":[{"embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/comments?post=80296"}],"version-history":[{"count":23,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/80296\/revisions"}],"predecessor-version":[{"id":81266,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/80296\/revisions\/81266"}],"wp:attachment":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/media?parent=80296"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/categories?post=80296"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/tags?post=80296"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}