Skip to content
On this page

Tool Interface

Overview

Tools are functions that agents can call to interact with the outside world. AgenTree provides a type-safe tool creation system using Zod schemas for parameter validation.

Tool Creation Function

tool()

The main function for creating tools with automatic schema generation and validation.

typescript
function tool<T extends z.ZodSchema>(options: ToolOptions<T>): Tool

ToolOptions Interface

typescript
interface ToolOptions<T extends z.ZodSchema> {
  name?: string;                              // Tool name (defaults to function name)
  description: string;                        // Description for LLM
  parameters: T;                              // Zod schema for parameters
  strict?: boolean;                           // Enable parameter validation (default: true)
  execute: (args: z.infer<T>, context?: any) => Promise<string> | string;
  errorFunction?: (context: any, error: Error) => string;
}

Parameters

ParameterTypeRequiredDefaultDescription
namestring❌Function nameTool identifier used by LLM
descriptionstringβœ…-Clear description of tool's purpose
parametersZodSchemaβœ…-Zod schema defining input parameters
strictboolean❌trueValidate parameters before execution
executeFunctionβœ…-Tool implementation function
errorFunctionFunction❌-Custom error handler

Basic Tool Creation

Simple Tool

typescript
import { tool } from 'agentree';
import { z } from 'zod';

const greetingTool = tool({
  name: 'greet_user',
  description: 'Generate a personalized greeting',
  parameters: z.object({
    name: z.string().describe('Name of the person to greet'),
    language: z.enum(['en', 'es', 'fr']).default('en').describe('Language for greeting')
  }),
  execute: ({ name, language }) => {
    const greetings = {
      en: `Hello, ${name}!`,
      es: `Β‘Hola, ${name}!`,
      fr: `Bonjour, ${name}!`
    };
    return greetings[language];
  }
});

Tool with Validation

typescript
const emailTool = tool({
  name: 'send_email',
  description: 'Send an email to a recipient',
  parameters: z.object({
    to: z.string().email().describe('Recipient email address'),
    subject: z.string().min(1).describe('Email subject'),
    body: z.string().min(1).describe('Email content'),
    priority: z.enum(['low', 'normal', 'high']).default('normal')
  }),
  strict: true,  // Strict validation (default)
  execute: async ({ to, subject, body, priority }) => {
    // Type safety: parameters are validated and typed
    const result = await sendEmail({ to, subject, body, priority });
    return `Email sent to ${to} with subject "${subject}"`;
  }
});

Parameter Schemas

Basic Types

typescript
const basicTypesTool = tool({
  name: 'process_data',
  description: 'Process various data types',
  parameters: z.object({
    // Strings
    text: z.string().describe('Text input'),
    requiredText: z.string().min(1).describe('Required non-empty text'),
    limitedText: z.string().max(100).describe('Text with max 100 characters'),
    regexText: z.string().regex(/^\d{3}-\d{2}-\d{4}$/).describe('SSN format'),
    
    // Numbers
    number: z.number().describe('Any number'),
    positiveNumber: z.number().positive().describe('Positive number only'),
    integerNumber: z.number().int().describe('Integer only'),
    rangeNumber: z.number().min(1).max(100).describe('Number between 1-100'),
    
    // Booleans
    flag: z.boolean().describe('True/false flag'),
    optionalFlag: z.boolean().optional().describe('Optional boolean'),
    
    // Enums
    choice: z.enum(['option1', 'option2', 'option3']).describe('Select one option'),
    status: z.enum(['pending', 'approved', 'rejected']).default('pending')
  }),
  execute: (params) => {
    // All parameters are properly typed
    return `Processed with ${params.choice} status: ${params.status}`;
  }
});

Complex Types

typescript
const complexTypesTool = tool({
  name: 'advanced_processor',
  description: 'Process complex data structures',
  parameters: z.object({
    // Arrays
    tags: z.array(z.string()).describe('Array of string tags'),
    numbers: z.array(z.number()).min(1).describe('At least one number'),
    limitedArray: z.array(z.string()).max(10).describe('Max 10 items'),
    
    // Objects
    user: z.object({
      name: z.string(),
      age: z.number().int().min(0),
      email: z.string().email().optional()
    }).describe('User information object'),
    
    // Nested objects
    config: z.object({
      database: z.object({
        host: z.string(),
        port: z.number().default(5432),
        ssl: z.boolean().default(true)
      }),
      cache: z.object({
        ttl: z.number().default(3600),
        maxSize: z.number().default(1000)
      }).optional()
    }).describe('Configuration object'),
    
    // Optional fields
    metadata: z.record(z.any()).optional().describe('Optional key-value metadata'),
    
    // Default values
    timeout: z.number().default(30).describe('Timeout in seconds'),
    retries: z.number().int().min(0).default(3).describe('Number of retries'),
    
    // Union types
    input: z.union([
      z.string().describe('Text input'),
      z.number().describe('Numeric input'),
      z.object({ type: z.string(), data: z.any() }).describe('Structured input')
    ]).describe('Flexible input type')
  }),
  execute: ({ user, config, input, timeout }) => {
    // All parameters are properly typed with TypeScript inference
    return `Processed user ${user.name} with timeout ${timeout}s`;
  }
});

