Skip to main content
AG-Kit provides a powerful framework for creating custom tools that extend your AI agents’ capabilities. Build specialized tools for your specific use cases with full type safety and seamless integration.

Tool Creation Patterns

Function Tools

Create tools from TypeScript/JavaScript functions:

Toolkits

Organize related tools into reusable toolkits:

Quick Start

Basic Tool Creation

Create a simple custom tool:
import { tool } from '@ag-kit/tools';
import { z } from 'zod';

const weatherTool = tool(
  async ({ city, units }) => {
    try {
      // Fetch weather data
      const response = await fetch(
        `https://api.weather.com/v1/current?city=${city}&units=${units}`
      );

      if (!response.ok) {
        return {
          success: false,
          error: `Weather API error: ${response.statusText}`,
          error_type: 'network'
        };
      }

      const data = await response.json();

      return {
        success: true,
        data: {
          city,
          temperature: data.temperature,
          condition: data.condition,
          humidity: data.humidity,
          units
        }
      };
    } catch (error) {
      return {
        success: false,
        error: error.message,
        error_type: 'execution'
      };
    }
  },
  {
    name: 'get_weather',
    description: 'Get current weather information for a city',
    schema: z.object({
      city: z.string().describe('City name'),
      units: z.enum(['celsius', 'fahrenheit']).default('celsius')
    })
  }
);

Tool Integration

Use custom tools with agents:
import { Agent } from '@ag-kit/core';
import { OpenAIProvider } from '@ag-kit/providers/openai';

const provider = new OpenAIProvider({
  apiKey: process.env.OPENAI_API_KEY!,
  model: 'gpt-4'
});

const agent = new Agent({
  name: 'weather-agent',
  model: provider,
  tools: [weatherTool],
  instructions: 'You can check weather information for any city.'
});

const response = await agent.run({
  input: 'What\'s the weather like in San Francisco?'
});

Tool Architecture

BaseTool Interface

All tools implement the standardized interface:
interface BaseTool<TInput = any, TOutput = any> {
  name: string;
  description: string;
  schema: ZodSchema<TInput>;
  invoke(input: TInput): Promise<ToolResult<TOutput>>;
}

Tool Result Structure

Consistent result format across all tools:
interface ToolResult<T = any> {
  success: boolean;
  data?: T;
  error?: string;
  error_type?: "validation" | "execution" | "permission" | "network";
  executionTime?: number;
}

Schema Validation

Use Zod for input validation:
import { z } from 'zod';

const complexSchema = z.object({
  // Required fields
  id: z.string().uuid(),
  name: z.string().min(1).max(100),

  // Optional fields with defaults
  priority: z.enum(['low', 'medium', 'high']).default('medium'),
  tags: z.array(z.string()).default([]),

  // Nested objects
  metadata: z.object({
    created: z.date(),
    author: z.string()
  }).optional(),

  // Conditional validation
  config: z.union([
    z.object({ type: z.literal('simple'), value: z.string() }),
    z.object({ type: z.literal('complex'), settings: z.record(z.any()) })
  ])
});

Toolkit Architecture

Custom Toolkits

Create custom toolkits to organize related tools:
import { BaseToolkit, tool } from '@ag-kit/tools';
import { z } from 'zod';

class WeatherToolkit extends BaseToolkit {
  constructor() {
    super({
      name: 'weather-toolkit',
      description: 'Comprehensive weather information toolkit'
    });
  }

  protected async onInitialize(): Promise<void> {
    // Add weather tools
    this.addTool(this.createCurrentWeatherTool());
    this.addTool(this.createForecastTool());
    this.addTool(this.createHistoricalTool());
  }

  private createCurrentWeatherTool() {
    return tool(
      async ({ city, units }) => {
        // Implementation
        return { success: true, data: { temperature: 22, condition: 'sunny' } };
      },
      {
        name: 'current_weather',
        description: 'Get current weather conditions',
        schema: z.object({
          city: z.string(),
          units: z.enum(['celsius', 'fahrenheit']).default('celsius')
        })
      }
    );
  }

  private createForecastTool() {
    return tool(
      async ({ city, days }) => {
        // Implementation
        return { success: true, data: { forecast: [] } };
      },
      {
        name: 'weather_forecast',
        description: 'Get weather forecast',
        schema: z.object({
          city: z.string(),
          days: z.number().min(1).max(14).default(5)
        })
      }
    );
  }

  private createHistoricalTool() {
    return tool(
      async ({ city, date }) => {
        // Implementation
        return { success: true, data: { historical: {} } };
      },
      {
        name: 'historical_weather',
        description: 'Get historical weather data',
        schema: z.object({
          city: z.string(),
          date: z.string().describe('Date in YYYY-MM-DD format')
        })
      }
    );
  }
}

Using Custom Toolkits

Initialize and use custom toolkits with agents:
// Create and initialize toolkit
const weatherToolkit = new WeatherToolkit();
await weatherToolkit.initialize();

// Get all tools from toolkit
const weatherTools = weatherToolkit.getTools();

Toolkit Management

Use the toolkit manager for centralized toolkit management:
import { ToolkitManager } from '@ag-kit/tools';

const toolkitManager = new ToolkitManager();
// Register toolkit
toolkitManager.register(weatherToolkit);

// Get toolkit by name
const toolkit = toolkitManager.getToolkit('weather-toolkit');

// Get all tools from all registered toolkits
const allTools = toolkitManager.getAllTools();

// Find specific tool across all toolkits
const currentWeatherTools = toolkitManager.findTool('current_weather');

