TypeScript framework for building LLM-powered applications with agents, tools, middleware, and model interoperability
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Complete API reference for creating and using tools in LangChain agents.
/**
* Create a structured tool from a function and schema
*
* @template T - The type of the input parameter, inferred from the schema
* @param func - Function to execute when tool is called. Receives validated input matching the schema and optional config.
* Should return a string or promise resolving to a string with the tool's result.
* Function signature: (input: T, config?: ToolConfig) => any | Promise<any>
* @param fields - Tool configuration object
* @param fields.name - Tool name used by LLM to identify and call the tool. Use descriptive, action-oriented names
* in snake_case (e.g., "search_web", "create_task"). Required.
* @param fields.description - Description explaining what the tool does and when to use it. This helps the LLM decide
* when to call the tool. Be specific and mention key parameters. Required.
* @param fields.schema - Zod schema defining and validating the tool's input structure. Use .describe() on schema fields
* to help the LLM understand each parameter. Required.
* @param fields.responseFormat - Optional format for tool responses. "content" returns just the content string (default),
* "content_and_artifact" returns both content and structured artifact data.
* @returns Structured tool instance that can be passed to createAgent() in the tools array
*
* @example
* ```typescript
* const calculator = tool(
* async ({ expression }) => String(eval(expression)),
* {
* name: "calculator",
* description: "Evaluate mathematical expressions for arithmetic calculations",
* schema: z.object({
* expression: z.string().describe("Mathematical expression to evaluate (e.g., '2 + 2', '10 * 5')"),
* }),
* }
* );
* ```
*/
function tool<T = any>(
func: (input: T, config?: ToolConfig) => any | Promise<any>,
fields: {
name: string;
description: string;
schema: ZodType<T>;
responseFormat?: "content" | "content_and_artifact";
}
): StructuredTool<T>;
/**
* Configuration object passed to tool functions during execution
*/
interface ToolConfig {
/**
* Configurable options from agent invocation (e.g., thread_id, user_id)
*/
configurable?: Record<string, any>;
/**
* Metadata for this tool call (for logging, tracing)
*/
metadata?: Record<string, any>;
/**
* Tags for categorizing this tool call
*/
tags?: string[];
/**
* Maximum recursion depth for tool calls
*/
recursionLimit?: number;
/**
* Custom name for this tool run (for tracing)
*/
runName?: string;
}/**
* Tool runtime information
*/
type ToolRuntime = {
name: string;
description: string;
schema: ZodType<any>;
};/**
* Base tool class
*/
abstract class Tool {
/**
* Tool name - used by LLM to identify the tool
*/
name: string;
/**
* Tool description - used by LLM to understand when to use the tool
*/
description: string;
/**
* Execute the tool with string input
* @param arg - String input argument
* @param callbacks - Optional callbacks
* @param tags - Optional tags for tracing
* @param metadata - Optional metadata
* @returns Promise resolving to string result
*/
abstract _call(
arg: string,
callbacks?: Callbacks,
tags?: string[],
metadata?: Record<string, any>
): Promise<string>;
/**
* Call the tool
* @param arg - Input argument
* @param callbacks - Optional callbacks
* @param tags - Optional tags
* @param metadata - Optional metadata
* @returns Promise resolving to result
*/
call(
arg: string | ToolCall,
callbacks?: Callbacks,
tags?: string[],
metadata?: Record<string, any>
): Promise<string>;
}/**
* Dynamically created tool with string input
*/
class DynamicTool extends Tool {
/**
* Create a dynamic tool
* @param fields - Tool configuration
*/
constructor(fields: {
name: string;
description: string;
func: (input: string, config?: ToolConfig) => Promise<string>;
returnDirect?: boolean;
verbose?: boolean;
});
/**
* The function to execute
*/
func: (input: string, config?: ToolConfig) => Promise<string>;
/**
* Whether to return the tool output directly to user
*/
returnDirect: boolean;
}/**
* Tool with structured input schema
*/
abstract class StructuredTool<T = any> extends Tool {
/**
* Zod schema for input validation
*/
schema: ZodType<T>;
/**
* Execute the tool with typed input
* @param arg - Typed input argument matching schema
* @param config - Optional tool configuration
* @returns Promise resolving to string result
*/
protected abstract _call(arg: T, config?: ToolConfig): Promise<string>;
/**
* Get input schema as JSON Schema
* @returns JSON Schema representation
*/
get inputSchema(): Record<string, any>;
}/**
* Dynamically created structured tool
*/
class DynamicStructuredTool<T = any> extends StructuredTool<T> {
/**
* Create a dynamic structured tool
* @param fields - Tool configuration with schema
*/
constructor(fields: {
name: string;
description: string;
schema: ZodType<T>;
func: (input: T, config?: ToolConfig) => Promise<string>;
returnDirect?: boolean;
verbose?: boolean;
});
/**
* The function to execute
*/
func: (input: T, config?: ToolConfig) => Promise<string>;
/**
* Whether to return the tool output directly to user
*/
returnDirect: boolean;
}/**
* Information about a tool call made by the model
*/
interface ToolCall {
/**
* Unique identifier for this tool call
*/
id: string;
/**
* Name of the tool being called
*/
name: string;
/**
* Arguments passed to the tool
*/
args: Record<string, any>;
}Naming Conventions:
search_web, create_task)search, create, update, delete, getsearch_web not search, send_email not sendtool1, helper, utilsDescription Guidelines:
Schema Design:
.describe() on every field to guide the LLM.optional().default(value)z.enum(["option1", "option2"]).min(), .max(), .email(), .url(), etc.Return Value Guidelines:
import { tool } from "langchain";
import { z } from "zod";
const resilientApiTool = tool(
async ({ endpoint, maxRetries = 3 }) => {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(endpoint);
const data = await response.json();
return JSON.stringify(data);
} catch (error) {
if (i === maxRetries - 1) {
return `Error after ${maxRetries} retries: ${error.message}`;
}
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
},
{
name: "resilient_api_call",
description: "Call an API endpoint with automatic retry on failure",
schema: z.object({
endpoint: z.string().url().describe("The API endpoint URL to call"),
maxRetries: z.number().default(3).describe("Maximum number of retry attempts"),
}),
}
);import { tool } from "langchain";
import { z } from "zod";
const validatedSearchTool = tool(
async ({ query, filters }) => {
// Input is already validated by Zod schema
// Additional business logic validation
if (query.length < 3) {
return "Error: Query must be at least 3 characters";
}
if (filters && filters.dateFrom && filters.dateTo) {
const from = new Date(filters.dateFrom);
const to = new Date(filters.dateTo);
if (from > to) {
return "Error: dateFrom must be before dateTo";
}
}
// Perform search
const results = await performSearch(query, filters);
return JSON.stringify(results, null, 2);
},
{
name: "validated_search",
description: "Search with comprehensive validation",
schema: z.object({
query: z.string().min(1).describe("Search query"),
filters: z.object({
category: z.string().optional(),
dateFrom: z.string().optional(),
dateTo: z.string().optional(),
}).optional(),
}),
}
);import { tool } from "langchain";
import { z } from "zod";
const databaseUpdateTool = tool(
async ({ userId, updates }, config) => {
// Access configurable from agent invocation
const { thread_id } = config?.configurable || {};
try {
// Perform database update
await db.users.update(userId, updates);
// Log the action
await auditLog.create({
action: "user_update",
userId,
updates,
threadId: thread_id,
timestamp: new Date().toISOString(),
});
return `Successfully updated user ${userId}`;
} catch (error) {
// Return error message (don't throw)
return `Error updating user: ${error.message}`;
}
},
{
name: "update_user",
description: "Update user profile information in the database",
schema: z.object({
userId: z.string().describe("User ID to update"),
updates: z.object({
name: z.string().optional(),
email: z.string().email().optional(),
preferences: z.record(z.any()).optional(),
}).describe("Fields to update"),
}),
}
);import { tool, type Tool } from "langchain";
import { z } from "zod";
/**
* Factory for creating CRUD tools for a resource
*/
function createCRUDTools<T>(
resourceName: string,
schema: z.ZodType<T>,
api: {
create: (data: T) => Promise<T>;
read: (id: string) => Promise<T | null>;
update: (id: string, data: Partial<T>) => Promise<T>;
delete: (id: string) => Promise<void>;
}
): Tool[] {
return [
tool(
async (data) => {
const created = await api.create(data);
return JSON.stringify(created);
},
{
name: `create_${resourceName}`,
description: `Create a new ${resourceName}`,
schema: schema,
}
),
tool(
async ({ id }) => {
const item = await api.read(id);
return item ? JSON.stringify(item) : `${resourceName} not found`;
},
{
name: `get_${resourceName}`,
description: `Get a ${resourceName} by ID`,
schema: z.object({ id: z.string() }),
}
),
tool(
async ({ id, updates }) => {
const updated = await api.update(id, updates);
return JSON.stringify(updated);
},
{
name: `update_${resourceName}`,
description: `Update a ${resourceName}`,
schema: z.object({
id: z.string(),
updates: schema.partial(),
}),
}
),
tool(
async ({ id }) => {
await api.delete(id);
return `${resourceName} deleted successfully`;
},
{
name: `delete_${resourceName}`,
description: `Delete a ${resourceName}`,
schema: z.object({ id: z.string() }),
}
),
];
}
// Usage
const taskTools = createCRUDTools("task", TaskSchema, taskApi);
const agent = createAgent({ model: "openai:gpt-4o", tools: taskTools });import { tool } from "langchain";
import { z } from "zod";
const streamingTool = tool(
async ({ query }) => {
// For long-running operations, return progress updates
const chunks = [];
chunks.push(`Starting search for: ${query}`);
// Perform operation in stages
const stage1 = await performStage1(query);
chunks.push(`Stage 1 complete: Found ${stage1.count} items`);
const stage2 = await performStage2(stage1.results);
chunks.push(`Stage 2 complete: Processed ${stage2.count} items`);
const final = await performStage3(stage2.results);
chunks.push(`Final results:\n${JSON.stringify(final, null, 2)}`);
// Return all chunks joined
return chunks.join("\n\n");
},
{
name: "streaming_search",
description: "Perform multi-stage search with progress updates",
schema: z.object({
query: z.string(),
}),
}
);Caching:
import { tool } from "langchain";
import { z } from "zod";
const cache = new Map();
const cachedTool = tool(
async ({ query }) => {
// Check cache first
if (cache.has(query)) {
return `[CACHED] ${cache.get(query)}`;
}
// Expensive operation
const result = await expensiveOperation(query);
// Cache result
cache.set(query, result);
return result;
},
{
name: "cached_search",
description: "Search with result caching",
schema: z.object({
query: z.string(),
}),
}
);Batching:
import { tool } from "langchain";
import { z } from "zod";
// Batch multiple requests together
let batchQueue: Array<{ query: string; resolve: (value: string) => void }> = [];
let batchTimeout: NodeJS.Timeout | null = null;
const batchedTool = tool(
async ({ query }) => {
return new Promise<string>((resolve) => {
batchQueue.push({ query, resolve });
if (batchTimeout) clearTimeout(batchTimeout);
batchTimeout = setTimeout(async () => {
const batch = [...batchQueue];
batchQueue = [];
// Execute all queries in one batch
const results = await executeBatch(batch.map(b => b.query));
// Resolve all promises
batch.forEach((item, i) => {
item.resolve(results[i]);
});
}, 100); // Wait 100ms to collect more requests
});
},
{
name: "batched_search",
description: "Search with automatic batching for efficiency",
schema: z.object({
query: z.string(),
}),
}
);Graceful Degradation:
import { tool } from "langchain";
import { z } from "zod";
const resilientTool = tool(
async ({ query, fallbackEnabled = true }) => {
try {
// Try primary method
return await primarySearch(query);
} catch (primaryError) {
if (!fallbackEnabled) {
return `Error: ${primaryError.message}`;
}
try {
// Try fallback method
return await fallbackSearch(query);
} catch (fallbackError) {
// Return informative error
return `Error: Primary and fallback methods failed.\nPrimary: ${primaryError.message}\nFallback: ${fallbackError.message}`;
}
}
},
{
name: "resilient_search",
description: "Search with automatic fallback on failure",
schema: z.object({
query: z.string(),
fallbackEnabled: z.boolean().default(true),
}),
}
);Timeout Protection:
import { tool } from "langchain";
import { z } from "zod";
const timeoutTool = tool(
async ({ query, timeoutMs = 5000 }) => {
const timeoutPromise = new Promise<string>((_, reject) =>
setTimeout(() => reject(new Error("Operation timed out")), timeoutMs)
);
const operationPromise = performOperation(query);
try {
return await Promise.race([operationPromise, timeoutPromise]);
} catch (error) {
return `Error: ${error.message}`;
}
},
{
name: "timeout_protected_operation",
description: "Perform operation with timeout protection",
schema: z.object({
query: z.string(),
timeoutMs: z.number().default(5000),
}),
}
);Input Sanitization:
import { tool } from "langchain";
import { z } from "zod";
const secureTool = tool(
async ({ query }) => {
// Sanitize input
const sanitized = query
.replace(/<script>/gi, "")
.replace(/javascript:/gi, "")
.trim();
// Validate sanitized input
if (sanitized !== query) {
return "Error: Input contains potentially malicious content";
}
// Safe to proceed
return await performSearch(sanitized);
},
{
name: "secure_search",
description: "Search with input sanitization",
schema: z.object({
query: z.string().max(1000),
}),
}
);Permission Checking:
import { tool } from "langchain";
import { z } from "zod";
const permissionCheckTool = tool(
async ({ userId, action }, config) => {
// Get user context from config
const currentUserId = config?.configurable?.user_id;
// Check permission
if (userId !== currentUserId) {
const hasPermission = await checkPermission(currentUserId, "manage_users");
if (!hasPermission) {
return "Error: Permission denied";
}
}
// Perform action
return await performAction(userId, action);
},
{
name: "user_action",
description: "Perform action with permission checking",
schema: z.object({
userId: z.string(),
action: z.string(),
}),
}
);docs