Advanced Schemas

typescript
const advancedTool = tool({
  name: 'file_processor',
  description: 'Advanced file processing with validation',
  parameters: z.object({
    // File validation
    filePath: z.string()
      .regex(/\.(txt|csv|json|xml)$/, 'Must be txt, csv, json, or xml file')
      .describe('Path to file to process'),
    
    // Conditional validation
    operation: z.enum(['read', 'write', 'append']),
    content: z.string().optional(),
    
    // Custom validation
    dateRange: z.object({
      start: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
      end: z.string().regex(/^\d{4}-\d{2}-\d{2}$/)
    }).refine(
      (data) => new Date(data.start) <= new Date(data.end),
      { message: "Start date must be before end date" }
    ),
    
    // Transform data
    csvOptions: z.object({
      delimiter: z.string().default(','),
      hasHeader: z.boolean().default(true),
      encoding: z.string().default('utf8')
    }).optional()
  }).refine(
    (data) => {
      // Cross-field validation
      if (data.operation === 'write' && !data.content) {
        return false;
      }
      return true;
    },
    { message: "Content required for write operations" }
  ),
  execute: async ({ filePath, operation, content, dateRange, csvOptions }) => {
    // Implementation with fully typed parameters
    switch (operation) {
      case 'read':
        return await fs.readFile(filePath, 'utf8');
      case 'write':
        await fs.writeFile(filePath, content!, 'utf8');
        return `Written to ${filePath}`;
      case 'append':
        await fs.appendFile(filePath, content || '', 'utf8');
        return `Appended to ${filePath}`;
    }
  }
});

Tool Implementation Patterns

Async Operations

typescript
const apiTool = tool({
  name: 'fetch_data',
  description: 'Fetch data from external API',
  parameters: z.object({
    url: z.string().url(),
    method: z.enum(['GET', 'POST', 'PUT', 'DELETE']).default('GET'),
    headers: z.record(z.string()).optional(),
    body: z.any().optional(),
    timeout: z.number().default(10000)
  }),
  execute: async ({ url, method, headers, body, timeout }) => {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), timeout);
    
    try {
      const response = await fetch(url, {
        method,
        headers: { 'Content-Type': 'application/json', ...headers },
        body: body ? JSON.stringify(body) : undefined,
        signal: controller.signal
      });
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }
      
      const data = await response.json();
      return JSON.stringify(data, null, 2);
    } finally {
      clearTimeout(timeoutId);
    }
  }
});

File Operations

typescript
const fileSystemTool = tool({
  name: 'file_manager',
  description: 'Manage files and directories',
  parameters: z.object({
    operation: z.enum(['list', 'create', 'delete', 'move', 'copy']),
    path: z.string().describe('File or directory path'),
    destination: z.string().optional().describe('Destination path for move/copy'),
    content: z.string().optional().describe('Content for new files'),
    recursive: z.boolean().default(false).describe('Recursive operation for directories')
  }),
  execute: async ({ operation, path, destination, content, recursive }) => {
    switch (operation) {
      case 'list':
        const entries = await fs.readdir(path, { withFileTypes: true });
        return entries.map(entry => 
          `${entry.isDirectory() ? 'DIR' : 'FILE'}: ${entry.name}`
        ).join('\n');
        
      case 'create':
        if (content !== undefined) {
          await fs.writeFile(path, content, 'utf8');
          return `File created: ${path}`;
        } else {
          await fs.mkdir(path, { recursive });
          return `Directory created: ${path}`;
        }
        
      case 'delete':
        const stats = await fs.stat(path);
        if (stats.isDirectory()) {
          await fs.rmdir(path, { recursive });
        } else {
          await fs.unlink(path);
        }
        return `Deleted: ${path}`;
        
      case 'move':
        if (!destination) throw new Error('Destination required for move');
        await fs.rename(path, destination);
        return `Moved ${path} to ${destination}`;
        
      case 'copy':
        if (!destination) throw new Error('Destination required for copy');
        await fs.copyFile(path, destination);
        return `Copied ${path} to ${destination}`;
    }
  }
});

Database Operations

