Skip to main content

Overview

Function tools allow you to wrap existing TypeScript functions and make them available to agents with minimal boilerplate. ADK-TS automatically generates function declarations from your function signatures and JSDoc comments.

Creating Function Tools

Using FunctionTool Class

The FunctionTool class wraps any TypeScript function:
import { FunctionTool } from '@iqai/adk';

/**
 * Calculate the sum of two numbers
 * @param a First number to add
 * @param b Second number to add
 */
function add(a: number, b: number) {
  return { result: a + b };
}

const addTool = new FunctionTool(add);

// Use with an agent
const agent = new AgentBuilder()
  .withTools([addTool])
  .buildLlm();

Using createFunctionTool Helper

The createFunctionTool() helper provides a more concise syntax:
import { createFunctionTool } from '@iqai/adk';

function multiply(a: number, b: number) {
  return { result: a * b };
}

const multiplyTool = createFunctionTool(multiply);

Function Signatures

Basic Functions

/**
 * Get the current timestamp
 */
function getTimestamp() {
  return { timestamp: Date.now() };
}

const timestampTool = new FunctionTool(getTimestamp);

Functions with Parameters

/**
 * Greet a user by name
 * @param name The user's name
 * @param formal Whether to use formal greeting
 */
function greet(name: string, formal: boolean = false) {
  const greeting = formal ? 'Good day' : 'Hello';
  return { message: `${greeting}, ${name}!` };
}

const greetTool = new FunctionTool(greet);

Async Functions

Async functions are automatically detected and handled:
/**
 * Fetch user data from an API
 * @param userId The user's ID
 */
async function fetchUser(userId: string) {
  const response = await fetch(`https://api.example.com/users/${userId}`);
  const data = await response.json();
  return { user: data };
}

const fetchUserTool = new FunctionTool(fetchUser);

Accessing ToolContext

Functions can access the ToolContext by including it as a parameter:
import type { ToolContext } from '@iqai/adk';

/**
 * Save a user preference
 * @param key Preference key
 * @param value Preference value
 */
function savePreference(
  key: string,
  value: string,
  toolContext: ToolContext  // Special parameter name
) {
  // Access session state
  toolContext.state.set(key, value);
  
  // Get session info
  const userId = toolContext.session.userId;
  
  return {
    success: true,
    userId,
    saved: { [key]: value }
  };
}

const savePrefTool = new FunctionTool(savePreference);
Parameters named toolContext or context are automatically recognized and excluded from the function declaration sent to the LLM.

JSDoc Documentation

Function tools extract information from JSDoc comments:
/**
 * Calculate the area of a rectangle
 * 
 * This function computes the area by multiplying width and height.
 * Both parameters must be positive numbers.
 * 
 * @param width The width of the rectangle in meters
 * @param height The height of the rectangle in meters
 * @returns Object containing the calculated area
 */
function calculateArea(width: number, height: number) {
  if (width <= 0 || height <= 0) {
    return { error: 'Width and height must be positive' };
  }
  
  return {
    area: width * height,
    unit: 'square meters'
  };
}

const areaTool = new FunctionTool(calculateArea);
The JSDoc description becomes the tool description that helps the LLM understand when to use the tool.

Configuration Options

Customize function tools with options:
const customTool = new FunctionTool(myFunction, {
  name: 'custom_name',  // Override function name
  description: 'Custom description',  // Override JSDoc description
  isLongRunning: true,  // Mark as long-running operation
  shouldRetryOnFailure: true,  // Enable retry logic
  maxRetryAttempts: 5,  // Number of retry attempts
  parameterTypes: {  // Override parameter types
    userId: 'string',
    count: 'number'
  }
});
name
string
Override the function name (defaults to function.name)
description
string
Override the function description (defaults to JSDoc)
isLongRunning
boolean
default:"false"
Whether the function is a long-running operation
shouldRetryOnFailure
boolean
default:"false"
Enable automatic retry on failure
maxRetryAttempts
number
default:"3"
Maximum retry attempts
parameterTypes
Record<string, string>
Explicit parameter type mappings

Type Conversion

Function tools automatically convert LLM arguments to the correct types:
function processOrder(
  orderId: number,     // Converted from string to number
  quantity: number,    // Converted from string to number
  express: boolean,    // Converted from string to boolean
  notes: string        // Kept as string
) {
  return {
    orderId,
    quantity,
    isExpress: express,
    notes
  };
}

const orderTool = new FunctionTool(processOrder);

// LLM might send: { orderId: "123", quantity: "5", express: "true" }
// Function receives: { orderId: 123, quantity: 5, express: true }

Error Handling

Function tools validate mandatory arguments and handle errors:
function divide(a: number, b: number) {
  if (b === 0) {
    return { error: 'Cannot divide by zero' };
  }
  
  return { result: a / b };
}

const divideTool = new FunctionTool(divide);

// If LLM forgets a parameter:
// Result: { error: "Invoking 'divide()' failed as the following mandatory input parameters are not present: b" }

Default Parameters

Default parameter values are supported:
/**
 * Format a date string
 * @param date Date to format
 * @param format Output format (default: 'YYYY-MM-DD')
 * @param timezone Timezone (default: 'UTC')
 */
function formatDate(
  date: string,
  format: string = 'YYYY-MM-DD',
  timezone: string = 'UTC'
) {
  // Only 'date' is mandatory
  // 'format' and 'timezone' are optional
  return {
    formatted: date,  // Simplified example
    format,
    timezone
  };
}

const dateTool = new FunctionTool(formatDate);

Complex Return Types

