Using createTool()
ThecreateTool() function is the recommended way to create custom tools. It provides automatic schema generation from Zod schemas and handles all the boilerplate for you.
Basic Tool Creation
import { createTool } from '@iqai/adk';
import { z } from 'zod';
const calculatorTool = createTool({
name: 'calculator',
description: 'Performs basic arithmetic operations',
schema: z.object({
operation: z.enum(['add', 'subtract', 'multiply', 'divide']),
a: z.number().describe('First number'),
b: z.number().describe('Second number')
}),
fn: ({ operation, a, b }) => {
switch (operation) {
case 'add': return { result: a + b };
case 'subtract': return { result: a - b };
case 'multiply': return { result: a * b };
case 'divide': return { result: b !== 0 ? a / b : 'Cannot divide by zero' };
}
}
});
Tool Without Parameters
For tools that don’t need parameters, you can omit the schema:const timestampTool = createTool({
name: 'get_timestamp',
description: 'Returns the current timestamp in milliseconds',
fn: () => ({ timestamp: Date.now() })
});
Async Tools
Both sync and async functions are supported:const fetchDataTool = createTool({
name: 'fetch_data',
description: 'Fetch data from an API',
schema: z.object({
url: z.string().url().describe('API endpoint URL')
}),
fn: async ({ url }) => {
const response = await fetch(url);
const data = await response.json();
return { data, status: response.status };
}
});
Using ToolContext
Access session state, memory, and other runtime information:const saveSettingTool = createTool({
name: 'save_setting',
description: 'Save a user setting to the session',
schema: z.object({
key: z.string().describe('Setting name'),
value: z.string().describe('Setting value')
}),
fn: ({ key, value }, context) => {
// Access and modify session state
context.state.set(key, value);
// Get session information
const userId = context.session.userId;
const sessionId = context.session.id;
return {
success: true,
userId,
sessionId,
message: `Saved ${key} = ${value}`
};
}
});
Complex Schema Example
const createUserTool = createTool({
name: 'create_user',
description: 'Create a new user in the system',
schema: z.object({
username: z.string().min(3).max(20).describe('Username (3-20 characters)'),
email: z.string().email().describe('Valid email address'),
age: z.number().int().min(13).describe('User age (must be 13 or older)'),
role: z.enum(['user', 'admin', 'moderator']).default('user'),
preferences: z.object({
theme: z.enum(['light', 'dark']).default('light'),
notifications: z.boolean().default(true)
}).optional().describe('User preferences')
}),
fn: async (user) => {
// Zod automatically validates the input
// Type is inferred: { username: string, email: string, age: number, ... }
// Save to database
const userId = await database.users.create(user);
return {
success: true,
userId,
message: `User ${user.username} created successfully`
};
}
});
Retry Configuration
const resilientApiTool = createTool({
name: 'resilient_api',
description: 'Call external API with automatic retries',
shouldRetryOnFailure: true,
maxRetryAttempts: 5,
schema: z.object({
endpoint: z.string()
}),
fn: async ({ endpoint }) => {
const response = await fetch(endpoint);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
}
});
Extending BaseTool
For advanced use cases, extend theBaseTool class directly:
Basic BaseTool Extension
import { BaseTool, ToolContext, Type } from '@iqai/adk';
import type { FunctionDeclaration } from '@iqai/adk';
class WeatherTool extends BaseTool {
private apiKey: string;
constructor(apiKey: string) {
super({
name: 'get_weather',
description: 'Get current weather for a location',
isLongRunning: false,
shouldRetryOnFailure: true,
maxRetryAttempts: 3
});
this.apiKey = apiKey;
}
getDeclaration(): FunctionDeclaration {
return {
name: this.name,
description: this.description,
parameters: {
type: Type.OBJECT,
properties: {
location: {
type: Type.STRING,
description: 'City name or coordinates'
},
units: {
type: Type.STRING,
description: 'Temperature units',
enum: ['celsius', 'fahrenheit'],
default: 'celsius'
}
},
required: ['location']
}
};
}
async runAsync(
args: { location: string; units?: string },
context: ToolContext
): Promise<any> {
const { location, units = 'celsius' } = args;
try {
const response = await fetch(
`https://api.weather.com/data?location=${location}&units=${units}&key=${this.apiKey}`
);
if (!response.ok) {
return {
error: `Weather API returned ${response.status}`,
location
};
}
const data = await response.json();
// Store in session state
context.state.set('last_weather_check', {
location,
timestamp: Date.now()
});
return {
location,
temperature: data.temp,
conditions: data.conditions,
humidity: data.humidity,
units
};
} catch (error) {
return {
error: error instanceof Error ? error.message : 'Unknown error',
location
};
}
}
}
// Usage
const weatherTool = new WeatherTool(process.env.WEATHER_API_KEY!);
Long-Running Operations
For operations that take significant time:class DataProcessingTool extends BaseTool {
constructor() {
super({
name: 'process_large_dataset',
description: 'Process large datasets asynchronously',
isLongRunning: true, // Mark as long-running
shouldRetryOnFailure: false
});
}
getDeclaration(): FunctionDeclaration {
return {
name: this.name,
description: this.description,
parameters: {
type: Type.OBJECT,
properties: {
datasetId: {
type: Type.STRING,
description: 'ID of the dataset to process'
},
operation: {
type: Type.STRING,
description: 'Processing operation to perform',
enum: ['transform', 'aggregate', 'filter']
}
},
required: ['datasetId', 'operation']
}
};
}
async runAsync(
args: { datasetId: string; operation: string },
context: ToolContext
): Promise<any> {
const { datasetId, operation } = args;
// Start async processing
const jobId = await this.startProcessingJob(datasetId, operation);
// Return immediately with resource ID
return {
jobId,
status: 'processing',
message: 'Job started. Use check_job_status to monitor progress.'
};
}
private async startProcessingJob(
datasetId: string,
operation: string
): Promise<string> {
// Implementation
return 'job-' + Date.now();
}
}
Custom Validation
class ValidatedTool extends BaseTool {
constructor() {
super({
name: 'validated_operation',
description: 'Operation with custom validation'
});
}
getDeclaration(): FunctionDeclaration {
return {
name: this.name,
description: this.description,
parameters: {
type: Type.OBJECT,
properties: {
email: { type: Type.STRING, description: 'Email address' },
amount: { type: Type.NUMBER, description: 'Transaction amount' }
},
required: ['email', 'amount']
}
};
}
validateArguments(args: Record<string, any>): boolean {
// Call parent validation first
if (!super.validateArguments(args)) {
return false;
}
// Custom validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(args.email)) {
console.error('Invalid email format');
return false;
}
if (args.amount <= 0 || args.amount > 10000) {
console.error('Amount must be between 0 and 10000');
return false;
}
return true;
}
async runAsync(
args: { email: string; amount: number },
context: ToolContext
): Promise<any> {
// Arguments are pre-validated
return { success: true, ...args };
}
}
Stateful Tools
class SessionCounterTool extends BaseTool {
constructor() {
super({
name: 'increment_counter',
description: 'Increment a session counter'
});
}
getDeclaration(): FunctionDeclaration {
return {
name: this.name,
description: this.description,
parameters: {
type: Type.OBJECT,
properties: {
counterName: {
type: Type.STRING,
description: 'Name of the counter to increment'
},
incrementBy: {
type: Type.INTEGER,
description: 'Amount to increment by',
default: 1
}
},
required: ['counterName']
}
};
}
async runAsync(
args: { counterName: string; incrementBy?: number },
context: ToolContext
): Promise<any> {
const { counterName, incrementBy = 1 } = args;
// Get current value from session state
const currentValue = context.state.get(counterName, 0);
const newValue = currentValue + incrementBy;
// Update state
context.state.set(counterName, newValue);
// Get all counters
const allCounters = context.state.getAll();
return {
counterName,
previousValue: currentValue,
newValue,
allCounters
};
}
}
Tool Configuration Options
When creating tools, you can configure:Tool name (alphanumeric and underscores only)
Clear description of what the tool does (minimum 3 characters)
Whether the tool returns immediately with a resource ID
Enable automatic retry logic for transient failures
Maximum number of retry attempts
Best Practices
Naming Conventions
// Good: Clear, descriptive names
const getUserProfile = createTool({ name: 'get_user_profile', ... });
const sendEmail = createTool({ name: 'send_email', ... });
const calculateTotal = createTool({ name: 'calculate_total', ... });
// Avoid: Vague or unclear names
const process = createTool({ name: 'process', ... }); // Too vague
const doStuff = createTool({ name: 'do_stuff', ... }); // Not descriptive
Schema Descriptions
Use Zod’s.describe() to help the LLM understand parameters:
const tool = createTool({
name: 'book_flight',
description: 'Book a flight reservation',
schema: z.object({
departure: z.string().describe('Departure airport code (e.g., LAX, JFK)'),
arrival: z.string().describe('Arrival airport code (e.g., LAX, JFK)'),
date: z.string().describe('Departure date in YYYY-MM-DD format'),
passengers: z.number().int().min(1).max(9).describe('Number of passengers (1-9)')
}),
fn: async (args) => { /* ... */ }
});
Error Handling
Return errors as part of the result object:const tool = createTool({
name: 'api_call',
description: 'Call external API',
schema: z.object({ url: z.string() }),
fn: async ({ url }) => {
try {
const response = await fetch(url);
if (!response.ok) {
return {
error: `HTTP ${response.status}: ${response.statusText}`,
url
};
}
const data = await response.json();
return { success: true, data };
} catch (error) {
return {
error: error instanceof Error ? error.message : 'Unknown error',
url
};
}
}
});
Using Memory and Artifacts
const memoryTool = createTool({
name: 'search_past_conversations',
description: 'Search through past conversation history',
schema: z.object({
query: z.string().describe('Search query')
}),
fn: async ({ query }, context) => {
// Search memory
const memories = await context.searchMemory(query);
// List artifacts
const artifacts = await context.listArtifacts();
return {
memories: memories.map(m => ({
content: m.content,
relevance: m.score
})),
artifacts,
query
};
}
});
Next Steps
Built-in Tools
Explore all available built-in tools
Function Tools
Learn about automatic function wrapping