Module 2 - Lesson 2g: Function/Tool Calling with Anthropic

Integrating external APIs and tools with Anthropic Claude function calling.

Published: 1/10/2026

Lesson 2g: Function/Tool Calling with Anthropic Claude

Learn how to give Claude access to external tools and APIs through function calling. Anthropic's approach differs from OpenAI's in how tool responses are handled.

Key Difference from OpenAI

OpenAI: Tool calls and responses in the same message format Anthropic: Explicit message structure with tool_result type

Code Example

Create src/anthropic/tools-prompt.ts:

import Anthropic from "@anthropic-ai/sdk";
import dotenv from "dotenv";

// Load environment variables
dotenv.config();
const input = process.argv[2]?.toUpperCase();
if (input && input !== "ACTUAL" && input !== "MOCK") {
  throw new Error(
    `Invalid weather tool type: "${process.argv[2]}". Must be "ACTUAL" or "MOCK".`
  );
}
const weatherToolType: "ACTUAL" | "MOCK" =
  input === "ACTUAL" ? "ACTUAL" : "MOCK";

// 1. Define a list of callable tools for the model
const tools: Anthropic.Tool[] = [
  {
    name: "get_mock_weather",
    description: "Get the mock weather for today for a location.",
    input_schema: {
      type: "object",
      properties: {
        location: {
          type: "string",
          description: "A location to get the weather for, e.g., London, UK",
        },
      },
      required: ["location"],
      additionalProperties: false,
    },
  },
  {
    name: "get_actual_weather",
    description: "Get the actual weather for today for a location.",
    input_schema: {
      type: "object",
      properties: {
        location: {
          type: "string",
          description: "A location to get the weather for, e.g., London, UK",
        },
      },
      required: ["location"],
      additionalProperties: false,
    },
  },
];

function getMockWeather(location: string): string {
  return location + " the weather is sunny with a high of 25°C.";
}

export const getWeather = async (
  location: string,
  unit = "celsius"
): Promise<string> => {
  const response = await fetch(
    `https://api.weatherapi.com/v1/current.json?q=${location}&aqi=no&key=${process.env.WEATHER_API_KEY}`
  );
  const data = await response.json();
  return JSON.stringify(data);
};

// Create Anthropic client
const anthropic = new Anthropic();

