From Zero to AI

Lesson 6.4: Integrating Tools

Duration: 75 minutes

Learning Objectives

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

  1. Design a tool registry for managing multiple tools
  2. Implement practical tools for calculations, weather, and notes
  3. Handle tool errors gracefully
  4. Create tools that interact with external APIs
  5. Build a web search tool for real-time information

Introduction

Tools transform your AI assistant from a conversational system into an agent that can take actions. In Module 3, you learned function calling. Now you will implement a complete tool system for your capstone project.

┌─────────────────────────────────────────────────────────────────────┐
│                       TOOL ARCHITECTURE                             │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │                     TOOL REGISTRY                            │   │
│  │                                                               │   │
│  │  ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐        │   │
│  │  │Calculator│  │ Weather │  │  Notes  │  │  Search │        │   │
│  │  └────┬────┘  └────┬────┘  └────┬────┘  └────┬────┘        │   │
│  │       │            │            │            │               │   │
│  └───────┼────────────┼────────────┼────────────┼───────────────┘   │
│          │            │            │            │                   │
│          ▼            ▼            ▼            ▼                   │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │                   TOOL EXECUTOR                              │   │
│  │                                                               │   │
│  │  • Validates inputs with Zod schemas                         │   │
│  │  • Executes tool logic                                       │   │
│  │  • Handles errors gracefully                                 │   │
│  │  • Returns formatted results                                 │   │
│  │                                                               │   │
│  └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Tool Registry

Create a registry to manage tools centrally.

Create src/tools/index.ts:

import { z } from 'zod';

import type { Tool, ToolDefinition } from '../core/types.js';
import { ToolError } from '../utils/errors.js';
import { createLogger } from '../utils/logger.js';

const logger = createLogger('ToolRegistry');

// Tool builder for type-safe tool creation
export interface ToolBuilder<T extends z.ZodType> {
  name: string;
  description: string;
  schema: T;
  execute: (args: z.infer<T>) => Promise<string>;
}

export function createTool<T extends z.ZodType>(builder: ToolBuilder<T>): Tool {
  const { name, description, schema, execute } = builder;

  // Convert Zod schema to JSON Schema for the AI
  const jsonSchema = zodToJsonSchema(schema);

  return {
    name,
    description,
    parameters: jsonSchema,
    execute: async (rawArgs: Record<string, unknown>) => {
      // Validate input with Zod
      const parseResult = schema.safeParse(rawArgs);

      if (!parseResult.success) {
        const errors = parseResult.error.issues
          .map((i) => `${i.path.join('.')}: ${i.message}`)
          .join(', ');
        logger.warn(`Tool ${name} validation failed: ${errors}`);
        return `Error: Invalid arguments - ${errors}`;
      }

      try {
        const result = await execute(parseResult.data);
        logger.debug(`Tool ${name} executed successfully`);
        return result;
      } catch (error) {
        const message = error instanceof Error ? error.message : 'Unknown error';
        logger.error(`Tool ${name} execution failed: ${message}`);
        throw new ToolError(message, name, error instanceof Error ? error : undefined);
      }
    },
  };
}

// Helper to convert Zod schema to JSON Schema
function zodToJsonSchema(schema: z.ZodType): ToolDefinition['parameters'] {
  // This is a simplified converter for common types
  // For production, consider using zod-to-json-schema library

  if (schema instanceof z.ZodObject) {
    const shape = schema._def.shape();
    const properties: Record<string, unknown> = {};
    const required: string[] = [];

    for (const [key, value] of Object.entries(shape)) {
      const zodValue = value as z.ZodType;
      properties[key] = zodTypeToJsonSchema(zodValue);

      if (!(zodValue instanceof z.ZodOptional)) {
        required.push(key);
      }
    }

    return {
      type: 'object',
      properties,
      required: required.length > 0 ? required : undefined,
    };
  }

  return { type: 'object', properties: {} };
}

