or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

advanced

error-handling.mdtype-inference.md
glossary.mdindex.mdquick-reference.mdtask-index.md
tile.json

tools.mddocs/guides/

Tool Guide

This guide covers creating and using tools to give agents the ability to take actions.

Creating Tools

Basic Tool

import { tool } from "langchain";
import { z } from "zod";

const calculator = tool(
  async ({ expression }) => {
    try {
      return String(eval(expression));
    } catch (error) {
      return `Error: ${error.message}`;
    }
  },
  {
    name: "calculator",
    description: "Evaluate mathematical expressions. Use for arithmetic calculations.",
    schema: z.object({
      expression: z.string().describe("The mathematical expression to evaluate"),
    }),
  }
);

Tool with Complex Schema

import { tool } from "langchain";
import { z } from "zod";

const searchDatabase = tool(
  async ({ query, filters, limit }) => {
    const results = await database.search({
      query,
      filters,
      limit,
    });
    return JSON.stringify(results, null, 2);
  },
  {
    name: "search_database",
    description: "Search the database with optional filters and result limit",
    schema: z.object({
      query: z.string().describe("Search query string"),
      filters: z.object({
        category: z.string().optional(),
        dateFrom: z.string().optional(),
        dateTo: z.string().optional(),
      }).optional().describe("Optional filters"),
      limit: z.number().default(10).describe("Maximum number of results"),
    }),
  }
);

Tool with External API

import { tool } from "langchain";
import { z } from "zod";

const getWeather = tool(
  async ({ location, units }) => {
    const response = await fetch(
      `https://api.weather.com/${location}?units=${units}`
    );
    if (!response.ok) {
      return `Error: Failed to fetch weather data`;
    }
    const data = await response.json();
    return JSON.stringify(data);
  },
  {
    name: "get_weather",
    description: "Get current weather for a location",
    schema: z.object({
      location: z.string().describe("City name or zip code"),
      units: z.enum(["celsius", "fahrenheit"]).default("celsius"),
    }),
  }
);

Tool with Side Effects

import { tool } from "langchain";
import { z } from "zod";

const sendEmail = tool(
  async ({ to, subject, body }) => {
    try {
      await emailService.send({ to, subject, body });
      return `Email sent successfully to ${to}`;
    } catch (error) {
      return `Failed to send email: ${error.message}`;
    }
  },
  {
    name: "send_email",
    description: "Send an email to a recipient",
    schema: z.object({
      to: z.string().email().describe("Recipient email address"),
      subject: z.string().describe("Email subject"),
      body: z.string().describe("Email body content"),
    }),
  }
);

Tool with Error Handling

import { tool } from "langchain";
import { z } from "zod";

const apiCall = tool(
  async ({ endpoint, method, body }) => {
    try {
      const response = await fetch(endpoint, {
        method,
        headers: { "Content-Type": "application/json" },
        body: body ? JSON.stringify(body) : undefined,
      });

      if (!response.ok) {
        return `Error: HTTP ${response.status} - ${response.statusText}`;
      }

      const data = await response.json();
      return JSON.stringify(data);
    } catch (error) {
      return `Error: ${error.message}`;
    }
  },
  {
    name: "api_call",
    description: "Make an API call to a specified endpoint",
    schema: z.object({
      endpoint: z.string().url().describe("API endpoint URL"),
      method: z.enum(["GET", "POST", "PUT", "DELETE"]).default("GET"),
      body: z.any().optional().describe("Request body for POST/PUT"),
    }),
  }
);

Tool with Context Access

import { tool } from "langchain";
import { z } from "zod";

const personalizedGreeting = tool(
  async ({ name }, config) => {
    // Access runtime configuration
    const userId = config?.configurable?.user_id;
    const preferences = await loadUserPreferences(userId);

    return `Hello ${name}! ${preferences.greeting_style}`;
  },
  {
    name: "personalized_greeting",
    description: "Generate a personalized greeting",
    schema: z.object({
      name: z.string().describe("Name to greet"),
    }),
  }
);

Using Tools with Agents

Single Tool

import { createAgent, tool } from "langchain";
import { z } from "zod";

