Zod Function Tools
This example demonstrates how to create type-safe function tools using Zod schemas, enabling robust parameter validation and function-only operation modes.
Overview
The Zod function tools example shows how to:
- Define function tools with comprehensive Zod schemas
- Create type-safe parameter validation
- Run function-only operations without AI providers
- Implement multiple tool types in a single provider
- Handle complex data structures and enums
Source Code
Location: Package-owned examples under packages/agents/examples/
Key Concepts
1. Advanced Zod Schema Definition
typescript
import { z } from 'zod';
const tools = {
// Mathematical operations with strict typing
add: {
name: 'add',
description: 'Adds two numbers and returns the result.',
parameters: z.object({
a: z.number().describe('First number'),
b: z.number().describe('Second number'),
}),
handler: async (params) => {
const { a, b } = params;
console.log(`add function called: ${a} + ${b}`);
return { result: a + b };
},
},
// Complex enum-based tool with optional parameters
getWeather: {
name: 'getWeather',
description: 'Returns weather information for a city.',
parameters: z.object({
location: z.enum(['Seoul', 'Busan', 'Jeju']).describe('City name to check weather'),
unit: z
.enum(['celsius', 'fahrenheit'])
.optional()
.default('celsius')
.describe('Temperature unit'),
}),
handler: async (params) => {
const { location, unit } = params;
console.log(`getWeather function called: ${location}, ${unit}`);
// Mock weather data (replace with real API calls)
const weatherData = {
Seoul: { temperature: 22, condition: 'Clear', humidity: 65 },
Busan: { temperature: 24, condition: 'Partly Cloudy', humidity: 70 },
Jeju: { temperature: 26, condition: 'Cloudy', humidity: 75 },
};
const data = weatherData[location];
const temp =
unit === 'fahrenheit' ? Math.round((data.temperature * 9) / 5 + 32) : data.temperature;
return {
temperature: temp,
unit: unit === 'celsius' ? 'C' : 'F',
condition: data.condition,
humidity: data.humidity,
};
},
},
};2. Function-Only Provider Setup
typescript
import { createZodFunctionTool } from '@robota-sdk/agent-core';
import { Robota } from '@robota-sdk/agent-core';
// Create tools using createZodFunctionTool
const addTool = createZodFunctionTool(
'add',
'Add two numbers together',
addSchema,
async (params) => {
const result = params.a + params.b;
console.log(`add function called: ${params.a} + ${params.b}`);
return { result };
},
);
const weatherTool = createZodFunctionTool(
'getWeather',
'Get weather information for a city',
weatherSchema,
async (params) => {
console.log(`getWeather function called: ${params.city}, ${params.unit}`);
// Mock weather data
const weatherData = {
Seoul: { temp: 22, condition: 'Clear', humidity: 65 },
Busan: { temp: 25, condition: 'Sunny', humidity: 70 },
Jeju: { temp: 26, condition: 'Cloudy', humidity: 75 },
};
const data = weatherData[params.city] || { temp: 20, condition: 'Unknown', humidity: 60 };
const temp = params.unit === 'fahrenheit' ? (data.temp * 9) / 5 + 32 : data.temp;
const unit = params.unit === 'fahrenheit' ? '°F' : '°C';
return {
city: params.city,
temperature: `${temp}${unit}`,
condition: data.condition,
humidity: `${data.humidity}%`,
};
},
);
// Create Robota instance with tools
const robota = new Robota({
name: 'ToolAgent',
aiProviders: [openaiProvider],
defaultModel: {
provider: 'openai',
model: 'gpt-4',
systemMessage: 'You are an AI assistant that processes user requests using tools.',
},
tools: [addTool, weatherTool],
});Running the Example
Ensure setup is complete (see Setup Guide)
Run a package-owned example (from repo root):
bashnpx tsx packages/agents/examples/tool-calling.ts
Expected Output
Zod Function Tool Provider example started...
User: Hello!
Robot: Hello! I'm an AI assistant that can help you with calculations and weather information. I can add numbers together and provide weather information for Seoul, Busan, or Jeju. How can I help you today?
User: Please add 5 and 7.
add function called: 5 + 7
Robot: I'll add 5 and 7 for you. The result is 12.
User: How's the weather in Seoul right now?
getWeather function called: Seoul, celsius
Robot: Here's the current weather information for Seoul:
- Temperature: 22°C
- Condition: Clear
- Humidity: 65%
User: Tell me the weather in Jeju in Fahrenheit
getWeather function called: Jeju, fahrenheit
Robot: Here's the weather information for Jeju in Fahrenheit:
- Temperature: 79°F
- Condition: Cloudy
- Humidity: 75%
Zod Function Tool Provider example completed!Advanced Zod Patterns
1. Complex Data Structures
typescript
const dataAnalysisTool = {
name: 'analyzeData',
description: 'Analyzes complex datasets',
parameters: z.object({
dataset: z
.array(
z.object({
id: z.string(),
value: z.number(),
category: z.enum(['A', 'B', 'C']),
timestamp: z.string().datetime(),
}),
)
.min(1)
.max(1000),
analysisType: z.enum(['summary', 'trends', 'outliers']),
options: z
.object({
includeStats: z.boolean().default(true),
precision: z.number().min(0).max(10).default(2),
})
.optional(),
}),
handler: async ({ dataset, analysisType, options = {} }) => {
const { includeStats = true, precision = 2 } = options;
switch (analysisType) {
case 'summary':
const total = dataset.reduce((sum, item) => sum + item.value, 0);
const average = total / dataset.length;
return {
type: 'summary',
count: dataset.length,
total: Number(total.toFixed(precision)),
average: Number(average.toFixed(precision)),
...(includeStats && {
categories: dataset.reduce((acc, item) => {
acc[item.category] = (acc[item.category] || 0) + 1;
return acc;
}, {}),
}),
};
case 'trends':
// Implement trend analysis
return { type: 'trends', message: 'Trend analysis completed' };
case 'outliers':
// Implement outlier detection
return { type: 'outliers', message: 'Outlier detection completed' };
}
},
};2. Validation with Custom Logic
typescript
const validatedCalculatorTool = {
name: 'validateCalculate',
description: 'Calculator with comprehensive validation',
parameters: z
.object({
operation: z.enum(['add', 'subtract', 'multiply', 'divide', 'power']),
operands: z
.array(z.number())
.min(2, 'At least 2 numbers required')
.max(10, 'Maximum 10 numbers allowed')
.refine(
(numbers) => numbers.every((n) => Number.isFinite(n)),
'All numbers must be finite',
),
precision: z.number().min(0).max(15).default(2),
})
.refine((data) => {
if (
data.operation === 'divide' &&
data.operands.includes(0) &&
data.operands.indexOf(0) > 0
) {
return false; // Division by zero
}
return true;
}, 'Division by zero is not allowed'),
handler: async ({ operation, operands, precision }) => {
let result: number;
switch (operation) {
case 'add':
result = operands.reduce((sum, num) => sum + num, 0);
break;
case 'subtract':
result = operands.reduce((diff, num, index) => (index === 0 ? num : diff - num));
break;
case 'multiply':
result = operands.reduce((product, num) => product * num, 1);
break;
case 'divide':
result = operands.reduce((quotient, num, index) => (index === 0 ? num : quotient / num));
break;
case 'power':
result = Math.pow(operands[0], operands[1]);
break;
}
return {
operation,
operands,
result: Number(result.toFixed(precision)),
precision,
};
},
};3. File Processing Tool
typescript
const fileProcessorTool = {
name: 'processFile',
description: 'Processes file-like data structures',
parameters: z.object({
fileInfo: z.object({
name: z.string().min(1),
extension: z.enum(['.txt', '.json', '.csv', '.md']),
size: z.number().positive(),
content: z.string(),
}),
operations: z
.array(
z.enum([
'count_lines',
'count_words',
'count_characters',
'extract_metadata',
'validate_format',
]),
)
.min(1),
outputFormat: z.enum(['summary', 'detailed', 'json']).default('summary'),
}),
handler: async ({ fileInfo, operations, outputFormat }) => {
const results = {};
for (const operation of operations) {
switch (operation) {
case 'count_lines':
results['lineCount'] = fileInfo.content.split('\n').length;
break;
case 'count_words':
results['wordCount'] = fileInfo.content
.split(/\s+/)
.filter((word) => word.length > 0).length;
break;
case 'count_characters':
results['characterCount'] = fileInfo.content.length;
break;
case 'extract_metadata':
results['metadata'] = {
fileName: fileInfo.name,
extension: fileInfo.extension,
fileSize: fileInfo.size,
};
break;
case 'validate_format':
results['isValid'] = fileInfo.content.length === fileInfo.size;
break;
}
}
return {
file: fileInfo.name,
operations: operations,
results: results,
format: outputFormat,
};
},
};Function-Only Architecture Benefits
1. No AI Provider Required
typescript
// Can run without any AI provider setup
const functionOnlyRobota = new Robota({
tools: [addTool, weatherTool],
systemPrompt: 'Function-only mode',
});
// Direct function execution
const result = await functionOnlyRobota.run('Add 10 and 20');2. Deterministic Behavior
typescript
// Predictable, rule-based responses
const ruleBased = {
name: 'processCommand',
parameters: z.object({
command: z.enum(['status', 'help', 'version']),
args: z.array(z.string()).optional(),
}),
handler: async ({ command, args = [] }) => {
switch (command) {
case 'status':
return { status: 'operational', timestamp: new Date().toISOString() };
case 'help':
return { help: 'Available commands: status, help, version' };
case 'version':
return { version: '1.0.0', build: '20241201' };
}
},
};3. Performance Benefits
- Faster execution (no AI API calls)
- Predictable latency
- No token costs
- Offline capability
Testing and Validation
1. Schema Testing
typescript
import { z } from 'zod';
// Test schema validation
const testSchema = z.object({
value: z.number().positive(),
name: z.string().min(1),
});
// Valid input
const validData = { value: 42, name: 'test' };
const result = testSchema.safeParse(validData);
console.log(result.success); // true
// Invalid input
const invalidData = { value: -1, name: '' };
const errorResult = testSchema.safeParse(invalidData);
console.log(errorResult.success); // false
console.log(errorResult.error.issues); // Detailed error info2. Handler Testing
typescript
async function testTool() {
const tool = tools.add;
// Test normal operation
const result1 = await tool.handler({ a: 5, b: 3 });
console.assert(result1.result === 8);
// Test edge cases
const result2 = await tool.handler({ a: 0, b: 0 });
console.assert(result2.result === 0);
console.log('All tests passed!');
}Best Practices
1. Schema Design
typescript
// Good: Descriptive and well-constrained
z.object({
temperature: z
.number()
.min(-273.15, 'Temperature cannot be below absolute zero')
.max(1000, 'Temperature too high')
.describe('Temperature in Celsius'),
unit: z.enum(['C', 'F', 'K']).describe('Temperature unit'),
});
// Avoid: Overly permissive
z.object({
temp: z.any(),
unit: z.string(),
});2. Error Handling
typescript
const robustHandler = async (params) => {
try {
// Validate business logic
if (params.amount <= 0) {
return {
success: false,
error: 'Amount must be positive',
code: 'INVALID_AMOUNT',
};
}
// Process
const result = await processData(params);
return {
success: true,
data: result,
};
} catch (error) {
return {
success: false,
error: error.message,
code: 'PROCESSING_ERROR',
};
}
};3. Documentation
typescript
const wellDocumentedTool = {
name: 'calculateInterest',
description: 'Calculates compound interest for financial planning',
parameters: z.object({
principal: z
.number()
.positive('Principal amount must be positive')
.describe('Initial investment amount in dollars'),
rate: z.number().min(0).max(1).describe('Annual interest rate as decimal (e.g., 0.05 for 5%)'),
time: z
.number()
.positive('Time period must be positive')
.describe('Investment period in years'),
compoundFrequency: z
.number()
.int()
.positive()
.default(12)
.describe('Number of times interest compounds per year'),
}),
handler: async ({ principal, rate, time, compoundFrequency }) => {
const amount = principal * Math.pow(1 + rate / compoundFrequency, compoundFrequency * time);
const interest = amount - principal;
return {
principal,
finalAmount: Number(amount.toFixed(2)),
interestEarned: Number(interest.toFixed(2)),
rate: rate * 100, // Convert to percentage for display
years: time,
};
},
};Next Steps
After mastering Zod function tools, explore:
- Custom Function Providers - Building custom tool providers
- AI with Tools - Combining with AI providers
- MCP Integration - Model Context Protocol tools
Troubleshooting
Schema Validation Errors
typescript
// Debug schema issues
const result = schema.safeParse(data);
if (!result.success) {
console.log('Validation errors:', result.error.format());
}Type Safety Issues
typescript
// Ensure proper typing
type ToolParams = z.infer<typeof toolSchema>;
const handler = async (params: ToolParams) => {
// params is fully typed
};Performance Optimization
- Use
.optional()and.default()for better UX - Implement caching for expensive operations
- Consider lazy validation for large datasets