From Zero to AI

Lesson 3.2: Defining Tools

Duration: 60 minutes

Learning Objectives

By the end of this lesson, you will be able to:

  1. Create tool definitions using JSON Schema
  2. Write effective tool descriptions
  3. Define parameters with proper types and constraints
  4. Use Zod for type-safe tool schemas
  5. Implement tools for both OpenAI and Anthropic

Tool Definition Structure

A tool definition tells the AI model:

  1. Name: A unique identifier for the function
  2. Description: What the function does and when to use it
  3. 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 tool
  • name must be a valid identifier (letters, numbers, underscores)
  • description helps the model understand when to use it
  • parameters follows 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

  1. Tool definitions use JSON Schema for parameter specification
  2. Good descriptions are essential for the model to use tools correctly
  3. OpenAI and Anthropic have similar formats with minor differences
  4. Zod provides type safety and can generate JSON Schema
  5. 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.

Continue to Lesson 3.3: Handling Tool Calls