Lesson 3.2: Defining Tools
Duration: 60 minutes
Learning Objectives
By the end of this lesson, you will be able to:
- Create tool definitions using JSON Schema
- Write effective tool descriptions
- Define parameters with proper types and constraints
- Use Zod for type-safe tool schemas
- Implement tools for both OpenAI and Anthropic
Tool Definition Structure
A tool definition tells the AI model:
- Name: A unique identifier for the function
- Description: What the function does and when to use it
- Parameters: What inputs the function accepts
The model uses this information to decide when to call the tool and what arguments to provide.
OpenAI Tool Format
OpenAI uses this structure for tool definitions:
import OpenAI from 'openai';
const tool: OpenAI.ChatCompletionTool = {
type: 'function',
function: {
name: 'get_weather',
description: 'Get the current weather for a location',
parameters: {
type: 'object',
properties: {
location: {
type: 'string',
description: "The city name, e.g., 'London' or 'Tokyo, Japan'",
},
units: {
type: 'string',
enum: ['celsius', 'fahrenheit'],
description: 'Temperature units',
},
},
required: ['location'],
},
},
};
Key elements:
type: "function"indicates this is a function toolnamemust be a valid identifier (letters, numbers, underscores)descriptionhelps the model understand when to use itparametersfollows JSON Schema specification
Anthropic Tool Format
Anthropic uses a similar but slightly different structure:
import Anthropic from '@anthropic-ai/sdk';
const tool: Anthropic.Tool = {
name: 'get_weather',
description: 'Get the current weather for a location',
input_schema: {
type: 'object',
properties: {
location: {
type: 'string',
description: "The city name, e.g., 'London' or 'Tokyo, Japan'",
},
units: {
type: 'string',
enum: ['celsius', 'fahrenheit'],
description: 'Temperature units',
},
},
required: ['location'],
},
};
The main difference is input_schema instead of parameters, and no outer type: "function" wrapper.
JSON Schema Basics
Tool parameters use JSON Schema. Here are the most common types:
String
{
type: "string",
description: "User's email address",
}
Number
{
type: "number",
description: "Product price in USD",
}
Integer
{
type: "integer",
description: "Quantity to order",
minimum: 1,
maximum: 100,
}
Boolean
{
type: "boolean",
description: "Whether to include tax",
}
Enum (Fixed Options)
{
type: "string",
enum: ["low", "medium", "high"],
description: "Priority level",
}
Array
{
type: "array",
items: { type: "string" },
description: "List of tags",
}
Object (Nested)
{
type: "object",
properties: {
street: { type: "string" },
city: { type: "string" },
zip: { type: "string" },
},
required: ["city"],
}
Writing Effective Descriptions
Good descriptions help the model choose the right tool. Follow these guidelines:
Tool Description
// Bad - too vague
description: 'Gets weather';
// Good - explains what and when
description: 'Get the current weather conditions including temperature, humidity, and forecast for a specific city. Use this when the user asks about weather, temperature, or climate conditions.';
Parameter Description
// Bad - just repeats the name
location: {
description: 'The location';
}
// Good - provides format and examples
location: {
description: "City name with optional country, e.g., 'Paris', 'Tokyo, Japan', 'New York, NY, USA'";
}
Complete Tool Examples
Calculator Tool
const calculatorTool: OpenAI.ChatCompletionTool = {
type: 'function',
function: {
name: 'calculate',
description:
'Perform mathematical calculations. Use this for any arithmetic operations, ' +
'percentages, or mathematical expressions the user needs computed.',
parameters: {
type: 'object',
properties: {
expression: {
type: 'string',
description:
"Mathematical expression to evaluate, e.g., '2 + 2', '15% of 200', 'sqrt(144)'",
},
},
required: ['expression'],
},
},
};
Search Tool
const searchTool: OpenAI.ChatCompletionTool = {
type: 'function',
function: {
name: 'search_web',
description:
'Search the web for current information. Use this when the user asks about ' +
'recent events, current facts, or anything that requires up-to-date information.',
parameters: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'The search query',
},
num_results: {
type: 'integer',
description: 'Number of results to return (1-10)',
minimum: 1,
maximum: 10,
},
},
required: ['query'],
},
},
};
Database Query Tool
const databaseTool: OpenAI.ChatCompletionTool = {
type: 'function',
function: {
name: 'query_products',
description:
'Search the product database. Use this when the user asks about products, ' +
'inventory, prices, or product availability.',
parameters: {
type: 'object',
properties: {
category: {
type: 'string',
enum: ['electronics', 'clothing', 'books', 'home'],
description: 'Product category to filter by',
},
min_price: {
type: 'number',
description: 'Minimum price filter',
},
max_price: {
type: 'number',
description: 'Maximum price filter',
},
in_stock: {
type: 'boolean',
description: 'Filter for in-stock items only',
},
search_term: {
type: 'string',
description: 'Text to search in product names and descriptions',
},
},
required: [],
},
},
};
Using Zod for Type Safety
Zod helps create type-safe tool schemas. Install it:
npm install zod
Define Schema with Zod
import { z } from 'zod';
// Define the parameter schema
const weatherParamsSchema = z.object({
location: z.string().describe("City name, e.g., 'London' or 'Tokyo, Japan'"),
units: z.enum(['celsius', 'fahrenheit']).optional().describe('Temperature units'),
});
// Infer TypeScript type from schema
type WeatherParams = z.infer<typeof weatherParamsSchema>;
// Result: { location: string; units?: "celsius" | "fahrenheit" }
Convert Zod to JSON Schema
Use zod-to-json-schema for conversion:
npm install zod-to-json-schema
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';
const paramsSchema = z.object({
location: z.string().describe('City name'),
units: z.enum(['celsius', 'fahrenheit']).optional(),
});
const jsonSchema = zodToJsonSchema(paramsSchema, {
$refStrategy: 'none',
});
// Use in tool definition
const tool: OpenAI.ChatCompletionTool = {
type: 'function',
function: {
name: 'get_weather',
description: 'Get current weather for a location',
parameters: jsonSchema,
},
};
Validate Tool Arguments
function executeWeatherTool(args: unknown): string {
// Validate and parse arguments
const parsed = weatherParamsSchema.safeParse(args);
if (!parsed.success) {
return JSON.stringify({
error: 'Invalid arguments',
details: parsed.error.issues,
});
}
const { location, units } = parsed.data;
// Now TypeScript knows the exact types
return getWeather(location, units ?? 'celsius');
}
Creating a Tool Registry
Organize multiple tools in a registry:
import OpenAI from 'openai';
interface ToolDefinition {
tool: OpenAI.ChatCompletionTool;
execute: (args: unknown) => Promise<string>;
}
const toolRegistry: Record<string, ToolDefinition> = {
get_weather: {
tool: {
type: 'function',
function: {
name: 'get_weather',
description: 'Get current weather for a location',
parameters: {
type: 'object',
properties: {
location: { type: 'string', description: 'City name' },
},
required: ['location'],
},
},
},
execute: async (args) => {
const { location } = args as { location: string };
// Simulate weather API call
return JSON.stringify({
location,
temperature: 22,
condition: 'sunny',
});
},
},
calculate: {
tool: {
type: 'function',
function: {
name: 'calculate',
description: 'Perform mathematical calculations',
parameters: {
type: 'object',
properties: {
expression: { type: 'string', description: 'Math expression' },
},
required: ['expression'],
},
},
},
execute: async (args) => {
const { expression } = args as { expression: string };
try {
// Simple evaluation (use a proper math library in production)
const result = Function(`"use strict"; return (${expression})`)();
return JSON.stringify({ result });
} catch {
return JSON.stringify({ error: 'Invalid expression' });
}
},
},
};
// Get all tools for API call
function getAllTools(): OpenAI.ChatCompletionTool[] {
return Object.values(toolRegistry).map((def) => def.tool);
}
// Execute a tool by name
async function executeTool(name: string, args: unknown): Promise<string> {
const toolDef = toolRegistry[name];
if (!toolDef) {
return JSON.stringify({ error: `Unknown tool: ${name}` });
}
return toolDef.execute(args);
}
Common Mistakes to Avoid
1. Vague Descriptions
// Bad
description: 'Does something with data';
// Good
description: 'Fetch user profile data by user ID. Returns name, email, and account status.';
2. Missing Required Fields
// Bad - no required array
parameters: {
type: "object",
properties: { id: { type: "string" } },
}
// Good - explicitly state required fields
parameters: {
type: "object",
properties: { id: { type: "string" } },
required: ["id"],
}
3. Wrong Type for IDs
// Bad - IDs as numbers can cause issues
id: { type: "number" }
// Good - IDs as strings are safer
id: { type: "string", description: "User ID, e.g., 'usr_123abc'" }
4. No Parameter Descriptions
// Bad
properties: {
query: { type: "string" },
limit: { type: "integer" },
}
// Good
properties: {
query: {
type: "string",
description: "Search query text",
},
limit: {
type: "integer",
description: "Maximum results (1-100)",
minimum: 1,
maximum: 100,
},
}
Key Takeaways
- Tool definitions use JSON Schema for parameter specification
- Good descriptions are essential for the model to use tools correctly
- OpenAI and Anthropic have similar formats with minor differences
- Zod provides type safety and can generate JSON Schema
- A tool registry pattern organizes tools and their implementations
Resources
| Resource | Type | Level |
|---|---|---|
| JSON Schema Guide | Documentation | Beginner |
| OpenAI Function Calling | Documentation | Beginner |
| Anthropic Tool Use | Documentation | Beginner |
| Zod Documentation | Documentation | Intermediate |
Next Lesson
In the next lesson, you will learn how to handle tool calls from the model and execute the corresponding functions.