function zodTypeToJsonSchema(zodType: z.ZodType): Record<string, unknown> {
  if (zodType instanceof z.ZodString) {
    const schema: Record<string, unknown> = { type: 'string' };
    if (zodType.description) schema.description = zodType.description;
    return schema;
  }

  if (zodType instanceof z.ZodNumber) {
    const schema: Record<string, unknown> = { type: 'number' };
    if (zodType.description) schema.description = zodType.description;
    return schema;
  }

  if (zodType instanceof z.ZodBoolean) {
    const schema: Record<string, unknown> = { type: 'boolean' };
    if (zodType.description) schema.description = zodType.description;
    return schema;
  }

  if (zodType instanceof z.ZodArray) {
    return {
      type: 'array',
      items: zodTypeToJsonSchema(zodType._def.type),
    };
  }

  if (zodType instanceof z.ZodOptional) {
    return zodTypeToJsonSchema(zodType._def.innerType);
  }

  if (zodType instanceof z.ZodEnum) {
    return {
      type: 'string',
      enum: zodType._def.values,
    };
  }

  return { type: 'string' };
}

// Registry class
export class ToolRegistry {
  private tools = new Map<string, Tool>();

  register(tool: Tool): void {
    this.tools.set(tool.name, tool);
    logger.info(`Registered tool: ${tool.name}`);
  }

  unregister(name: string): boolean {
    const result = this.tools.delete(name);
    if (result) {
      logger.info(`Unregistered tool: ${name}`);
    }
    return result;
  }

  get(name: string): Tool | undefined {
    return this.tools.get(name);
  }

  getAll(): Tool[] {
    return Array.from(this.tools.values());
  }

  getDefinitions(): ToolDefinition[] {
    return this.getAll().map(({ name, description, parameters }) => ({
      name,
      description,
      parameters,
    }));
  }

  has(name: string): boolean {
    return this.tools.has(name);
  }

  clear(): void {
    this.tools.clear();
    logger.info('Tool registry cleared');
  }
}

// Export tools
export { calculatorTool } from './calculator.js';
export { weatherTool } from './weather.js';
export { notesTool } from './notes.js';
export { webSearchTool } from './web-search.js';

Calculator Tool

A calculator for mathematical operations.

Create src/tools/calculator.ts:

import { z } from 'zod';

import { createTool } from './index.js';

const CalculatorSchema = z.object({
  expression: z
    .string()
    .describe(
      "The mathematical expression to evaluate. Supports +, -, *, /, ^, sqrt(), sin(), cos(), tan(), log(), and parentheses. Examples: '2 + 2', '(10 + 5) * 2', 'sqrt(16)', '2^8'"
    ),
});

// Safe math evaluator (no eval!)
function evaluateExpression(expr: string): number {
  // Remove whitespace
  const cleaned = expr.replace(/\s+/g, '');

  // Tokenize
  const tokens = tokenize(cleaned);

  // Parse and evaluate
  const result = parseExpression(tokens, 0);

  if (result.index !== tokens.length) {
    throw new Error('Invalid expression: unexpected tokens');
  }

  return result.value;
}

interface Token {
  type: 'number' | 'operator' | 'function' | 'paren';
  value: string | number;
}