const calculator = tool(
  async ({ expression }) => String(eval(expression)),
  {
    name: "calculator",
    description: "Evaluate mathematical expressions",
    schema: z.object({
      expression: z.string(),
    }),
  }
);

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

const result = await agent.invoke({
  messages: [{ role: "user", content: "What is 25 * 4?" }],
});

Multiple Tools

import { createAgent, tool } from "langchain";
import { z } from "zod";

const add = tool(
  async ({ a, b }) => String(a + b),
  {
    name: "add",
    description: "Add two numbers",
    schema: z.object({ a: z.number(), b: z.number() }),
  }
);

const multiply = tool(
  async ({ a, b }) => String(a * b),
  {
    name: "multiply",
    description: "Multiply two numbers",
    schema: z.object({ a: z.number(), b: z.number() }),
  }
);

const divide = tool(
  async ({ a, b }) => {
    if (b === 0) return "Error: Division by zero";
    return String(a / b);
  },
  {
    name: "divide",
    description: "Divide two numbers",
    schema: z.object({ a: z.number(), b: z.number() }),
  }
);

const agent = createAgent({
  model: "openai:gpt-4o",
  tools: [add, multiply, divide],
});

const result = await agent.invoke({
  messages: [{ role: "user", content: "What is (10 + 5) * 3?" }],
});

Tool Collections

// Organize tools by category
const mathTools = [addTool, multiplyTool, divideTool];
const searchTools = [webSearchTool, databaseSearchTool];
const communicationTools = [sendEmailTool, sendSlackTool];

// Create agent with all tools
const agent = createAgent({
  model: "openai:gpt-4o",
  tools: [...mathTools, ...searchTools, ...communicationTools],
});

Tool Patterns

Async Long-Running Tools

import { tool } from "langchain";
import { z } from "zod";

const longTask = tool(
  async ({ taskId }) => {
    // Start background job
    const job = await jobQueue.submit(taskId);

    // Wait for completion
    const result = await job.wait({ timeout: 30000 });

    return `Task ${taskId} completed: ${result}`;
  },
  {
    name: "long_task",
    description: "Execute a long-running background task",
    schema: z.object({
      taskId: z.string().describe("Task identifier"),
    }),
  }
);

Tools with Validation

import { tool } from "langchain";
import { z } from "zod";

const createUser = tool(
  async ({ email, age, username }) => {
    // Additional runtime validation
    if (await userExists(email)) {
      return "Error: User with this email already exists";
    }

    const user = await database.createUser({ email, age, username });
    return `User created: ${user.id}`;
  },
  {
    name: "create_user",
    description: "Create a new user account",
    schema: z.object({
      email: z.string().email(),
      age: z.number().min(13).max(120),
      username: z.string().min(3).max(20).regex(/^[a-zA-Z0-9_]+$/),
    }),
  }
);

Tools with Rate Limiting

import { tool } from "langchain";
import { z } from "zod";
import { RateLimiter } from "limiter";

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

const apiCall = tool(
  async ({ endpoint }) => {
    // Check rate limit
    const allowed = await limiter.removeTokens(1);
    if (!allowed) {
      return "Error: Rate limit exceeded. Please try again later.";
    }

    const response = await fetch(endpoint);
    const data = await response.json();
    return JSON.stringify(data);
  },
  {
    name: "rate_limited_api",
    description: "Make rate-limited API call",
    schema: z.object({
      endpoint: z.string().url(),
    }),
  }
);

Tools with Caching

import { tool } from "langchain";
import { z } from "zod";

const cache = new Map();

const expensiveOperation = tool(
  async ({ input }) => {
    // Check cache
    if (cache.has(input)) {
      return cache.get(input);
    }

    // Perform expensive operation
    const result = await performExpensiveComputation(input);

    // Cache result
    cache.set(input, result);

    return result;
  },
  {
    name: "expensive_operation",
    description: "Perform cached expensive operation",
    schema: z.object({
      input: z.string(),
    }),
  }
);

Tools with Retry Logic

import { tool } from "langchain";
import { z } from "zod";