typescript
const databaseTool = tool({
  name: 'database_query',
  description: 'Execute database queries with connection pooling',
  parameters: z.object({
    query: z.string().describe('SQL query to execute'),
    params: z.array(z.any()).optional().describe('Query parameters'),
    database: z.string().default('default').describe('Database connection name'),
    timeout: z.number().default(30000).describe('Query timeout in ms')
  }),
  execute: async ({ query, params = [], database, timeout }) => {
    const connection = await getConnection(database);
    
    try {
      // Set query timeout
      const timeoutPromise = new Promise((_, reject) => {
        setTimeout(() => reject(new Error('Query timeout')), timeout);
      });
      
      const queryPromise = connection.query(query, params);
      const result = await Promise.race([queryPromise, timeoutPromise]);
      
      return JSON.stringify({
        rowCount: result.rowCount,
        rows: result.rows,
        executionTime: result.executionTime
      }, null, 2);
    } finally {
      connection.release();
    }
  }
});

Error Handling

Built-in Error Handling

typescript
const robustTool = tool({
  name: 'robust_operation',
  description: 'Tool with comprehensive error handling',
  parameters: z.object({
    input: z.string(),
    retries: z.number().int().min(0).default(3)
  }),
  execute: async ({ input, retries }) => {
    let lastError: Error | null = null;
    
    for (let attempt = 0; attempt <= retries; attempt++) {
      try {
        const result = await riskyOperation(input);
        return `Success on attempt ${attempt + 1}: ${result}`;
      } catch (error) {
        lastError = error as Error;
        if (attempt < retries) {
          // Wait before retry with exponential backoff
          await new Promise(resolve => 
            setTimeout(resolve, Math.pow(2, attempt) * 1000)
          );
        }
      }
    }
    
    throw new Error(`Failed after ${retries + 1} attempts: ${lastError?.message}`);
  }
});

Custom Error Functions

typescript
const apiToolWithErrorHandling = tool({
  name: 'resilient_api_call',
  description: 'API call with intelligent error handling',
  parameters: z.object({
    endpoint: z.string().url(),
    apiKey: z.string().optional()
  }),
  execute: async ({ endpoint, apiKey }) => {
    const response = await fetch(endpoint, {
      headers: apiKey ? { 'Authorization': `Bearer ${apiKey}` } : {}
    });
    
    if (!response.ok) {
      throw new Error(`HTTP_${response.status}`);
    }
    
    return await response.text();
  },
  errorFunction: (context, error) => {
    const message = error.message;
    
    if (message.includes('HTTP_401')) {
      return 'Authentication failed. Please check your API key and ensure it has the necessary permissions.';
    } else if (message.includes('HTTP_403')) {
      return 'Access forbidden. Your API key may not have permission to access this endpoint.';
    } else if (message.includes('HTTP_404')) {
      return 'Endpoint not found. Please verify the URL is correct.';
    } else if (message.includes('HTTP_429')) {
      return 'Rate limit exceeded. Please wait before making another request.';
    } else if (message.includes('HTTP_5')) {
      return 'Server error occurred. Please try again later.';
    } else if (message.includes('fetch')) {
      return 'Network error occurred. Please check your internet connection.';
    }
    
    return `API call failed: ${message}`;
  }
});

Tool Registration

Automatic Registration

Tools are automatically registered when passed to agents:

typescript
import { ToolRegistry } from 'agentree';

const myTool = tool({ /* ... */ });

// Automatic registration
const agent = new Agent({
  tools: [myTool],  // Tool is registered for child access
  // ...
});

// Check registration
console.log('Registered tools:', ToolRegistry.list());
console.log('Tool available:', ToolRegistry.has('my_tool'));

Manual Registration

typescript
// Manual registration for global access
ToolRegistry.register(myTool);

// Get tool by name
const retrievedTool = ToolRegistry.get('my_tool');

// Clear all tools (useful for testing)
ToolRegistry.clear();

Tool Name Resolution

typescript
// Tool name defaults to function name
const autoNamedTool = tool({
  description: 'Tool with automatic name',
  parameters: z.object({}),
  execute: function myToolName() {  // Name: 'myToolName'
    return 'result';
  }
});

// Explicit name override
const explicitNamedTool = tool({
  name: 'custom_name',  // Name: 'custom_name'
  description: 'Tool with explicit name',
  parameters: z.object({}),
  execute: () => 'result'
});

Tool Interface

Generated Tool Interface

The tool() function returns a Tool object with this interface:

typescript
interface Tool {
  name: string;                                    // Tool name
  description: string;                             // Tool description
  parameters: {                                    // JSON Schema parameters
    type: 'object';
    properties: Record<string, any>;
    required?: string[];
  };
  execute: (args: any, context?: any) => Promise<string> | string;
  errorFunction?: (context: any, error: Error) => string;
}

Tool Metadata for LLM

Tools are converted to this format for LLM consumption:

