Function Calling & Tool Use
Enable AI agents to use external tools and APIs
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"
When to Use Function Calling
- • 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
- • 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
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:
- You describe available functions to the model
- User makes a request
- Model decides if it needs to call a function
- Model returns function name and arguments (JSON)
- Your code executes the function
- You send the result back to the model
- 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
- Descriptive names: Use
get_user_by_idnotfetch_user - Complete descriptions: Explain when and why to use each tool
- Strong schemas: Leverage JSON schema validation fully
- Fast tools: Keep tool execution under 2 seconds when possible
- Graceful degradation: Return partial data rather than failing completely
- 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.