Skip to main content

Overview

The defineTool() function creates a tool definition with full type inference. Tools are functions that agents can invoke during execution to perform actions like API calls, database queries, calculations, or any other programmatic task.

Signature

function defineTool<TData = unknown>(
  definition: ToolDefinition<TData>
): ToolDefinition<TData>

Parameters

definition
ToolDefinition<TData>
required
Tool definition object containing schema and execute function:

Returns

definition
ToolDefinition<TData>
The same tool definition with proper type inference

Type Parameters

TData
generic
default:"unknown"
Type of custom state data accessible via ctx.data in the execute function

Usage Examples

Basic Tool

import { defineTool } from '@agentlib/core'

const getCurrentTime = defineTool({
  schema: {
    name: 'getCurrentTime',
    description: 'Get the current time in a specific timezone',
    parameters: {
      type: 'object',
      properties: {
        timezone: {
          type: 'string',
          description: 'IANA timezone identifier (e.g., America/New_York)',
          default: 'UTC'
        }
      },
      required: []
    }
  },
  execute: async (args) => {
    const timezone = args.timezone as string || 'UTC'
    return new Date().toLocaleString('en-US', { timeZone: timezone })
  }
})

Tool with Multiple Parameters

const calculator = defineTool({
  schema: {
    name: 'calculate',
    description: 'Perform arithmetic operations on two numbers',
    parameters: {
      type: 'object',
      properties: {
        operation: {
          type: 'string',
          enum: ['add', 'subtract', 'multiply', 'divide'],
          description: 'The arithmetic operation to perform'
        },
        a: {
          type: 'number',
          description: 'First operand'
        },
        b: {
          type: 'number',
          description: 'Second operand'
        }
      },
      required: ['operation', 'a', 'b']
    }
  },
  execute: async (args) => {
    const { operation, a, b } = args as { operation: string; a: number; b: number }
    
    switch (operation) {
      case 'add': return a + b
      case 'subtract': return a - b
      case 'multiply': return a * b
      case 'divide':
        if (b === 0) throw new Error('Division by zero')
        return a / b
      default:
        throw new Error(`Unknown operation: ${operation}`)
    }
  }
})

Tool with External API Call

import fetch from 'node-fetch'

const searchWeb = defineTool({
  schema: {
    name: 'searchWeb',
    description: 'Search the web for information',
    parameters: {
      type: 'object',
      properties: {
        query: {
          type: 'string',
          description: 'Search query'
        },
        limit: {
          type: 'number',
          description: 'Maximum number of results',
          default: 5,
          minimum: 1,
          maximum: 10
        }
      },
      required: ['query']
    }
  },
  execute: async (args) => {
    const { query, limit = 5 } = args as { query: string; limit?: number }
    
    const response = await fetch(
      `https://api.search.example.com/search?q=${encodeURIComponent(query)}&limit=${limit}`,
      { headers: { 'Authorization': `Bearer ${process.env.SEARCH_API_KEY}` } }
    )
    
    if (!response.ok) {
      throw new Error(`Search API error: ${response.statusText}`)
    }
    
    const data = await response.json()
    return data.results
  }
})

Tool with Typed State Access

interface UserContext {
  userId: string
  preferences: {
    language: string
    timezone: string
  }
}

const getUserPreferences = defineTool<UserContext>({
  schema: {
    name: 'getUserPreferences',
    description: 'Retrieve user preferences from context',
    parameters: {
      type: 'object',
      properties: {},
      required: []
    }
  },
  execute: async (args, ctx) => {
    // ctx.data is typed as UserContext
    return {
      userId: ctx.data.userId,
      language: ctx.data.preferences.language,
      timezone: ctx.data.preferences.timezone
    }
  }
})

Tool that Emits Events

const processData = defineTool({
  schema: {
    name: 'processData',
    description: 'Process a large dataset',
    parameters: {
      type: 'object',
      properties: {
        datasetId: { type: 'string' }
      },
      required: ['datasetId']
    }
  },
  execute: async (args, ctx) => {
    const { datasetId } = args as { datasetId: string }
    
    ctx.emit('processing:started', { datasetId })
    
    // Simulate processing
    for (let i = 0; i < 100; i += 10) {
      await new Promise(resolve => setTimeout(resolve, 100))
      ctx.emit('processing:progress', { datasetId, progress: i })
    }
    
    ctx.emit('processing:completed', { datasetId })
    
    return { success: true, recordsProcessed: 1000 }
  }
})

Tool with Database Access

import { Pool } from 'pg'

const dbPool = new Pool({ connectionString: process.env.DATABASE_URL })