// Async function with proper return type
async function toolsPrompt(weatherToolType: "ACTUAL" | "MOCK"): Promise<void> {
  try {
    console.log("Testing Anthropic connection...");
    console.log(`Using weather tool type: ${weatherToolType}`);

    // create a messages array to hold the conversation
    const weatherRequest = weatherToolType === "ACTUAL" ? "actual" : "mock";
    let messages: Anthropic.Messages.MessageParam[] = [
      {
        role: "user",
        content: `Suggest a travel destination within Europe where there is a Christmas market that is famous but is not in a big city. I would like to go somewhere that is less than 2 hours from a major airport and has good public transport links, please provide the ${weatherRequest} weather as of today there`,
      },
    ];

    // Make API call - response is automatically typed!
    // Using a system prompt along with user prompt
    const response = await anthropic.messages.create({
      model: "claude-haiku-4-5",
      tools: tools,
      system:
        "You are a helpful travel assistant. Provide detailed travel suggestions based on user preferences and give a guide to the destination and include distance from the airport. Get mocked weather data for locations when requested using the get_mock_weather tool and actual weather data using the get_actual_weather tool.",
      messages: messages,
      max_tokens: 1000,
    });

    // note there is a beta feature for handling tool responses automatically
    // but here we will do it manually to show how it works

    const toolUseContent = response.content.find(
      (content) => content.type === "tool_use"
    );

    if (toolUseContent) {
      const toolName = toolUseContent.name;
      const toolInput = toolUseContent.input as {
        location: string;
        unit?: string;
      };
      const toolUseId = toolUseContent.id;

      let weatherResult: string;
      if (toolName === "get_mock_weather") {
        weatherResult = getMockWeather(toolInput.location);
      } else if (toolName === "get_actual_weather") {
        weatherResult = await getWeather(
          toolInput.location,
          toolInput.unit || "celsius"
        );
      } else {
        throw new Error(`Unknown tool: ${toolName}`);
      }

      // Add assistant message with tool use
      messages.push({
        role: "assistant",
        content: response.content,
      });

      // Add tool result message
      messages.push({
        role: "user",
        content: [
          {
            type: "tool_result",
            tool_use_id: toolUseId,
            content: weatherResult,
          },
        ],
      });
    }
    // Now make a final call to get the complete response including tool output
    console.dir(messages, { depth: 10 });
    const finalResponse = await anthropic.messages.create({
      model: "claude-haiku-4-5",
      tools: tools,
      messages: messages,
      max_tokens: 1000,
    });

    // TypeScript knows the structure of response
    console.log("✅ Tools Prompt Success!");
    console.log("Tokens used:");
    console.dir(finalResponse.usage, { depth: null });

    // Check if we got a response
    if (!finalResponse.content || finalResponse.content.length === 0) {
      throw new Error("No content in response");
    }

    // Extract text
    const textBlocks = finalResponse.content.filter(
      (block) => block.type === "text"
    );

    if (textBlocks.length === 0) {
      throw new Error("No text content in response");
    }

    console.log(
      "AI Response:",
      textBlocks.map((block) => block.text).join("\n")
    );
  } catch (error) {
    // Proper error handling with type guards
    if (error instanceof Anthropic.APIError) {
      console.log("❌ API Error:", error.status, error.message);
    } else if (error instanceof Error) {
      console.log("❌ Error:", error.message);
    } else {
      console.log("❌ Unknown error occurred");
    }
  }
}

// Run the test
toolsPrompt(weatherToolType).catch((error) => {
  console.error("Error:", error);
});

Run It

# With mock weather
pnpm tsx src/anthropic/tools-prompt.ts MOCK

# With actual weather (requires WEATHER_API_KEY in .env)
pnpm tsx src/anthropic/tools-prompt.ts ACTUAL

Understanding Tool Calling Flow

1. Define Tools

Define what tools Claude can use:

const tools: Anthropic.Tool[] = [
  {
    name: "get_weather",
    description: "Get current weather for a location",
    input_schema: {
      type: "object",
      properties: {
        location: {
          type: "string",
          description: "City name, e.g., 'London, UK'"
        },
        unit: {
          type: "string",
          enum: ["celsius", "fahrenheit"],
          description: "Temperature unit"
        }
      },
      required: ["location"]
    }
  }
];

2. Initial Request

Claude decides if it needs to use a tool:

const response = await anthropic.messages.create({
  model: "claude-haiku-4-5",
  max_tokens: 1000,
  tools: tools,
  messages: [{
    role: "user",
    content: "What's the weather in Paris?"
  }]
});

// Response contains tool_use
console.log(response.content);
// [{ type: "tool_use", id: "...", name: "get_weather", input: {...} }]

3. Execute Tool

Run the actual function:

const toolUse = response.content.find(block => block.type === "tool_use");

if (toolUse && toolUse.type === "tool_use") {
  const result = await getWeather(toolUse.input.location);

  // Add to conversation
  messages.push({
    role: "assistant",
    content: response.content  // Include the tool_use
  });

  messages.push({
    role: "user",
    content: [{
      type: "tool_result",
      tool_use_id: toolUse.id,
      content: result
    }]
  });
}

4. Final Response

Claude uses the tool result:

const finalResponse = await anthropic.messages.create({
  model: "claude-haiku-4-5",
  max_tokens: 1000,
  tools: tools,
  messages: messages  // Includes tool use and result
});

// Now contains the final answer
console.log(finalResponse.content[0].text);

Complete Example: Calculator Tool

