Define and use tools in LangChain - includes tool decorator, custom tools, built-in tools, and tool schemas
Install
npx skillscat add christian-bromann/langchain-skills/langchain-tools Install via the SkillsCat registry.
langchain-tools (JavaScript/TypeScript)
Overview
Tools are functions that agents can execute to perform actions like fetching data, running code, or querying databases. Tools have schemas that describe their purpose and parameters, helping models understand when and how to use them.
Key Concepts:
- tool(): Decorator to create tools from functions
- Schema: Zod schema defining tool parameters
- Description: Helps model understand when to use the tool
- Built-in Tools: Pre-made tools for common tasks
When to Define Custom Tools
| Scenario | Create Custom Tool? | Why |
|---|---|---|
| Domain-specific logic | ✅ Yes | Unique to your application |
| Third-party API integration | ✅ Yes | Custom integration needed |
| Database queries | ✅ Yes | Your schema/data |
| Common utilities (search, calc) | ⚠️ Maybe | Check for existing tools first |
| File operations | ⚠️ Maybe | Built-in filesystem tools exist |
Decision Tables
Tool Definition Methods
| Method | When to Use | Example |
|---|---|---|
tool() with function |
Simple tools | Basic transformations |
tool() with schema |
Complex parameters | Multiple typed fields |
StructuredTool |
Full control | Custom error handling |
| Built-in tools | Common operations | Search, code execution |
Code Examples
Basic Tool Definition
import { tool } from "langchain";
import { z } from "zod";
// Simple tool
const calculator = tool(
async ({ operation, a, b }: { operation: string; a: number; b: number }) => {
if (operation === "add") return a + b;
if (operation === "multiply") return a * b;
throw new Error(`Unknown operation: ${operation}`);
},
{
name: "calculator",
description: "Perform mathematical calculations. Use this when you need to compute numbers.",
schema: z.object({
operation: z.enum(["add", "subtract", "multiply", "divide"]).describe("The mathematical operation"),
a: z.number().describe("First number"),
b: z.number().describe("Second number"),
}),
}
);
// Use with agent
const result = await calculator.invoke({
operation: "add",
a: 5,
b: 3,
});
console.log(result); // "8"Tool with Detailed Schema
import { tool } from "langchain";
import { z } from "zod";
const searchDatabase = tool(
async ({ query, limit, filters }) => {
// Your database search logic
return `Found ${limit} results for: ${query}`;
},
{
name: "search_database",
description: "Search the customer database for records matching criteria",
schema: z.object({
query: z.string().describe("Search query (keywords or customer name)"),
limit: z.number().default(10).describe("Maximum number of results to return"),
filters: z.object({
status: z.enum(["active", "inactive", "pending"]).optional(),
created_after: z.string().optional().describe("ISO date string"),
}).optional(),
}),
}
);Async Tool
import { tool } from "langchain";
import { z } from "zod";
const fetchWeather = tool(
async ({ location }: { location: string }) => {
// Async API call
const response = await fetch(
`https://api.weather.com/v1/location/${location}`
);
const data = await response.json();
return `Temperature: ${data.temp}°F, Conditions: ${data.conditions}`;
},
{
name: "get_weather",
description: "Get current weather conditions for a location",
schema: z.object({
location: z.string().describe("City name or ZIP code"),
}),
}
);Tool with Error Handling
import { tool } from "langchain";
import { z } from "zod";
const divisionTool = tool(
async ({ numerator, denominator }) => {
if (denominator === 0) {
throw new Error("Cannot divide by zero");
}
return numerator / denominator;
},
{
name: "divide",
description: "Divide two numbers",
schema: z.object({
numerator: z.number(),
denominator: z.number(),
}),
}
);
// Error will be caught and returned as ToolMessageTool with Side Effects
import { tool } from "langchain";
import { z } from "zod";
import fs from "fs/promises";
const writeFile = tool(
async ({ filepath, content }) => {
await fs.writeFile(filepath, content, "utf-8");
return `Successfully wrote ${content.length} characters to ${filepath}`;
},
{
name: "write_file",
description: "Write content to a file. Use carefully as this modifies the filesystem.",
schema: z.object({
filepath: z.string().describe("Path to the file"),
content: z.string().describe("Content to write"),
}),
}
);Tool with External Dependencies
import { tool } from "langchain";
import { z } from "zod";
import axios from "axios";
const githubSearch = tool(
async ({ query, language }) => {
const response = await axios.get(
"https://api.github.com/search/repositories",
{
params: { q: `${query} language:${language}`, sort: "stars" },
headers: { Authorization: `Bearer ${process.env.GITHUB_TOKEN}` },
}
);
const repos = response.data.items.slice(0, 5);
return repos.map(r => `${r.full_name} (⭐ ${r.stargazers_count})`).join("\n");
},
{
name: "search_github",
description: "Search GitHub repositories",
schema: z.object({
query: z.string().describe("Search query"),
language: z.string().optional().describe("Programming language filter"),
}),
}
);Tool with Complex Return Type
import { tool } from "langchain";
import { z } from "zod";
const analyzeText = tool(
async ({ text }) => {
return JSON.stringify({
word_count: text.split(/\s+/).length,
char_count: text.length,
sentences: text.split(/[.!?]+/).length,
avg_word_length: text.split(/\s+/).reduce((sum, w) => sum + w.length, 0) / text.split(/\s+/).length,
});
},
{
name: "analyze_text",
description: "Analyze text statistics",
schema: z.object({
text: z.string().describe("Text to analyze"),
}),
}
);Tool with Runtime Configuration
import { tool } from "langchain";
import { z } from "zod";
function createDatabaseTool(connectionString: string) {
return tool(
async ({ query }) => {
// Use connectionString to connect to DB
const results = await db.query(query);
return JSON.stringify(results);
},
{
name: "query_database",
description: "Execute SQL query on the database",
schema: z.object({
query: z.string().describe("SQL query to execute"),
}),
}
);
}
// Create tool with specific configuration
const prodDbTool = createDatabaseTool(process.env.PROD_DB_URL);
const devDbTool = createDatabaseTool(process.env.DEV_DB_URL);Multiple Related Tools
import { tool } from "langchain";
import { z } from "zod";
// Toolkit pattern: group of related tools
const emailTools = {
send: tool(
async ({ to, subject, body }) => {
// Send email logic
return `Email sent to ${to}`;
},
{
name: "send_email",
description: "Send an email message",
schema: z.object({
to: z.string().email(),
subject: z.string(),
body: z.string(),
}),
}
),
read: tool(
async ({ folder, limit }) => {
// Read emails logic
return `Retrieved ${limit} emails from ${folder}`;
},
{
name: "read_emails",
description: "Read emails from a folder",
schema: z.object({
folder: z.string().default("inbox"),
limit: z.number().default(10),
}),
}
),
};
// Use all email tools
const agent = createAgent({
model: "gpt-4.1",
tools: Object.values(emailTools),
});Tool with Response Format Validation
import { tool } from "langchain";
import { z } from "zod";
const getUser = tool(
async ({ userId }) => {
const user = await db.users.findById(userId);
// Return structured data as JSON string
return JSON.stringify({
id: user.id,
name: user.name,
email: user.email,
created: user.createdAt.toISOString(),
});
},
{
name: "get_user",
description: "Get user information by ID",
schema: z.object({
userId: z.string().describe("User ID to lookup"),
}),
}
);Tool with Streaming Updates
import { tool } from "langchain";
import { z } from "zod";
const processLargeFile = tool(
async ({ filepath }, { runtime }) => {
const totalLines = 1000;
for (let i = 0; i < totalLines; i += 100) {
// Stream progress updates
await runtime.stream_writer.write({
type: "progress",
data: { processed: i, total: totalLines },
});
// Process chunk
await processChunk(i, i + 100);
}
return "Processing complete";
},
{
name: "process_file",
description: "Process a large file with progress updates",
schema: z.object({
filepath: z.string(),
}),
}
);Boundaries
What You CAN Configure
✅ Function logic: Any JavaScript/TypeScript code
✅ Parameters: Via Zod schema with descriptions
✅ Name and description: Guide model's tool selection
✅ Return value: Any serializable data (string, JSON, etc.)
✅ Async operations: Tools can be async
✅ Error handling: Throw errors or return error messages
What You CANNOT Configure
❌ When model calls tool: Model decides based on context
❌ Tool call order: Model determines execution flow
❌ Parameter values: Model generates based on schema
❌ Response format from model: Tool returns, model interprets
Gotchas
1. Poor Tool Descriptions
// ❌ Problem: Vague description
const badTool = tool(
async ({ data }) => "result",
{
name: "tool",
description: "Does something with data", // Too vague!
schema: z.object({ data: z.string() }),
}
);
// ✅ Solution: Specific, actionable description
const goodTool = tool(
async ({ query }) => searchDatabase(query),
{
name: "search_customers",
description: "Search customer database by name, email, or ID. Returns customer records with contact information. Use this when user asks about customer data.",
schema: z.object({
query: z.string().describe("Customer name, email, or ID to search for"),
}),
}
);2. Missing Parameter Descriptions
// ❌ Problem: No field descriptions
const badSchema = z.object({
query: z.string(),
limit: z.number(),
});
// ✅ Solution: Describe each field
const goodSchema = z.object({
query: z.string().describe("Search terms or keywords"),
limit: z.number().describe("Maximum results to return (1-100)"),
});3. Non-Serializable Return Values
// ❌ Problem: Returning complex objects
const badTool = tool(
async () => new Date(), // Date not serializable!
{ name: "get_time", description: "Get time", schema: z.object({}) }
);
// ✅ Solution: Return strings or JSON
const goodTool = tool(
async () => new Date().toISOString(),
{ name: "get_time", description: "Get current time", schema: z.object({}) }
);
// Or stringify objects
const dataTool = tool(
async () => JSON.stringify({ timestamp: Date.now(), user: getCurrentUser() }),
{ name: "get_data", description: "Get data", schema: z.object({}) }
);4. Tools Without Schemas
// ❌ Problem: No schema
const badTool = tool(
async (input: any) => "result",
{ name: "tool", description: "A tool" }
// Missing schema!
);
// ✅ Solution: Always provide schema
const goodTool = tool(
async ({ input }) => "result",
{
name: "tool",
description: "A tool",
schema: z.object({ input: z.string() }), // Clear schema
}
);5. Forgetting Async
// ❌ Problem: Not awaiting async operations
const badTool = tool(
({ url }) => {
fetch(url); // Not awaited!
return "done";
},
{
name: "fetch_url",
description: "Fetch URL",
schema: z.object({ url: z.string() }),
}
);
// ✅ Solution: Use async/await
const goodTool = tool(
async ({ url }) => {
const response = await fetch(url);
const data = await response.text();
return data;
},
{
name: "fetch_url",
description: "Fetch URL content",
schema: z.object({ url: z.string().url() }),
}
);6. Tool Names with Spaces or Special Chars
// ❌ Problem: Invalid tool name
const badTool = tool(
async () => "result",
{
name: "Get Weather!", // Special chars not allowed
description: "Get weather",
schema: z.object({}),
}
);
// ✅ Solution: Use snake_case or camelCase
const goodTool = tool(
async () => "result",
{
name: "get_weather", // Valid name
description: "Get weather",
schema: z.object({}),
}
);