function tokenize(expr: string): Token[] {
  const tokens: Token[] = [];
  let i = 0;

  while (i < expr.length) {
    const char = expr[i];

    // Numbers (including decimals)
    if (/[0-9.]/.test(char)) {
      let num = '';
      while (i < expr.length && /[0-9.]/.test(expr[i])) {
        num += expr[i++];
      }
      tokens.push({ type: 'number', value: parseFloat(num) });
      continue;
    }

    // Operators
    if (['+', '-', '*', '/', '^'].includes(char)) {
      tokens.push({ type: 'operator', value: char });
      i++;
      continue;
    }

    // Parentheses
    if (char === '(' || char === ')') {
      tokens.push({ type: 'paren', value: char });
      i++;
      continue;
    }

    // Functions
    const functions = ['sqrt', 'sin', 'cos', 'tan', 'log', 'abs', 'floor', 'ceil', 'round'];
    let foundFunc = false;

    for (const fn of functions) {
      if (expr.slice(i, i + fn.length).toLowerCase() === fn) {
        tokens.push({ type: 'function', value: fn });
        i += fn.length;
        foundFunc = true;
        break;
      }
    }

    if (foundFunc) continue;

    // Constants
    if (expr.slice(i, i + 2).toLowerCase() === 'pi') {
      tokens.push({ type: 'number', value: Math.PI });
      i += 2;
      continue;
    }

    if (expr[i].toLowerCase() === 'e' && !/[a-z]/i.test(expr[i + 1] || '')) {
      tokens.push({ type: 'number', value: Math.E });
      i++;
      continue;
    }

    throw new Error(`Invalid character: ${char}`);
  }

  return tokens;
}

interface ParseResult {
  value: number;
  index: number;
}

function parseExpression(tokens: Token[], index: number): ParseResult {
  return parseAddSub(tokens, index);
}

function parseAddSub(tokens: Token[], index: number): ParseResult {
  let result = parseMulDiv(tokens, index);

  while (
    result.index < tokens.length &&
    tokens[result.index].type === 'operator' &&
    (tokens[result.index].value === '+' || tokens[result.index].value === '-')
  ) {
    const op = tokens[result.index].value;
    const right = parseMulDiv(tokens, result.index + 1);

    result = {
      value: op === '+' ? result.value + right.value : result.value - right.value,
      index: right.index,
    };
  }

  return result;
}

function parseMulDiv(tokens: Token[], index: number): ParseResult {
  let result = parsePower(tokens, index);

  while (
    result.index < tokens.length &&
    tokens[result.index].type === 'operator' &&
    (tokens[result.index].value === '*' || tokens[result.index].value === '/')
  ) {
    const op = tokens[result.index].value;
    const right = parsePower(tokens, result.index + 1);

    if (op === '/' && right.value === 0) {
      throw new Error('Division by zero');
    }

    result = {
      value: op === '*' ? result.value * right.value : result.value / right.value,
      index: right.index,
    };
  }

  return result;
}

function parsePower(tokens: Token[], index: number): ParseResult {
  let result = parseUnary(tokens, index);

  if (
    result.index < tokens.length &&
    tokens[result.index].type === 'operator' &&
    tokens[result.index].value === '^'
  ) {
    const right = parsePower(tokens, result.index + 1);
    result = {
      value: Math.pow(result.value, right.value),
      index: right.index,
    };
  }

  return result;
}

function parseUnary(tokens: Token[], index: number): ParseResult {
  if (tokens[index].type === 'operator' && tokens[index].value === '-') {
    const result = parsePrimary(tokens, index + 1);
    return { value: -result.value, index: result.index };
  }

  return parsePrimary(tokens, index);
}

function parsePrimary(tokens: Token[], index: number): ParseResult {
  const token = tokens[index];

  if (!token) {
    throw new Error('Unexpected end of expression');
  }

  // Number
  if (token.type === 'number') {
    return { value: token.value as number, index: index + 1 };
  }

  // Function
  if (token.type === 'function') {
    if (tokens[index + 1]?.value !== '(') {
      throw new Error(`Expected ( after ${token.value}`);
    }

    const argResult = parseExpression(tokens, index + 2);

    if (tokens[argResult.index]?.value !== ')') {
      throw new Error(`Expected ) after function argument`);
    }

    const arg = argResult.value;
    let value: number;

    switch (token.value) {
      case 'sqrt':
        value = Math.sqrt(arg);
        break;
      case 'sin':
        value = Math.sin(arg);
        break;
      case 'cos':
        value = Math.cos(arg);
        break;
      case 'tan':
        value = Math.tan(arg);
        break;
      case 'log':
        value = Math.log(arg);
        break;
      case 'abs':
        value = Math.abs(arg);
        break;
      case 'floor':
        value = Math.floor(arg);
        break;
      case 'ceil':
        value = Math.ceil(arg);
        break;
      case 'round':
        value = Math.round(arg);
        break;
      default:
        throw new Error(`Unknown function: ${token.value}`);
    }

    return { value, index: argResult.index + 1 };
  }

  // Parentheses
  if (token.type === 'paren' && token.value === '(') {
    const result = parseExpression(tokens, index + 1);

    if (tokens[result.index]?.value !== ')') {
      throw new Error('Mismatched parentheses');
    }

    return { value: result.value, index: result.index + 1 };
  }

  throw new Error(`Unexpected token: ${JSON.stringify(token)}`);
}

