TypeScript AI Tool Calling

A complete guide to registering type-safe tools, executing local functions, and coordinating multi-step agentic execution loops in TypeScript.

TL;DR

  1. Define tool argument schemas using standard parameters JSON Schema objects.
  2. Dispatch model tool_calls requests to local TypeScript functions.
  3. Append returned tool execution results with the tool role.

Tool Definition and Schemas

    Tool definition object

    Declare function signature and parameters for OpenAI.

    const weatherTool = {
      type: 'function' as const,
      function: {
        name: 'getWeather',
        description: 'Get weather',
        parameters: {
          type: 'object',
          properties: { city: { type: 'string' } },
          required: ['city'],
        },
      },
    };
    interface ToolDefinition

    Define reusable TypeScript interface for custom tools.

    interface ToolDefinition {
      name: string;
      description: string;
      parameters: Record<string, unknown>;
    }
    tool_choice parameter

    Control whether model must execute a tool or answer freely.

    const cfg = {
      tool_choice: 'auto' as const,
      // or: { type: 'function', function: { name: 'fn' } }
    };

Executing Local Functions

    Typed tool registry

    Map tool names to typed execution handlers safely.

    type ToolFn = (args: any) => Promise<string>;
    const handlers: Record<string, ToolFn> = {
      getWeather: async ({ city }) => {
        return JSON.stringify({ temp: 72, city });
      },
    };
    Execute tool call safely

    Parse JSON arguments and invoke target handler.

    async function executeCall(call: any) {
      const fn = handlers[call.function.name];
      if (!fn) throw new Error('Unknown tool');
      const args = JSON.parse(call.function.arguments);
      return await fn(args);
    }
    Tool result message

    Format function execution output for conversation thread.

    function makeToolMsg(id: string, output: string) {
      return {
        role: 'tool' as const,
        tool_call_id: id,
        content: output,
      };
    }

Multi-Step Agentic Loops

    Agent loop condition

    Run agent loop until model emits standard text response.

    let step = 0;
    const maxSteps = 5;
    while (step < maxSteps) {
      const res = await getCompletion(messages);
      const msg = res.choices[0].message;
      messages.push(msg);
      if (!msg.tool_calls?.length) break;
      step++;
    }
    Parallel tool dispatch

    Execute multiple concurrent tool calls with Promise.all.

    const toolResults = await Promise.all(
      msg.tool_calls.map(async (call) => ({
        role: 'tool' as const,
        tool_call_id: call.id,
        content: await executeCall(call),
      }))
    );
    messages.push(...toolResults);
    Agent step guard

    Throw descriptive error when maximum step threshold hit.

    if (step >= maxSteps) {
      throw new Error('Agent exceeded maximum steps');
    }

Anthropic Claude Tools

    Anthropic tool schema

    Format tool definitions for Claude messages API.

    const claudeTools = [{
      name: 'lookup_user',
      description: 'Lookup user record',
      input_schema: {
        type: 'object',
        properties: { id: { type: 'string' } },
        required: ['id'],
      },
    }];
    Claude tool_use block

    Detect tool invocation blocks within Claude response.

    const toolUse = res.content.find(
      (block) => block.type === 'tool_use'
    );
    if (toolUse) {
      const input = toolUse.input;
      console.log('Tool called:', toolUse.name);
    }
    Claude tool_result block

    Send tool response block back to Anthropic API.

    const reply = {
      role: 'user' as const,
      content: [{
        type: 'tool_result' as const,
        tool_use_id: toolUse.id,
        content: 'User: active',
      }],
    };

Tips

  1. Always map your tool definitions to a strongly typed TypeScript Record of handler functions to ensure complete compile-time routing safety.
  2. Set a conservative maxSteps loop counter when orchestrating autonomous agent loops to prevent unbounded execution and runaway token billing.

Warnings

  1. Never execute arbitrary system shell commands or raw database queries without validating tool arguments against a strict z.object() schema.
  2. Remember to return a matching tool_call_id in every tool response message or provider APIs will reject the conversation thread.

In Practice

FAQ