const queryDatabase = defineTool({
  schema: {
    name: 'queryDatabase',
    description: 'Execute a safe SELECT query on the database',
    parameters: {
      type: 'object',
      properties: {
        table: {
          type: 'string',
          enum: ['users', 'orders', 'products'],
          description: 'Table to query'
        },
        filters: {
          type: 'object',
          description: 'Key-value filters',
          additionalProperties: true
        },
        limit: {
          type: 'number',
          default: 10,
          maximum: 100
        }
      },
      required: ['table']
    }
  },
  execute: async (args) => {
    const { table, filters = {}, limit = 10 } = args as {
      table: string
      filters?: Record<string, any>
      limit?: number
    }
    
    const whereClauses = Object.entries(filters)
      .map(([key, _], idx) => `${key} = $${idx + 1}`)
      .join(' AND ')
    
    const query = `
      SELECT * FROM ${table}
      ${whereClauses ? `WHERE ${whereClauses}` : ''}
      LIMIT $${Object.keys(filters).length + 1}
    `
    
    const values = [...Object.values(filters), limit]
    const result = await dbPool.query(query, values)
    
    return result.rows
  }
})

Tool with Complex Return Type

interface WeatherData {
  location: string
  temperature: number
  conditions: string
  humidity: number
  wind: { speed: number; direction: string }
  forecast: Array<{ day: string; high: number; low: number }>
}

const getWeather = defineTool({
  schema: {
    name: 'getWeather',
    description: 'Get detailed weather information for a location',
    parameters: {
      type: 'object',
      properties: {
        location: {
          type: 'string',
          description: 'City name or coordinates'
        },
        includeForecast: {
          type: 'boolean',
          description: 'Include 5-day forecast',
          default: false
        }
      },
      required: ['location']
    }
  },
  execute: async (args): Promise<WeatherData> => {
    const { location, includeForecast = false } = args as {
      location: string
      includeForecast?: boolean
    }
    
    // Fetch from weather API
    const weatherData: WeatherData = {
      location,
      temperature: 22,
      conditions: 'Partly cloudy',
      humidity: 65,
      wind: { speed: 15, direction: 'NW' },
      forecast: includeForecast ? [
        { day: 'Monday', high: 24, low: 18 },
        { day: 'Tuesday', high: 26, low: 19 }
      ] : []
    }
    
    return weatherData
  }
})

Tool with Error Handling

const sendEmail = defineTool({
  schema: {
    name: 'sendEmail',
    description: 'Send an email to a recipient',
    parameters: {
      type: 'object',
      properties: {
        to: {
          type: 'string',
          format: 'email',
          description: 'Recipient email address'
        },
        subject: {
          type: 'string',
          description: 'Email subject'
        },
        body: {
          type: 'string',
          description: 'Email body content'
        }
      },
      required: ['to', 'subject', 'body']
    }
  },
  execute: async (args, ctx) => {
    const { to, subject, body } = args as {
      to: string
      subject: string
      body: string
    }
    
    try {
      // Validate email format
      const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
      if (!emailRegex.test(to)) {
        throw new Error(`Invalid email address: ${to}`)
      }
      
      // Send email via API
      ctx.emit('email:sending', { to, subject })
      
      // await emailService.send({ to, subject, body })
      
      ctx.emit('email:sent', { to, subject })
      
      return {
        success: true,
        messageId: 'msg-' + Date.now(),
        timestamp: new Date().toISOString()
      }
    } catch (error) {
      ctx.emit('email:error', { to, error })
      throw new Error(`Failed to send email: ${(error as Error).message}`)
    }
  }
})

Using Tools with Agents

import { createAgent, defineTool } from '@agentlib/core'
import { openai } from '@agentlib/openai'

const tools = [
  getCurrentTime,
  calculator,
  searchWeb,
  getWeather
]

const agent = createAgent({
  name: 'multi-tool-agent',
  model: openai(),
  tools,
  reasoning: 'react'
})

const result = await agent.run('What is the weather in Paris and what time is it there?')
console.log(result.output)

Parameter Schema Guidelines

The parameters field must be a valid JSON Schema object:
parameters: {
  type: 'object',          // Must be 'object'
  properties: {
    paramName: {
      type: 'string' | 'number' | 'boolean' | 'array' | 'object',
      description: 'Clear description for the LLM',
      // Optional constraints:
      enum: ['option1', 'option2'],        // For string/number
      minimum: 0,                          // For number
      maximum: 100,                        // For number
      minLength: 1,                        // For string
      maxLength: 500,                      // For string
      pattern: '^[a-z]+$',                // For string (regex)
      format: 'email' | 'uri' | 'date',   // For string
      items: { type: 'string' },          // For array
      default: 'default value'            // Default if not provided
    }
  },
  required: ['paramName'],  // Required parameters
  additionalProperties: false  // Reject extra properties
}

Best Practices

  1. Clear descriptions: LLMs use descriptions to decide when and how to use tools
  2. Type safety: Use TypeScript types for args in execute function
  3. Error handling: Throw descriptive errors for invalid inputs or failures
  4. Return serializable data: Ensure return values can be JSON-stringified
  5. Use constraints: Add min/max, enums, patterns to validate inputs
  6. Emit events: Use ctx.emit() for progress tracking and debugging
  7. Access context: Use ctx.data for user-specific state
  8. Keep tools focused: Each tool should do one thing well

See Also

Build docs developers (and LLMs) love