export const calculatorTool = createTool({
  name: 'calculator',
  description:
    'Performs mathematical calculations. Use for any math operations including basic arithmetic, powers, roots, and trigonometry.',
  schema: CalculatorSchema,
  execute: async ({ expression }) => {
    try {
      const result = evaluateExpression(expression);

      // Format result nicely
      if (Number.isInteger(result)) {
        return `${expression} = ${result}`;
      }

      // Round to reasonable precision
      const rounded = Math.round(result * 1000000) / 1000000;
      return `${expression} = ${rounded}`;
    } catch (error) {
      return `Error evaluating "${expression}": ${error instanceof Error ? error.message : 'Invalid expression'}`;
    }
  },
});

Weather Tool

A weather tool using the OpenWeatherMap API.

Create src/tools/weather.ts:

import { z } from 'zod';

import { config } from '../core/config.js';
import { createTool } from './index.js';

const WeatherSchema = z.object({
  location: z
    .string()
    .describe(
      "The city name to get weather for. Can include country code, e.g., 'London,UK' or 'Tokyo,JP'"
    ),
  units: z
    .enum(['metric', 'imperial'])
    .optional()
    .describe(
      "Temperature units: 'metric' for Celsius, 'imperial' for Fahrenheit. Defaults to metric."
    ),
});

interface WeatherData {
  name: string;
  sys: { country: string };
  main: {
    temp: number;
    feels_like: number;
    humidity: number;
    pressure: number;
  };
  weather: Array<{ main: string; description: string }>;
  wind: { speed: number };
}

export const weatherTool = createTool({
  name: 'weather',
  description:
    'Gets current weather information for a location. Returns temperature, conditions, humidity, and wind speed.',
  schema: WeatherSchema,
  execute: async ({ location, units = 'metric' }) => {
    const apiKey = config.weatherApiKey;

    if (!apiKey) {
      return 'Weather service is not configured. Please set WEATHER_API_KEY in your environment.';
    }

    try {
      const url = new URL('https://api.openweathermap.org/data/2.5/weather');
      url.searchParams.set('q', location);
      url.searchParams.set('appid', apiKey);
      url.searchParams.set('units', units);

      const response = await fetch(url.toString());

      if (!response.ok) {
        if (response.status === 404) {
          return `Location "${location}" not found. Please check the spelling or try a different city name.`;
        }
        throw new Error(`API error: ${response.status}`);
      }

      const data = (await response.json()) as WeatherData;

      const tempUnit = units === 'metric' ? '°C' : '°F';
      const speedUnit = units === 'metric' ? 'm/s' : 'mph';

      return `Weather in ${data.name}, ${data.sys.country}:
- Conditions: ${data.weather[0].description}
- Temperature: ${Math.round(data.main.temp)}${tempUnit} (feels like ${Math.round(data.main.feels_like)}${tempUnit})
- Humidity: ${data.main.humidity}%
- Wind: ${data.wind.speed} ${speedUnit}`;
    } catch (error) {
      return `Error getting weather for "${location}": ${error instanceof Error ? error.message : 'Unknown error'}`;
    }
  },
});

Notes Tool

A tool for saving and retrieving notes with persistent storage.

Create src/tools/notes.ts:

import { mkdir, readFile, writeFile } from 'fs/promises';
import { dirname, join } from 'path';
import { z } from 'zod';

import { createLogger } from '../utils/logger.js';
import { createTool } from './index.js';

const logger = createLogger('NotesTool');

const NotesSchema = z.object({
  action: z
    .enum(['save', 'get', 'list', 'delete'])
    .describe(
      "Action to perform: 'save' to store a note, 'get' to retrieve a specific note, 'list' to see all notes, 'delete' to remove a note"
    ),
  key: z
    .string()
    .optional()
    .describe('The key/name for the note. Required for save, get, and delete actions.'),
  content: z.string().optional().describe('The content to save. Required for save action.'),
});

interface NotesStorage {
  notes: Record<string, { content: string; createdAt: string; updatedAt: string }>;
}

const NOTES_FILE = './data/notes.json';

async function loadNotes(): Promise<NotesStorage> {
  try {
    const data = await readFile(NOTES_FILE, 'utf-8');
    return JSON.parse(data);
  } catch {
    return { notes: {} };
  }
}

async function saveNotes(storage: NotesStorage): Promise<void> {
  const dir = dirname(NOTES_FILE);
  await mkdir(dir, { recursive: true });
  await writeFile(NOTES_FILE, JSON.stringify(storage, null, 2));
}

export const notesTool = createTool({
  name: 'notes',
  description:
    'Manages persistent notes. Can save, retrieve, list, and delete notes. Notes persist across conversations.',
  schema: NotesSchema,
  execute: async ({ action, key, content }) => {
    const storage = await loadNotes();

    switch (action) {
      case 'save': {
        if (!key) {
          return 'Error: Key is required for saving a note.';
        }
        if (!content) {
          return 'Error: Content is required for saving a note.';
        }

        const now = new Date().toISOString();
        const isUpdate = key in storage.notes;

        storage.notes[key] = {
          content,
          createdAt: isUpdate ? storage.notes[key].createdAt : now,
          updatedAt: now,
        };

        await saveNotes(storage);
        logger.debug(`Saved note: ${key}`);

        return isUpdate
          ? `Updated note "${key}" successfully.`
          : `Saved note "${key}" successfully.`;
      }

      case 'get': {
        if (!key) {
          return 'Error: Key is required for retrieving a note.';
        }

        const note = storage.notes[key];

        if (!note) {
          return `Note "${key}" not found. Use action "list" to see available notes.`;
        }

        return `Note "${key}" (saved ${new Date(note.updatedAt).toLocaleString()}):\n${note.content}`;
      }

      case 'list': {
        const keys = Object.keys(storage.notes);

        if (keys.length === 0) {
          return 'No notes saved yet.';
        }

        const noteList = keys.map((k) => {
          const note = storage.notes[k];
          const preview =
            note.content.length > 50 ? note.content.substring(0, 50) + '...' : note.content;
          return `- ${k}: ${preview}`;
        });

        return `Saved notes (${keys.length}):\n${noteList.join('\n')}`;
      }

      case 'delete': {
        if (!key) {
          return 'Error: Key is required for deleting a note.';
        }

        if (!(key in storage.notes)) {
          return `Note "${key}" not found.`;
        }

        delete storage.notes[key];
        await saveNotes(storage);
        logger.debug(`Deleted note: ${key}`);

        return `Deleted note "${key}" successfully.`;
      }

      default:
        return `Unknown action: ${action}`;
    }
  },
});

Web Search Tool

A simple web search tool using a search API.

Create src/tools/web-search.ts:

import { z } from 'zod';

import { createLogger } from '../utils/logger.js';
import { createTool } from './index.js';

const logger = createLogger('WebSearchTool');

const WebSearchSchema = z.object({
  query: z.string().describe('The search query. Be specific for better results.'),
  maxResults: z.number().optional().describe('Maximum number of results to return. Defaults to 3.'),
});

// This is a simulated search for demonstration
// In production, you would use a real search API like:
// - Google Custom Search API
// - Bing Search API
// - SerpAPI
// - Brave Search API