// 1. Define tool
const tools: Anthropic.Tool[] = [
  {
    name: "calculate",
    description: "Perform mathematical calculations",
    input_schema: {
      type: "object",
      properties: {
        expression: {
          type: "string",
          description: "Math expression to evaluate, e.g., '2 + 2'"
        }
      },
      required: ["expression"]
    }
  }
];

// 2. Tool implementation
function calculate(expression: string): string {
  try {
    // Simple eval (use mathjs in production!)
    const result = eval(expression);
    return String(result);
  } catch (error) {
    return `Error: ${error.message}`;
  }
}

// 3. Use with Claude
async function mathWithClaude(question: string) {
  let messages: Anthropic.Messages.MessageParam[] = [
    { role: "user", content: question }
  ];

  // Initial request
  const response = await anthropic.messages.create({
    model: "claude-haiku-4-5",
    max_tokens: 1000,
    tools: tools,
    messages: messages
  });

  // Check for tool use
  const toolUse = response.content.find(block => block.type === "tool_use");

  if (toolUse && toolUse.type === "tool_use") {
    // Execute tool
    const result = calculate(toolUse.input.expression);

    // Add to conversation
    messages.push({
      role: "assistant",
      content: response.content
    });

    messages.push({
      role: "user",
      content: [{
        type: "tool_result",
        tool_use_id: toolUse.id,
        content: result
      }]
    });

    // Get final response
    const finalResponse = await anthropic.messages.create({
      model: "claude-haiku-4-5",
      max_tokens: 1000,
      tools: tools,
      messages: messages
    });

    return finalResponse.content[0].text;
  }

  return response.content[0].text;
}

// Usage
const answer = await mathWithClaude("What is 234 × 567?");
console.log(answer);
// "234 × 567 equals 132,678"

Multiple Tools Example

const tools: Anthropic.Tool[] = [
  {
    name: "get_weather",
    description: "Get current weather",
    input_schema: {
      type: "object",
      properties: {
        location: { type: "string" }
      },
      required: ["location"]
    }
  },
  {
    name: "get_time",
    description: "Get current time",
    input_schema: {
      type: "object",
      properties: {
        timezone: { type: "string" }
      },
      required: ["timezone"]
    }
  },
  {
    name: "search_flights",
    description: "Search for flights",
    input_schema: {
      type: "object",
      properties: {
        from: { type: "string" },
        to: { type: "string" },
        date: { type: "string" }
      },
      required: ["from", "to", "date"]
    }
  }
];

// Claude will choose the right tool(s) based on the query
const response = await anthropic.messages.create({
  model: "claude-haiku-4-5",
  max_tokens: 1000,
  tools: tools,
  messages: [{
    role: "user",
    content: "I want to fly from London to Paris tomorrow. What's the weather there?"
  }]
});

// May call multiple tools: search_flights AND get_weather

Handling Tool Errors

async function executeToolSafely(toolName: string, input: any): Promise<string> {
  try {
    if (toolName === "get_weather") {
      return await getWeather(input.location);
    } else if (toolName === "calculate") {
      return calculate(input.expression);
    } else {
      return `Error: Unknown tool '${toolName}'`;
    }
  } catch (error) {
    return `Error executing ${toolName}: ${error.message}`;
  }
}

// Use in conversation
const result = await executeToolSafely(toolUse.name, toolUse.input);

messages.push({
  role: "user",
  content: [{
    type: "tool_result",
    tool_use_id: toolUse.id,
    content: result,
    is_error: result.startsWith("Error")  // Mark as error
  }]
});

OpenAI vs Anthropic Tool Calling

OpenAI Approach

const response = await openai.responses.create({
  model: "gpt-5-nano",
  tools: [...],
  input: "..."
});

// Tool call in response
const toolCall = response.tool_calls[0];

// Add tool result
messages.push({
  role: "tool",
  tool_call_id: toolCall.id,
  content: result
});

Anthropic Approach

