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
Quick Start
The simplest way to create a tool is using thetool 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 theDynamicStructuredTool 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
Write Clear Descriptions
Write Clear Descriptions
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."
Use Strong Type Validation
Use Strong Type Validation
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()
})
Add Security Boundaries
Add Security Boundaries
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);
},
{ /* ... */ }
);
Return Structured Data
Return Structured Data
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()
}
});
Handle Rate Limits
Handle Rate Limits
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
