Tool Calling Basics

Connect language models to external functions using JSON Schema definitions and structured tool call responses.

TL;DR

  1. Describe each tool with a name, description, and json schema.
  2. Check the response for tool_calls instead of final text.
  3. Validate arguments with zod before running any tool.

Universal Tool Definition

    OpenAI Tool Declaration

    Structure tool object conforming to OpenAI function calling spec.

    const openAiTool = {
      type: 'function',
      function: {
        name: 'search_users',
        description: 'Look up user account by email',
        parameters: {
          type: 'object',
          properties: {
            email: { type: 'string', format: 'email' },
          },
          required: ['email'],
        },
      },
    };
    Zod To JSON Schema

    Derive JSON Schema definitions directly from Zod models.

    import { zodToJsonSchema } from 'zod-to-json-schema';
    import { z } from 'zod';
    const UserQuery = z.object({
      email: z.string().email(),
    });
    const schema = zodToJsonSchema(UserQuery, 'UserQuery');
    Strict Tool Schema Flag

    Enforce strict JSON schema conformance in OpenAI tools.

    const strictTool = {
      type: 'function',
      function: {
        name: 'get_quote',
        strict: true,
        parameters: { type: 'object' },
      },
    };

Argument Parsing In TypeScript

    Safe JSON Parsing

    Safely deserialize stringified JSON tool call arguments.

    function safeParseArgs<T>(raw: string): T | null {
      try {
        return JSON.parse(raw) as T;
      } catch (err) {
        logger.error('Malformed tool args', { err });
        return null;
      }
    }
    Zod Runtime Validation

    Validate parsed argument object against strict type contract.

    const SearchSchema = z.object({
      email: z.string().email(),
    });
    const parsed = SearchSchema.safeParse(rawJson);
    if (!parsed.success) {
      throw new Error(
        `Validation: ${parsed.error.message}`
      );
    }
    Dispatcher Map Pattern

    Route tool calls to specific handler functions dynamically.

    type H = (args: any) => Promise<any>;
    const registry: Record<string, H> = {
      search_users: handleSearchUsers,
      calc_tax: handleCalcTax,
    };
    const output = await registry[call.name](parsed.data);

Tool Conversation Lifecycle

    Detect Tool Invocations

    Check if assistant response requested external tool execution.

    const calls = res.choices[0].message.tool_calls;
    if (calls && calls.length > 0) {
      for (const call of calls) {
        await processToolCall(call);
      }
    }
    Format Tool Message Block

    Append tool execution output to dialogue context array.

    const toolMessage = {
      role: 'tool',
      tool_call_id: call.id,
      content: JSON.stringify(toolExecutionResult),
    };
    Dispatch Continuation Request

    Feed tool execution message back to model for final answer.

    const msgs = [...chatHistory, assistantMsg, toolMsg];
    const finalRes = await openai.chat.completions.create({
      model: 'gpt-6.1-sol',
      messages: msgs,
    });

Defensive Engineering Controls

    Tool Execution Timeout

    Abort sluggish tool operations after fixed duration ceiling.

    async function withTimeout<T>(
      p: Promise<T>,
      ms = 5000
    ): Promise<T> {
      const timer = new Promise((_, rej) =>
        setTimeout(() => rej(new Error('Timeout')), ms)
      );
      return Promise.race([p, timer]) as Promise<T>;
    }
    Sanitized Error Forwarding

    Provide actionable error messages so model can recover.

    catch (err: any) {
      return {
        role: 'tool',
        tool_call_id: call.id,
        content: JSON.stringify({ error: err.message }),
      };
    }
    Permission Whitelisting

    Verify active user possesses permission to invoke tool.

    function checkPerm(user: User, toolName: string) {
      if (!user.roles.includes(TOOL_PERMS[toolName])) {
        throw new Error('Unauthorized tool access');
      }
    }

Tips

  1. Write detailed descriptions for every input_schema parameter to help the model select appropriate tool arguments accurately.
  2. Map tool names to execution handlers using a TypeScript Record<string, Function> dictionary for clean routing.

Warnings

  1. Always wrap JSON.parse of tool arguments in try/catch, because models can occasionally emit malformed JSON.
  2. Never run arbitrary shell commands or unsanitized database queries requested by tool_calls without rigorous parameter whitelisting.

In Practice

FAQ