const response = await anthropic.messages.create({
  model: "claude-haiku-4-5",
  tools: [...],
  messages: [...]
});

// Tool use in content
const toolUse = response.content.find(b => b.type === "tool_use");

// Add tool result
messages.push({
  role: "assistant",
  content: response.content
});

messages.push({
  role: "user",
  content: [{
    type: "tool_result",
    tool_use_id: toolUse.id,
    content: result
  }]
});

Key Differences

FeatureOpenAIAnthropic
Tool definitiontools arraytools array
Tool use locationresponse.tool_callscontent array
Result formatrole: "tool"type: "tool_result"
ConversationAppend tool messageAssistant + user messages

Best Practices

1. Clear Tool Descriptions

{
  name: "search_database",
  description: "Search the customer database by name, email, or ID. Returns customer details including order history.",
  input_schema: {...}
}

2. Validate Tool Input

if (toolUse.type === "tool_use") {
  // Validate before executing
  if (!toolUse.input.location) {
    throw new Error("Missing required parameter: location");
  }

  const result = await getWeather(toolUse.input.location);
}

3. Handle Multiple Tool Calls

// Claude might request multiple tools
const toolUses = response.content.filter(block => block.type === "tool_use");

for (const toolUse of toolUses) {
  const result = await executeTool(toolUse.name, toolUse.input);

  // Add each result
  messages.push({
    role: "user",
    content: [{
      type: "tool_result",
      tool_use_id: toolUse.id,
      content: result
    }]
  });
}

4. Provide Context in System Prompt

system: `You are a travel assistant with access to:
- get_weather: Current weather conditions
- search_flights: Flight availability
- book_hotel: Hotel reservations

Always check weather before suggesting destinations.
Use search_flights to find the best options.`

Common Use Cases

1. API Integration

async function callAPI(endpoint: string, params: any) {
  const response = await fetch(`https://api.example.com/${endpoint}`, {
    method: 'POST',
    body: JSON.stringify(params)
  });
  return await response.json();
}

2. Database Queries

async function queryDatabase(query: string) {
  const results = await db.execute(query);
  return JSON.stringify(results);
}

3. File Operations

async function readFile(path: string) {
  const content = await fs.readFile(path, 'utf-8');
  return content;
}

Key Takeaways

  • ✅ Define tools with clear descriptions and schemas
  • ✅ Tool use appears in content array (not separate property)
  • ✅ Must add both assistant message AND tool_result message
  • ✅ Claude decides when to use tools based on context
  • ✅ Can handle multiple tools in one conversation

Module 2 Complete!

Congratulations! You've completed Module 2 and learned how to use Anthropic Claude API. You now know how to:

  • ✅ Make basic API calls to Claude
  • ✅ Use system prompts effectively
  • ✅ Control response creativity with temperature
  • ✅ Craft detailed extended prompts
  • ✅ Stream responses in real-time
  • ✅ Get validated structured output
  • ✅ Integrate external tools and APIs

What's Next?

  • Compare OpenAI and Anthropic implementations side-by-side
  • Build a multi-provider application
  • Explore advanced features and optimizations
  • Apply your skills to real projects!

Back to Module 2 Overview


Quick Reference

// Tool calling pattern
const tools = [{ name: "...", description: "...", input_schema: {...} }];

const response = await anthropic.messages.create({
  model: "claude-haiku-4-5",
  max_tokens: 1000,
  tools: tools,
  messages: messages
});

const toolUse = response.content.find(b => b.type === "tool_use");

if (toolUse && toolUse.type === "tool_use") {
  const result = await executeTool(toolUse.name, toolUse.input);

  messages.push({ role: "assistant", content: response.content });
  messages.push({
    role: "user",
    content: [{ type: "tool_result", tool_use_id: toolUse.id, content: result }]
  });

  const final = await anthropic.messages.create({
    model: "claude-haiku-4-5",
    max_tokens: 1000,
    tools: tools,
    messages: messages
  });
}