Skip to main content

Introduction

Tools enable language models to interact with external systems and data sources. They allow agents to:
  • Query databases and APIs
  • Perform calculations
  • Access files and documents
  • Execute code
  • Interact with any external service
LangChain.js provides flexible ways to create tools with built-in validation and error handling.

Quick Start

The simplest way to create a tool is using the tool function:
import { tool } from "@langchain/core/tools";
import { z } from "zod";

const weatherTool = tool(
  async ({ location }) => {
    // Your tool logic here
    const response = await fetch(`https://api.weather.com/v1/${location}`);
    const data = await response.json();
    return `Temperature: ${data.temp}°F, Conditions: ${data.conditions}`;
  },
  {
    name: "get_weather",
    description: "Get current weather for a location. Use this when users ask about weather conditions.",
    schema: z.object({
      location: z.string().describe("The city name, e.g., 'San Francisco' or 'London'")
    })
  }
);

// Use with an agent
import { createAgent } from "langchain";
import { ChatOpenAI } from "@langchain/openai";

const agent = createAgent({
  model: new ChatOpenAI({ model: "gpt-4o" }),
  tools: [weatherTool]
});

const result = await agent.invoke({
  messages: "What's the weather in Tokyo?"
});

Tool Components

Tool Schema

Define input parameters using Zod schemas:
import { z } from "zod";

const schema = z.object({
  query: z.string().describe("Search query"),
  maxResults: z.number().optional().describe("Maximum results to return"),
  language: z.enum(["en", "es", "fr"]).optional().default("en")
});

Tool Function

The function that executes when the tool is called:
const toolFunction = async (input, config) => {
  // input: validated and parsed according to schema
  // config: runtime configuration (callbacks, metadata, etc.)
  
  const results = await performSearch(input.query);
  return JSON.stringify(results);
};

Tool Metadata

Provide name and description to guide the model:
const metadata = {
  name: "web_search",
  description: "Search the web for information. Use when you need current information or facts not in your training data."
};

Creating Basic Tools

Simple String Input Tool

For tools that take a single string input:
import { tool } from "@langchain/core/tools";
import { z } from "zod";

const summarizeTool = tool(
  async (text) => {
    // Process text and return summary
    const summary = await generateSummary(text);
    return summary;
  },
  {
    name: "summarize_text",
    description: "Summarize long text into key points",
    schema: z.string().describe("Text to summarize")
  }
);

Structured Input Tool

For tools with multiple parameters:
const calculatorTool = tool(
  async ({ operation, a, b }) => {
    const operations = {
      add: (x, y) => x + y,
      subtract: (x, y) => x - y,
      multiply: (x, y) => x * y,
      divide: (x, y) => x / y
    };
    
    const result = operations[operation](a, b);
    return `${a} ${operation} ${b} = ${result}`;
  },
  {
    name: "calculator",
    description: "Perform mathematical operations",
    schema: z.object({
      operation: z.enum(["add", "subtract", "multiply", "divide"]),
      a: z.number().describe("First number"),
      b: z.number().describe("Second number")
    })
  }
);

Advanced Tool Patterns

Tool with External API

import { tool } from "@langchain/core/tools";
import { z } from "zod";

const githubTool = tool(
  async ({ owner, repo }) => {
    const response = await fetch(
      `https://api.github.com/repos/${owner}/${repo}`,
      {
        headers: {
          'Authorization': `Bearer ${process.env.GITHUB_TOKEN}`
        }
      }
    );
    
    if (!response.ok) {
      throw new Error(`GitHub API error: ${response.statusText}`);
    }
    
    const data = await response.json();
    return JSON.stringify({
      name: data.name,
      description: data.description,
      stars: data.stargazers_count,
      forks: data.forks_count,
      language: data.language
    });
  },
  {
    name: "get_github_repo",
    description: "Get information about a GitHub repository",
    schema: z.object({
      owner: z.string().describe("Repository owner username"),
      repo: z.string().describe("Repository name")
    })
  }
);

Tool with Database Access

import { tool } from "@langchain/core/tools";
import { z } from "zod";
import { createClient } from '@supabase/supabase-js';

const supabase = createClient(
  process.env.SUPABASE_URL,
  process.env.SUPABASE_KEY
);

const databaseTool = tool(
  async ({ table, filters }) => {
    let query = supabase.from(table).select('*');
    
    // Apply filters
    for (const [column, value] of Object.entries(filters)) {
      query = query.eq(column, value);
    }
    
    const { data, error } = await query;
    
    if (error) {
      throw new Error(`Database error: ${error.message}`);
    }
    
    return JSON.stringify(data);
  },
  {
    name: "query_database",
    description: "Query the product database. Use to find products, check inventory, or get pricing.",
    schema: z.object({
      table: z.enum(["products", "inventory", "orders"]),
      filters: z.record(z.string(), z.any()).describe("Filter conditions")
    })
  }
);