Functions can return any serializable object:
interface UserProfile {
  id: string;
  name: string;
  email: string;
  preferences: Record<string, any>;
  lastActive: Date;
}

/**
 * Retrieve user profile data
 * @param userId The user ID to look up
 */
async function getUserProfile(userId: string): Promise<UserProfile> {
  const user = await database.users.findById(userId);
  
  return {
    id: user.id,
    name: user.name,
    email: user.email,
    preferences: user.preferences,
    lastActive: user.lastActive
  };
}

const profileTool = new FunctionTool(getUserProfile);

Real-World Examples

Database Query Tool

import type { ToolContext } from '@iqai/adk';
import { FunctionTool } from '@iqai/adk';

/**
 * Search for products in the database
 * @param query Search query string
 * @param category Product category filter
 * @param maxResults Maximum number of results (default: 10)
 */
async function searchProducts(
  query: string,
  category: string = 'all',
  maxResults: number = 10,
  toolContext: ToolContext
) {
  // Log search to session state
  const searches = toolContext.state.get('product_searches', []);
  searches.push({ query, category, timestamp: Date.now() });
  toolContext.state.set('product_searches', searches);
  
  // Perform search
  const products = await database.products.search({
    query,
    category: category !== 'all' ? category : undefined,
    limit: maxResults
  });
  
  return {
    query,
    category,
    results: products.map(p => ({
      id: p.id,
      name: p.name,
      price: p.price,
      inStock: p.inventory > 0
    })),
    totalResults: products.length
  };
}

const searchTool = new FunctionTool(searchProducts, {
  shouldRetryOnFailure: true,
  maxRetryAttempts: 3
});

API Integration Tool

/**
 * Get current weather for a location
 * @param location City name or coordinates
 * @param units Temperature units (metric or imperial)
 */
async function getWeather(
  location: string,
  units: string = 'metric'
) {
  const apiKey = process.env.WEATHER_API_KEY;
  
  try {
    const response = await fetch(
      `https://api.weather.com/data?location=${encodeURIComponent(location)}&units=${units}&key=${apiKey}`
    );
    
    if (!response.ok) {
      return {
        error: `Weather API error: ${response.status}`,
        location
      };
    }
    
    const data = await response.json();
    
    return {
      location,
      temperature: data.temp,
      conditions: data.conditions,
      humidity: data.humidity,
      windSpeed: data.wind.speed,
      units
    };
  } catch (error) {
    return {
      error: error instanceof Error ? error.message : 'Unknown error',
      location
    };
  }
}

const weatherTool = new FunctionTool(getWeather, {
  shouldRetryOnFailure: true,
  maxRetryAttempts: 2
});

Stateful Counter Tool

/**
 * Increment a named counter in the session
 * @param counterName Name of the counter
 * @param amount Amount to increment by (default: 1)
 */
function incrementCounter(
  counterName: string,
  amount: number = 1,
  toolContext: ToolContext
) {
  const currentValue = toolContext.state.get(counterName, 0);
  const newValue = currentValue + amount;
  
  toolContext.state.set(counterName, newValue);
  
  return {
    counterName,
    previousValue: currentValue,
    newValue,
    incrementedBy: amount
  };
}

const counterTool = new FunctionTool(incrementCounter);

Comparison: FunctionTool vs createTool

FeatureFunctionToolcreateTool
Schema definitionAutomatic from functionExplicit Zod schema
Type safetyRuntimeCompile-time + runtime
ValidationBasicFull Zod validation
DocumentationJSDocZod descriptions
BoilerplateMinimalMinimal
Best forExisting functionsNew tools
// FunctionTool - wrap existing function
function existing(a: number, b: string) {
  return { a, b };
}
const tool1 = new FunctionTool(existing);

// createTool - define from scratch
const tool2 = createTool({
  name: 'new_tool',
  description: 'A new tool',
  schema: z.object({
    a: z.number(),
    b: z.string()
  }),
  fn: ({ a, b }) => ({ a, b })
});

Best Practices

Use Descriptive JSDoc

// Good: Clear, detailed documentation
/**
 * Send an email notification to a user
 * 
 * This function sends a templated email using the configured email service.
 * It validates the email address and handles delivery failures gracefully.
 * 
 * @param recipient Email address of the recipient
 * @param subject Email subject line
 * @param template Template name to use (welcome, notification, alert)
 * @param data Data to populate the template
 */
async function sendEmail(
  recipient: string,
  subject: string,
  template: string,
  data: Record<string, any>
) { /* ... */ }

// Avoid: Minimal or missing documentation
function send(r: string, s: string, t: string, d: any) { /* ... */ }

Return Structured Objects

// Good: Structured response
function processPayment(amount: number, currency: string) {
  return {
    success: true,
    transactionId: 'txn_123',
    amount,
    currency,
    timestamp: Date.now()
  };
}

// Avoid: Plain string
function processPayment(amount: number, currency: string) {
  return `Processed ${amount} ${currency}`;  // Hard to parse
}

Handle Errors as Return Values

// Good: Error as return value
function validateInput(input: string) {
  if (!input || input.length < 3) {
    return {
      error: 'Input must be at least 3 characters',
      valid: false
    };
  }
  
  return {
    valid: true,
    input
  };
}

// Avoid: Throwing exceptions
function validateInput(input: string) {
  if (!input || input.length < 3) {
    throw new Error('Input too short');  // Breaks agent flow
  }
  return input;
}

Next Steps

Creating Tools

Learn about createTool() and BaseTool

Built-in Tools

Explore pre-built tools

Build docs developers (and LLMs) love