interface SearchResult {
  title: string;
  url: string;
  snippet: string;
}

async function performSearch(query: string, maxResults: number): Promise<SearchResult[]> {
  // Simulated search results for demonstration
  // Replace this with actual API calls in production

  logger.info(`Searching for: ${query}`);

  // In a real implementation:
  // const response = await fetch(`https://api.search-provider.com/search?q=${encodeURIComponent(query)}`);
  // const data = await response.json();
  // return data.results;

  // Simulated response
  await new Promise((resolve) => setTimeout(resolve, 500)); // Simulate API latency

  return [
    {
      title: `Search result 1 for "${query}"`,
      url: `https://example.com/result1?q=${encodeURIComponent(query)}`,
      snippet: `This is a simulated search result. In a production environment, this would contain actual search results from a search API for the query "${query}".`,
    },
    {
      title: `Search result 2 for "${query}"`,
      url: `https://example.com/result2?q=${encodeURIComponent(query)}`,
      snippet: `Another simulated result. To enable real search, integrate with Google Custom Search, Bing Search API, or similar services.`,
    },
    {
      title: `Search result 3 for "${query}"`,
      url: `https://example.com/result3?q=${encodeURIComponent(query)}`,
      snippet: `Third simulated result demonstrating the search tool functionality.`,
    },
  ].slice(0, maxResults);
}

export const webSearchTool = createTool({
  name: 'web_search',
  description:
    'Searches the web for current information. Use when you need up-to-date information that may not be in your training data.',
  schema: WebSearchSchema,
  execute: async ({ query, maxResults = 3 }) => {
    try {
      const results = await performSearch(query, maxResults);

      if (results.length === 0) {
        return `No results found for "${query}".`;
      }

      const formatted = results
        .map((r, i) => `${i + 1}. ${r.title}\n   URL: ${r.url}\n   ${r.snippet}`)
        .join('\n\n');

      return `Search results for "${query}":\n\n${formatted}`;
    } catch (error) {
      return `Error searching for "${query}": ${error instanceof Error ? error.message : 'Unknown error'}`;
    }
  },
});

Integrating Tools with the Assistant

Update src/index.ts to register all tools:

import * as readline from 'readline';

import { Assistant } from './core/assistant.js';
import { createRetriever } from './rag/retriever.js';
import { calculatorTool, notesTool, weatherTool, webSearchTool } from './tools/index.js';
import { createLogger } from './utils/logger.js';

const logger = createLogger('Main');

async function main() {
  console.log('AI Knowledge Assistant');
  console.log('======================');
  console.log('Type your message and press Enter.');
  console.log('Commands: /clear, /status, /tools, /exit');
  console.log('');

  // Create assistant with tools
  const assistant = new Assistant({
    enableRag: true,
    tools: [calculatorTool, weatherTool, notesTool, webSearchTool],
  });

  // Configure RAG retriever
  const retriever = createRetriever();
  assistant.setRagRetriever(retriever);

  console.log('Initializing knowledge base...');

  try {
    await retriever('initialize');
    console.log('Knowledge base ready!');
  } catch {
    console.log('Warning: Could not load documents.');
  }

  console.log('Tools available: calculator, weather, notes, web_search\n');

  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });

  const prompt = () => {
    rl.question('You: ', async (input) => {
      const trimmed = input.trim();

      if (!trimmed) {
        prompt();
        return;
      }

      // Handle commands
      if (trimmed === '/exit') {
        console.log('Goodbye!');
        rl.close();
        process.exit(0);
      }

      if (trimmed === '/clear') {
        assistant.clearHistory();
        console.log('Conversation history cleared.\n');
        prompt();
        return;
      }

      if (trimmed === '/status') {
        const history = assistant.getHistory();
        console.log(`Messages in history: ${history.length}`);
        console.log('');
        prompt();
        return;
      }

      if (trimmed === '/tools') {
        console.log('Available tools:');
        console.log('  - calculator: Perform math calculations');
        console.log('  - weather: Get current weather (requires API key)');
        console.log('  - notes: Save and retrieve persistent notes');
        console.log('  - web_search: Search the web for information');
        console.log('');
        prompt();
        return;
      }

      // Process message with streaming
      process.stdout.write('Assistant: ');

      try {
        for await (const chunk of assistant.chatStream(trimmed)) {
          if (chunk.type === 'text' && chunk.content) {
            process.stdout.write(chunk.content);
          } else if (chunk.type === 'tool_call' && chunk.toolCall) {
            process.stdout.write(`\n[Using ${chunk.toolCall.name}...]\n`);
          }
        }
        console.log('\n');
      } catch (error) {
        console.error(`\nError: ${error instanceof Error ? error.message : error}\n`);
      }

      prompt();
    });
  };

  prompt();
}

