Function Calling & Tool Use

Enable AI agents to use external tools and APIs

11 min readUpdated 11/9/2025
💡ELI5: What is Function Calling?

Imagine you have a smart assistant who can't just talk, but can also use tools! Function calling is like giving AI a toolbox - it can use a calculator, look up the weather, send emails, or check your calendar. Instead of just chatting, it can actually DO things!

When you ask "What's the weather?", the AI doesn't guess - it picks up the "weather tool" from its toolbox, uses it to check the real weather, and tells you the actual answer.

Example: You: "What's 847 × 293?" → AI thinks: "I need my calculator tool!" → Uses calculator(847, 293) → Answers: "248,071"

🛠️For Product Managers & Builders

When to Use Function Calling

Perfect for:
  • • Querying databases or external APIs
  • • Taking actions (send email, create task, update record)
  • • Getting real-time data (weather, stock prices, news)
  • • Enforcing structured outputs (valid JSON)
  • • Building autonomous agents with capabilities
Less suitable for:
  • • Pure text generation without external data
  • • Tasks where deterministic code is simpler
  • • Operations where tool latency is prohibitive
  • • Simple calculations better handled in code

Why Function Calling Matters

Grounded in Reality
Fetch real data instead of hallucinating
Action-Oriented
Agents that can actually do things
Structured Outputs
Reliable JSON for production systems
Composability
Chain multiple tools for complex tasks
Deep Dive

Function Calling & Tool Use

Function calling (also called tool use) is what transforms LLMs from text generators into agents that can interact with the world. Instead of just producing text, models can call functions in your code, query APIs, search databases, and take actions.

What is Function Calling?

Function calling lets you describe functions to an LLM, and the model can choose to invoke them when appropriate. The model doesn't execute the function—it returns a structured request for your code to execute.

The flow:

  1. You describe available functions to the model
  2. User makes a request
  3. Model decides if it needs to call a function
  4. Model returns function name and arguments (JSON)
  5. Your code executes the function
  6. You send the result back to the model
  7. Model generates a final response

Why Function Calling Matters

Grounded in reality: Instead of hallucinating data, models can fetch real information from APIs and databases.

Action-oriented agents: Models can send emails, create calendar events, update databases—anything your functions enable.

Structured outputs: Function calls return valid JSON, not freeform text. This reliability is essential for production systems.

Composability: Chain multiple function calls to accomplish complex tasks.

How It Works

Step 1: Define Your Functions

Describe your functions in a JSON schema:

const tools = [{
  name: "get_weather",
  description: "Get current weather for a location",
  input_schema: {
    type: "object",
    properties: {
      location: {
        type: "string",
        description: "City and state, e.g. San Francisco, CA"
      },
      unit: {
        type: "string",
        enum: ["celsius", "fahrenheit"],
        description: "Temperature unit"
      }
    },
    required: ["location"]
  }
}]

Step 2: Send to the Model

Include tool definitions in your API call:

const response = await anthropic.messages.create({
  model: "claude-sonnet-4.5",
  max_tokens: 1024,
  tools: tools,
  messages: [{
    role: "user",
    content: "What's the weather in SF?"
  }]
})

Step 3: Handle Tool Calls

The model returns a tool use request:

{
  type: "tool_use",
  name: "get_weather",
  input: {
    location: "San Francisco, CA",
    unit: "fahrenheit"
  }
}

Step 4: Execute and Return Result

Call the actual function and send results back:

// Execute the function
const weatherData = await getWeather("San Francisco, CA", "fahrenheit")

// Send result back to model
const finalResponse = await anthropic.messages.create({
  model: "claude-sonnet-4.5",
  max_tokens: 1024,
  tools: tools,
  messages: [
    ...previousMessages,
    {
      role: "user",
      content: "What's the weather in SF?"
    },
    {
      role: "assistant",
      content: response.content // includes tool_use block
    },
    {
      role: "user",
      content: [{
        type: "tool_result",
        tool_use_id: response.content[0].id,
        content: JSON.stringify(weatherData)
      }]
    }
  ]
})

Step 5: Get Final Response

The model incorporates the tool result:

"It's currently 65°F and sunny in San Francisco."

Designing Effective Tools

Clear Descriptions

The model decides whether to use a tool based solely on your description:

// BAD
description: "Gets weather"

// GOOD
description: "Returns current weather conditions including temperature, conditions, humidity, and wind speed for a specific location. Use when users ask about current weather."

Granular Tools

Better to have multiple specific tools than one swiss-army-knife:

// LESS EFFECTIVE
{
  name: "database_query",
  description: "Query any database table"
}

// MORE EFFECTIVE
{
  name: "get_user_by_id",
  description: "Fetch user details by user ID"
},
{
  name: "search_users",
  description: "Search users by name or email"
},
{
  name: "update_user_profile",
  description: "Update a user's profile information"
}

Strong Type Definitions

Use JSON schema to enforce correct inputs:

{
  name: "send_email",
  input_schema: {
    type: "object",
    properties: {
      to: {
        type: "string",
        format: "email",
        description: "Recipient email address"
      },
      subject: {
        type: "string",
        maxLength: 100
      },
      body: {
        type: "string"
      },
      priority: {
        type: "string",
        enum: ["low", "normal", "high"],
        default: "normal"
      }
    },
    required: ["to", "subject", "body"]
  }
}

Parallel Tool Use

Modern models can call multiple tools in parallel:

// User: "What's the weather in SF and NYC?"

// Model returns TWO tool calls:
[
  {
    type: "tool_use",
    name: "get_weather",
    input: { location: "San Francisco, CA" }
  },
  {
    type: "tool_use",
    name: "get_weather",
    input: { location: "New York, NY" }
  }
]

Execute them in parallel for speed:

const results = await Promise.all([
  getWeather("San Francisco, CA"),
  getWeather("New York, NY")
])

Common Tool Patterns

Information Retrieval

{
  name: "search_documentation",
  description: "Search internal product documentation",
  input_schema: {
    type: "object",
    properties: {
      query: { type: "string" }
    }
  }
}

Data Mutation

{
  name: "create_issue",
  description: "Create a new issue in the issue tracker",
  input_schema: {
    type: "object",
    properties: {
      title: { type: "string" },
      description: { type: "string" },
      priority: { type: "string", enum: ["low", "medium", "high"] }
    },
    required: ["title"]
  }
}

Computation

{
  name: "calculate_tax",
  description: "Calculate tax amount based on subtotal and tax rate",
  input_schema: {
    type: "object",
    properties: {
      subtotal: { type: "number" },
      tax_rate: { type: "number" }
    },
    required: ["subtotal", "tax_rate"]
  }
}

External API Integration

{
  name: "get_stock_price",
  description: "Get current stock price for a ticker symbol",
  input_schema: {
    type: "object",
    properties: {
      symbol: {
        type: "string",
        description: "Stock ticker symbol (e.g., AAPL)"
      }
    },
    required: ["symbol"]
  }
}

Error Handling

Tools can fail. Handle errors gracefully:

async function executeTool(toolName, input) {
  try {
    return await tools[toolName](input)
  } catch (error) {
    return {
      error: true,
      message: error.message
    }
  }
}

// Send error back to model
{
  type: "tool_result",
  tool_use_id: toolUseId,
  content: JSON.stringify({
    error: "API rate limit exceeded. Try again in 60 seconds."
  }),
  is_error: true
}

The model will adapt its response: "I'm sorry, I couldn't fetch that information right now due to rate limiting. Could you try again in a minute?"

Security Considerations

Validate Tool Inputs

Never trust tool inputs blindly:

function deleteUser(input) {
  // Validate
  if (!input.userId || typeof input.userId !== 'string') {
    throw new Error("Invalid user ID")
  }

  // Check authorization
  if (!currentUser.canDelete(input.userId)) {
    throw new Error("Unauthorized")
  }

  // Execute
  return database.deleteUser(input.userId)
}

Implement Confirmations

For destructive actions, require confirmation:

{
  name: "delete_all_data",
  description: "Delete all user data. REQUIRES CONFIRMATION from user before executing.",
  // ...
}

// In your code
if (toolName === "delete_all_data" && !userConfirmed) {
  return {
    needsConfirmation: true,
    message: "This will delete all data. Are you sure?"
  }
}

Least Privilege

Only expose tools the agent actually needs:

// For customer support bot
const supportTools = [
  tools.search_docs,
  tools.create_ticket,
  tools.get_order_status
]

// For admin bot
const adminTools = [
  ...supportTools,
  tools.refund_order,
  tools.delete_user
]

Debugging Tool Use

Log All Tool Calls

console.log({
  timestamp: new Date(),
  tool: toolName,
  input: input,
  result: result,
  userId: currentUser.id
})

Test Tools Independently

Before integrating with LLMs, ensure tools work correctly:

// Unit test
expect(await getWeather("San Francisco, CA"))
  .toMatchObject({
    temperature: expect.any(Number),
    conditions: expect.any(String)
  })

Monitor Tool Success Rates

Track which tools fail frequently and why:

{
  tool: "search_docs",
  successRate: 0.95,
  avgLatency: "150ms",
  commonErrors: ["Timeout", "No results found"]
}

Best Practices

  1. Descriptive names: Use get_user_by_id not fetch_user
  2. Complete descriptions: Explain when and why to use each tool
  3. Strong schemas: Leverage JSON schema validation fully
  4. Fast tools: Keep tool execution under 2 seconds when possible
  5. Graceful degradation: Return partial data rather than failing completely
  6. Audit logs: Record all tool invocations for debugging and security

When to Use Function Calling

Perfect for:

  • Querying databases or APIs
  • Taking actions (send email, create task)
  • Getting real-time data (weather, stock prices)
  • Enforcing structured outputs
  • Building autonomous agents

Less suitable for:

  • Pure text generation
  • When deterministic logic is better (use regular code)
  • Tasks where tool latency is prohibitive

Function calling is the bridge between LLMs and the real world. Master it, and you can build agents that don't just talk—they act.

Related Resources

Continue Learning

Ready to Build Agents with Tools?
Explore frameworks and patterns for function calling