typescript
interface ToolMetadata {
  name: string;
  description: string;
  parameters: {
    type: 'object';
    properties: Record<string, any>;
    required?: string[];
  };
}

Advanced Tool Patterns

Contextual Tools

Tools that adapt behavior based on agent context:

typescript
const contextualTool = tool({
  name: 'smart_file_reader',
  description: 'Read files with format detection',
  parameters: z.object({
    filePath: z.string()
  }),
  execute: async ({ filePath }, context) => {
    // Access agent context if available
    const agentName = context?.agentName || 'unknown';
    const agentDepth = context?.agentDepth || 0;
    
    console.log(`Tool called by ${agentName} at depth ${agentDepth}`);
    
    const extension = path.extname(filePath);
    const content = await fs.readFile(filePath, 'utf8');
    
    switch (extension) {
      case '.json':
        return `JSON data: ${JSON.stringify(JSON.parse(content), null, 2)}`;
      case '.csv':
        return `CSV data (${content.split('\n').length} rows):\n${content}`;
      default:
        return `Text content (${content.length} chars):\n${content}`;
    }
  }
});

Tool Composition

Create tools that use other tools:

typescript
const compositeTool = tool({
  name: 'data_pipeline',
  description: 'Execute a complete data processing pipeline',
  parameters: z.object({
    inputFile: z.string(),
    outputFile: z.string(),
    transformations: z.array(z.string())
  }),
  execute: async ({ inputFile, outputFile, transformations }) => {
    // Use other tools programmatically
    let data = await readFileTool.execute({ path: inputFile });
    
    for (const transformation of transformations) {
      data = await transformationTool.execute({ 
        data, 
        operation: transformation 
      });
    }
    
    await writeFileTool.execute({ 
      path: outputFile, 
      content: data 
    });
    
    return `Pipeline completed: ${inputFile} -> ${outputFile}`;
  }
});

Dynamic Tool Generation

Generate tools programmatically:

typescript
const createDatabaseTool = (tableName: string) => {
  return tool({
    name: `query_${tableName}`,
    description: `Query the ${tableName} table`,
    parameters: z.object({
      where: z.string().optional().describe('WHERE clause'),
      limit: z.number().optional().describe('Result limit'),
      orderBy: z.string().optional().describe('ORDER BY clause')
    }),
    execute: async ({ where, limit, orderBy }) => {
      let query = `SELECT * FROM ${tableName}`;
      if (where) query += ` WHERE ${where}`;
      if (orderBy) query += ` ORDER BY ${orderBy}`;
      if (limit) query += ` LIMIT ${limit}`;
      
      const result = await database.query(query);
      return JSON.stringify(result.rows, null, 2);
    }
  });
};

// Generate tools for each table
const usersTool = createDatabaseTool('users');
const ordersTool = createDatabaseTool('orders');
const productsTool = createDatabaseTool('products');

Best Practices

1. Clear Tool Names and Descriptions

typescript
// Good: Descriptive and specific
const calculateMortgageTool = tool({
  name: 'calculate_mortgage_payment',
  description: 'Calculate monthly mortgage payment including principal, interest, taxes, and insurance',
  // ...
});

// Poor: Vague and generic
const calcTool = tool({
  name: 'calc',
  description: 'Calculate stuff',
  // ...
});

2. Comprehensive Parameter Validation

typescript
// Good: Detailed validation with helpful descriptions
const emailTool = tool({
  parameters: z.object({
    to: z.string()
      .email('Must be a valid email address')
      .describe('Recipient email address'),
    subject: z.string()
      .min(1, 'Subject cannot be empty')
      .max(100, 'Subject must be under 100 characters')
      .describe('Email subject line'),
    body: z.string()
      .min(1, 'Email body cannot be empty')
      .describe('Email content in plain text or HTML'),
    priority: z.enum(['low', 'normal', 'high'])
      .default('normal')
      .describe('Email priority level')
  }),
  // ...
});

3. Proper Error Handling

typescript
// Good: Comprehensive error handling
const fileTool = tool({
  name: 'secure_file_operation',
  execute: async ({ filePath, operation }) => {
    try {
      // Validate file path
      if (filePath.includes('..')) {
        throw new Error('Path traversal not allowed');
      }
      
      // Check permissions
      await fs.access(filePath, fs.constants.R_OK);
      
      // Perform operation
      return await performOperation(filePath, operation);
    } catch (error) {
      if (error.code === 'ENOENT') {
        throw new Error(`File not found: ${filePath}`);
      } else if (error.code === 'EACCES') {
        throw new Error(`Permission denied: ${filePath}`);
      }
      throw error;
    }
  },
  errorFunction: (context, error) => {
    // Provide user-friendly error messages
    return `File operation failed: ${error.message}. Please check the file path and permissions.`;
  }
});