main().catch((error) => {
  logger.error('Fatal error', error);
  process.exit(1);
});

Testing Tools

Run the assistant and test each tool:

npm start

Example interactions:

AI Knowledge Assistant
======================
Type your message and press Enter.
Commands: /clear, /status, /tools, /exit

Initializing knowledge base...
Knowledge base ready!
Tools available: calculator, weather, notes, web_search

You: What is 15% of 850 plus the square root of 144?
[Using calculator...]
[Using calculator...]
Assistant: Let me calculate that for you:

1. First, 15% of 850 = 127.5
2. Square root of 144 = 12

Adding them together: 127.5 + 12 = **139.5**

You: Save a note called "meeting" with the content "Team sync at 3pm tomorrow"
[Using notes...]
Assistant: I've saved your note. The meeting reminder "Team sync at 3pm tomorrow" has been stored under the key "meeting". You can retrieve it anytime by asking about your notes.

You: What notes do I have saved?
[Using notes...]
Assistant: You have 1 note saved:

- **meeting**: Team sync at 3pm tomorrow

You: Search for the latest TypeScript features
[Using web_search...]
Assistant: Based on the search results, here's what I found about the latest TypeScript features:

1. TypeScript continues to evolve with new features for type safety and developer experience
2. Recent versions have introduced improvements to type inference and narrowing
3. For the most accurate and current information, I recommend checking the official TypeScript documentation at typescriptlang.org

Note: The search results are simulated. For real-time search results, configure a search API.

Best Practices for Tools

1. Clear Descriptions

Write descriptions that help the AI understand when to use the tool:

// Good
description: 'Performs mathematical calculations including arithmetic, powers, roots, and trigonometric functions. Use for any math operations.';

// Less helpful
description: 'Calculator tool';

2. Validate Inputs

Always validate inputs before executing:

const result = schema.safeParse(args);
if (!result.success) {
  return `Error: ${result.error.message}`;
}

3. Handle Errors Gracefully

Return helpful error messages instead of throwing:

try {
  const result = await apiCall();
  return formatResult(result);
} catch (error) {
  return `Error: ${error.message}. Please try again.`;
}

4. Return Structured Results

Format results clearly:

return `Weather in ${city}:
- Temperature: ${temp}°C
- Conditions: ${conditions}
- Humidity: ${humidity}%`;

Key Takeaways

  1. Tool registry centralizes tool management
  2. Zod schemas provide runtime validation and JSON Schema generation
  3. Error handling in tools should be graceful and informative
  4. Clear descriptions help the AI decide when to use tools
  5. Structured output makes tool results easy to understand

Practice Exercise

  1. Add a "time" tool that returns the current time in different timezones
  2. Create a "unit_converter" tool for converting between units
  3. Implement a "reminder" tool that schedules future reminders
  4. Add rate limiting to prevent tool abuse
  5. Create a tool that calls an external API of your choice

Next Steps

Your assistant now has knowledge and actions. In the next lesson, you will learn how to deploy it to production.

Continue to Lesson 6.5: Deployment Basics