Tool with File System Access

import { tool } from "@langchain/core/tools";
import { z } from "zod";
import fs from "fs/promises";
import path from "path";

const fileReaderTool = tool(
  async ({ filepath }) => {
    // Security: validate path is within allowed directory
    const allowedDir = path.resolve(process.cwd(), 'data');
    const fullPath = path.resolve(allowedDir, filepath);
    
    if (!fullPath.startsWith(allowedDir)) {
      throw new Error("Access denied: path outside allowed directory");
    }
    
    try {
      const content = await fs.readFile(fullPath, 'utf-8');
      return content;
    } catch (error) {
      throw new Error(`Failed to read file: ${error.message}`);
    }
  },
  {
    name: "read_file",
    description: "Read contents of a file from the data directory",
    schema: z.object({
      filepath: z.string().describe("Path to file relative to data directory")
    })
  }
);

Tool Response Formats

Content-Only Response (Default)

Return a string or object:
const simpleTool = tool(
  async ({ query }) => {
    return "This is the tool response";
  },
  {
    name: "simple_tool",
    description: "A simple tool",
    schema: z.object({ query: z.string() })
  }
);

Content and Artifact Response

Return both display content and structured data:
const advancedTool = tool(
  async ({ query }) => {
    const data = await fetchData(query);
    
    // Return [content, artifact]
    return [
      `Found ${data.length} results for ${query}`,  // Content shown to user
      { results: data, timestamp: Date.now() }       // Artifact for programmatic use
    ];
  },
  {
    name: "advanced_search",
    description: "Search with detailed results",
    schema: z.object({ query: z.string() }),
    responseFormat: "content_and_artifact"
  }
);

Using the DynamicStructuredTool Class

For more control, use the DynamicStructuredTool class:
import { DynamicStructuredTool } from "@langchain/core/tools";
import { z } from "zod";

class CustomTool extends DynamicStructuredTool {
  constructor() {
    super({
      name: "custom_tool",
      description: "A custom tool with class-based implementation",
      schema: z.object({
        input: z.string()
      }),
      func: async (input, runManager) => {
        // Access to runManager for callbacks
        await runManager?.handleToolStart?.(...);
        
        const result = await this.processInput(input);
        
        await runManager?.handleToolEnd?.(...);
        return result;
      }
    });
  }
  
  private async processInput(input: any): Promise<string> {
    // Your custom logic
    return "Processed result";
  }
}

const customTool = new CustomTool();

Tool Configuration

Adding Metadata

const tool = tool(
  async ({ query }) => { /* ... */ },
  {
    name: "search",
    description: "Search tool",
    schema: z.object({ query: z.string() }),
    metadata: {
      category: "search",
      version: "1.0.0",
      author: "Your Team"
    }
  }
);

Verbose Error Messages

Enable detailed parsing errors:
const tool = tool(
  async ({ query }) => { /* ... */ },
  {
    name: "search",
    description: "Search tool",
    schema: z.object({ query: z.string() }),
    verboseParsingErrors: true  // Show detailed validation errors
  }
);

Return Direct

Stop agent loop after tool execution:
const finalTool = tool(
  async ({ data }) => {
    return "This is the final answer";
  },
  {
    name: "final_answer",
    description: "Provide the final answer",
    schema: z.object({ data: z.string() }),
    returnDirect: true  // Agent will stop after this tool
  }
);

Accessing Runtime Context

Access configuration and state during tool execution:
import { tool } from "@langchain/core/tools";
import { LangGraphRunnableConfig } from "@langchain/langgraph";
import { z } from "zod";

const contextualTool = tool(
  async ({ query }, config: LangGraphRunnableConfig) => {
    // Access callbacks
    const callbacks = config.callbacks;
    
    // Access store (for agents with memory)
    const store = config.store;
    const userData = await store?.get(["users"], config.userId);
    
    // Access metadata
    const metadata = config.metadata;
    
    // Use context in your logic
    return `Processing ${query} for user ${userData.name}`;
  },
  {
    name: "contextual_search",
    description: "Search with user context",
    schema: z.object({ query: z.string() })
  }
);

Error Handling

Graceful Error Handling

