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
| Feature | OpenAI | Anthropic |
|---|---|---|
| Tool definition | tools array | tools array |
| Tool use location | response.tool_calls | content array |
| Result format | role: "tool" | type: "tool_result" |
| Conversation | Append tool message | Assistant + 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
contentarray (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!
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 }); }