// Initialize all registered toolkits
await toolkitManager.initializeAll();

// Cleanup all toolkits
await toolkitManager.destroyAll();

Toolkit Events

Listen to toolkit lifecycle events:
weatherToolkit.addEventListener((event) => {
  switch (event.type) {
    case 'toolkit_initialized':
      console.log(`Toolkit ${event.toolkit.name} initialized`);
      break;
    case 'tool_added':
      console.log(`Tool ${event.tool.name} added`);
      break;
    case 'tool_executed':
      console.log(`Tool ${event.toolName} executed`);
      break;
    case 'toolkit_destroyed':
      console.log(`Toolkit ${event.toolkit.name} destroyed`);
      break;
  }
});

Tool Testing

Unit Testing

Test custom tools thoroughly:
import { describe, it, expect } from 'jest/globals';

describe('weatherTool', () => {
  it('should return weather data for valid city', async () => {
    const result = await weatherTool.invoke({
      city: 'San Francisco',
      units: 'celsius'
    });

    expect(result.success).toBe(true);
    expect(result.data).toHaveProperty('temperature');
    expect(result.data.city).toBe('San Francisco');
    expect(result.data.units).toBe('celsius');
  });

  it('should handle invalid city gracefully', async () => {
    const result = await weatherTool.invoke({
      city: 'InvalidCity123',
      units: 'celsius'
    });

    expect(result.success).toBe(false);
    expect(result.error_type).toBe('network');
  });

  it('should validate input schema', async () => {
    const result = await weatherTool.invoke({
      city: '', // Invalid empty city
      units: 'celsius'
    });

    expect(result.success).toBe(false);
    expect(result.error_type).toBe('validation');
  });
});

Toolkit Testing

Test custom toolkits comprehensively:
import { describe, it, expect, beforeEach, afterEach } from 'jest/globals';

describe('WeatherToolkit', () => {
  let weatherToolkit: WeatherToolkit;

  beforeEach(async () => {
    weatherToolkit = new WeatherToolkit();
    await weatherToolkit.initialize();
  });

  afterEach(async () => {
    await weatherToolkit.destroy();
  });

  it('should initialize with correct tools', () => {
    const toolNames = weatherToolkit.getToolNames();
    expect(toolNames).toContain('current_weather');
    expect(toolNames).toContain('weather_forecast');
    expect(toolNames).toContain('historical_weather');
    expect(weatherToolkit.getTools()).toHaveLength(3);
  });

  it('should execute tools correctly', async () => {
    const result = await weatherToolkit.invokeTool('current_weather', {
      city: 'San Francisco',
      units: 'celsius'
    });

    expect(result.success).toBe(true);
    expect(result.data).toHaveProperty('temperature');
  });

  it('should handle batch tool execution', async () => {
    const results = await weatherToolkit.invokeTools([
      { toolName: 'current_weather', input: { city: 'Tokyo' } },
      { toolName: 'weather_forecast', input: { city: 'Tokyo', days: 3 } }
    ]);

    expect(results).toHaveLength(2);
    expect(results.every(r => r.success)).toBe(true);
  });

  it('should validate toolkit integrity', () => {
    const validation = weatherToolkit.validate();
    expect(validation.valid).toBe(true);
    expect(validation.errors).toHaveLength(0);
  });

  it('should emit events correctly', async () => {
    const events: any[] = [];

    const newToolkit = new WeatherToolkit();
    newToolkit.addEventListener((event) => {
      events.push(event);
    });

    await newToolkit.initialize();
    await newToolkit.invokeTool('current_weather', { city: 'London' });
    await newToolkit.destroy();

    expect(events.some(e => e.type === 'toolkit_initialized')).toBe(true);
    expect(events.some(e => e.type === 'tool_executed')).toBe(true);
    expect(events.some(e => e.type === 'toolkit_destroyed')).toBe(true);
  });
});

Performance Optimization

Caching

Implement caching for expensive operations:
const cache = new Map();

const cachedWeatherTool = tool(
  async ({ city, units }) => {
    const cacheKey = `${city}-${units}`;
    const cached = cache.get(cacheKey);

    if (cached && Date.now() - cached.timestamp < 300000) { // 5 minutes
      return {
        success: true,
        data: { ...cached.data, cached: true }
      };
    }

    const result = await fetchWeatherData(city, units);

    if (result.success) {
      cache.set(cacheKey, {
        data: result.data,
        timestamp: Date.now()
      });
    }

    return result;
  },
  {
    name: 'get_weather_cached',
    description: 'Get weather with caching',
    schema: z.object({
      city: z.string(),
      units: z.enum(['celsius', 'fahrenheit']).default('celsius')
    })
  }
);

Connection Pooling

Reuse connections for better performance:
class DatabaseConnectionPool {
  private pool: any[] = [];

  async getConnection() {
    if (this.pool.length > 0) {
      return this.pool.pop();
    }
    return await createNewConnection();
  }

  releaseConnection(connection: any) {
    if (this.pool.length < 10) {
      this.pool.push(connection);
    } else {
      connection.close();
    }
  }
}

const pool = new DatabaseConnectionPool();

const optimizedDbTool = tool(
  async ({ query, parameters }) => {
    const connection = await pool.getConnection();

    try {
      const result = await connection.query(query, parameters);
      return { success: true, data: result };
    } finally {
      pool.releaseConnection(connection);
    }
  },
  {
    name: 'optimized_db_query',
    description: 'Database query with connection pooling',
    schema: z.object({
      query: z.string(),
      parameters: z.array(z.any()).default([])
    })
  }
);

Next Steps