const robustTool = tool(
  async ({ url }) => {
    try {
      const response = await fetch(url, { timeout: 5000 });
      
      if (!response.ok) {
        return `Error: HTTP ${response.status} - ${response.statusText}`;
      }
      
      const data = await response.json();
      return JSON.stringify(data);
      
    } catch (error) {
      if (error.name === 'TimeoutError') {
        return "Error: Request timed out";
      }
      return `Error: ${error.message}`;
    }
  },
  {
    name: "fetch_url",
    description: "Fetch data from a URL",
    schema: z.object({ url: z.string().url() })
  }
);

Throwing Errors

const strictTool = tool(
  async ({ query }) => {
    if (!query || query.trim() === "") {
      throw new Error("Query cannot be empty");
    }
    
    const results = await search(query);
    
    if (results.length === 0) {
      throw new Error(`No results found for query: ${query}`);
    }
    
    return JSON.stringify(results);
  },
  {
    name: "strict_search",
    description: "Search with strict validation",
    schema: z.object({ query: z.string().min(1) })
  }
);

Tool Collections

Creating a Toolkit

import { BaseToolkit, StructuredToolInterface } from "@langchain/core/tools";

class DatabaseToolkit extends BaseToolkit {
  tools: StructuredToolInterface[];
  
  constructor(private connectionString: string) {
    super();
    this.tools = [
      this.createQueryTool(),
      this.createInsertTool(),
      this.createUpdateTool()
    ];
  }
  
  private createQueryTool() {
    return tool(
      async ({ query }) => {
        // Query logic
      },
      {
        name: "db_query",
        description: "Query the database",
        schema: z.object({ query: z.string() })
      }
    );
  }
  
  private createInsertTool() {
    return tool(
      async ({ table, data }) => {
        // Insert logic
      },
      {
        name: "db_insert",
        description: "Insert data into database",
        schema: z.object({
          table: z.string(),
          data: z.record(z.any())
        })
      }
    );
  }
  
  private createUpdateTool() {
    return tool(
      async ({ table, id, data }) => {
        // Update logic
      },
      {
        name: "db_update",
        description: "Update database records",
        schema: z.object({
          table: z.string(),
          id: z.string(),
          data: z.record(z.any())
        })
      }
    );
  }
}

// Use the toolkit
const toolkit = new DatabaseToolkit(process.env.DATABASE_URL);
const tools = toolkit.getTools();

Testing Tools

import { describe, it, expect } from "vitest";

describe("weatherTool", () => {
  it("should return weather data", async () => {
    const result = await weatherTool.invoke({
      location: "San Francisco"
    });
    
    expect(result).toContain("Temperature");
    expect(result).toContain("Conditions");
  });
  
  it("should handle invalid location", async () => {
    await expect(
      weatherTool.invoke({ location: "InvalidCity123" })
    ).rejects.toThrow();
  });
  
  it("should validate schema", async () => {
    await expect(
      weatherTool.invoke({ location: 123 })
    ).rejects.toThrow("Received tool input did not match expected schema");
  });
});

Best Practices

Tool descriptions guide the agent’s decision-making:
// Bad: vague description
description: "Does something"

// Good: specific and actionable
description: "Search the product database for items matching the query. Returns product name, price, and availability. Use this when users ask about products, pricing, or stock."
Leverage Zod for robust input validation:
schema: z.object({
  email: z.string().email("Must be a valid email"),
  age: z.number().min(0).max(150),
  country: z.enum(["US", "UK", "CA"]),
  preferences: z.array(z.string()).optional()
})
Validate and sanitize inputs:
const secureTool = tool(
  async ({ command }) => {
    // Whitelist allowed commands
    const allowedCommands = ["status", "info", "list"];
    if (!allowedCommands.includes(command)) {
      throw new Error(`Command not allowed: ${command}`);
    }
    
    // Sanitize inputs
    const sanitized = command.replace(/[^a-zA-Z0-9]/g, "");
    return executeCommand(sanitized);
  },
  { /* ... */ }
);
Make tool outputs easy to process:
// Return JSON strings for structured data
return JSON.stringify({
  status: "success",
  data: results,
  metadata: {
    count: results.length,
    timestamp: Date.now()
  }
});
Implement rate limiting for external APIs:
import { RateLimiter } from "limiter";

const limiter = new RateLimiter({
  tokensPerInterval: 10,
  interval: "minute"
});

const rateLimitedTool = tool(
  async ({ query }) => {
    await limiter.removeTokens(1);
    return await callAPI(query);
  },
  { /* ... */ }
);

Next Steps

Building Agents

Use tools with agents for complex tasks

Working with Chat Models

Integrate tools with function calling

Callbacks and Tracing

Monitor tool execution and performance

Streaming

Stream tool outputs for real-time feedback

Build docs developers (and LLMs) love