const resilientApiCall = tool(
  async ({ url }) => {
    const maxRetries = 3;
    let lastError;

    for (let i = 0; i < maxRetries; i++) {
      try {
        const response = await fetch(url);
        if (!response.ok) throw new Error(`HTTP ${response.status}`);
        const data = await response.json();
        return JSON.stringify(data);
      } catch (error) {
        lastError = error;
        if (i < maxRetries - 1) {
          await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
        }
      }
    }

    return `Error after ${maxRetries} attempts: ${lastError.message}`;
  },
  {
    name: "resilient_api_call",
    description: "Make API call with automatic retries",
    schema: z.object({
      url: z.string().url(),
    }),
  }
);

Alternative Tool APIs

DynamicTool (String Input)

import { DynamicTool } from "langchain";

const weatherTool = new DynamicTool({
  name: "get_weather",
  description: "Get weather for a location (pass location as string)",
  func: async (input: string) => {
    const response = await fetch(`https://api.weather.com/${input}`);
    const data = await response.json();
    return JSON.stringify(data);
  },
});

DynamicStructuredTool

import { DynamicStructuredTool } from "langchain";
import { z } from "zod";

const weatherTool = new DynamicStructuredTool({
  name: "get_weather",
  description: "Get weather for a location",
  schema: z.object({
    location: z.string().describe("City name"),
    units: z.enum(["celsius", "fahrenheit"]).default("celsius"),
  }),
  func: async ({ location, units }) => {
    const response = await fetch(
      `https://api.weather.com/${location}?units=${units}`
    );
    const data = await response.json();
    return JSON.stringify(data);
  },
});

Extending Tool Base Class

import { Tool } from "langchain";

class CustomTool extends Tool {
  name = "custom_tool";
  description = "A custom tool implementation";

  async _call(input: string): Promise<string> {
    // Implement tool logic
    return `Processed: ${input}`;
  }
}

const tool = new CustomTool();

Extending StructuredTool Base Class

import { StructuredTool } from "langchain";
import { z } from "zod";

class CustomStructuredTool extends StructuredTool {
  name = "custom_structured_tool";
  description = "A custom structured tool";
  schema = z.object({
    query: z.string(),
    limit: z.number().optional(),
  });

  async _call({ query, limit = 10 }): Promise<string> {
    // Implement tool logic
    const results = await search(query, limit);
    return JSON.stringify(results);
  }
}

const tool = new CustomStructuredTool();

Best Practices

Tool Naming

  • Use descriptive, action-oriented names: search_web, create_task, send_email
  • Use snake_case for consistency
  • Make names intuitive for LLMs
  • Avoid abbreviations unless universally understood

Tool Descriptions

  • Be specific about what the tool does
  • Mention key parameters and their purpose
  • Explain when the tool should be used
  • Keep descriptions concise (1-2 sentences)
  • Include examples if the usage is not obvious

Schema Design

  • Use .describe() on all fields to help the LLM
  • Provide sensible defaults where appropriate
  • Use z.enum() for constrained choices
  • Make optional parameters truly optional
  • Use validation (.email(), .url(), .min(), .max()) where appropriate

Error Handling

  • Return error messages as strings (don't throw)
  • Include helpful context in error messages
  • Validate inputs before expensive operations
  • Handle network failures gracefully
  • Provide actionable error messages

Return Values

  • Return structured data as JSON strings
  • Keep return values concise
  • Include only relevant information
  • Consider the LLM's context window
  • Format data for easy parsing by the LLM

Performance

  • Cache results when appropriate
  • Implement timeouts for external calls
  • Use rate limiting for API calls
  • Consider async/await for I/O operations
  • Batch operations when possible

Security

  • Validate and sanitize all inputs
  • Never expose sensitive data in tool output
  • Use environment variables for API keys
  • Implement proper authentication
  • Be cautious with tools that modify data
  • Consider using human-in-the-loop for dangerous operations

Testing

  • Test tools independently before using with agents
  • Verify error handling paths
  • Test with invalid inputs
  • Mock external dependencies
  • Test rate limiting and retries

See Tool API Reference for complete API documentation.