Agent Tool Calling in Practice: Let AI Call External APIs and Services
Ever had this happen? You want AI to check the weather, read a file, or call an API, and it just responds with “I cannot access external data.”
Pretty frustrating, right.
It’s not that the AI isn’t smart enough—it’s missing a core capability called tool calling. Today, let’s talk about the technology that transforms AI from “just chatting” to “actually doing work.”
What is AI Tool Calling?
Simply put, tool calling gives AI a pair of hands.
Traditional large language models can only answer questions based on their training data. Ask “What’s the weather in Beijing today?” and they’ll tell you “I cannot access real-time data.” But with tool calling, AI can actively request to execute a function—like calling a weather API—and return the results to you.
How important is this capability? Well, it’s like the difference between a strategist who only knows theory and a general who can actually lead troops into battle.
Three Main Approaches
Currently, there are three main tool calling solutions on the market:
| Approach | Representative Product | Use Case |
|---|---|---|
| Function Calling | OpenAI GPT | Structured output, simple API calls |
| Tool Use | Claude | Complex tool chains, multi-step tasks |
| MCP | Claude Code | Standardized tool ecosystem |
Which one to choose? It depends on your needs. OpenAI Function Calling is enough for simple scenarios; Claude Tool Use is better for complex agent systems; MCP is the trend for building tool ecosystems.
OpenAI Function Calling: From Basics to Practice
Let’s look at OpenAI’s approach first. Its Function Calling is designed to be simple, with three core steps:
- Define tools (tell AI what tools are available)
- AI decides which tool to call (returns function name and parameters)
- You execute the tool and feed back the results
A Complete Example
Let’s say we want to build a weather query tool. First, define the tool Schema:
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather information for a specified city",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "City name, e.g. 'Beijing', 'Shanghai'"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit, defaults to Celsius"
}
},
"required": ["city"]
}
}
}]
Pay attention to that description field—don’t be lazy with it. AI relies on it to determine when to use this tool. I’ve seen people write “get weather” and the AI had no clue when to use it. After changing it to “Get current weather information for a specified city,” the call accuracy jumped from 60% to 95%.
Next, send the request:
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "user", "content": "Is it hot in Beijing today?"}
],
tools=tools
)
AI will return a tool call request:
tool_call = response.choices[0].message.tool_calls[0]
# tool_call.function.name = "get_weather"
# tool_call.function.arguments = '{"city": "Beijing"}'
Then you execute the actual function call and return the results:
# Execute your weather API call
weather_result = get_weather_from_api("Beijing")
# Feed back the results
final_response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "user", "content": "Is it hot in Beijing today?"},
response.choices[0].message, # AI's tool call request
{
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(weather_result)
}
]
)
AI will then give you a natural response based on the weather data: “It’s 28°C in Beijing today—pretty hot. Don’t forget sunscreen if you’re heading out.”
Strict Mode: Ensuring Output Stability
In 2024, OpenAI introduced Strict Mode, which solves the JSON Schema matching instability issue. Enabling it is simple:
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"strict": True, # Add this line
# ... other fields
}
}]
Once enabled, AI-returned parameters are guaranteed to 100% match your defined Schema. Strongly recommended for production environments—otherwise you might encounter all sorts of weird parsing errors.
Claude Tool Use: More Powerful Tool Chains
Claude’s Tool Use has several important design differences from OpenAI.
Parallel Calling
Claude supports returning multiple tool calls at once. For example, if a user asks “Compare the weather in Beijing and Shanghai,” Claude will request two get_weather calls simultaneously:
response = client.messages.create(
model="claude-sonnet-4-20250514",
messages=[{"role": "user", "content": "Compare the weather in Beijing and Shanghai"}],
tools=tools
)
# response.content may contain two tool_use blocks
for block in response.content:
if block.type == "tool_use":
print(f"Calling {block.name} with params: {block.input}")
This is particularly useful for complex tasks. OpenAI also supports parallel calling, but Claude’s implementation is more elegant—it intelligently determines which calls can be parallelized and which must be sequential.
Tool Choice Strategy
Claude provides more granular tool selection control:
# Auto select (default)
tool_choice = {"type": "auto"}
# Force tool usage (won't answer without tools)
tool_choice = {"type": "any"}
# Specify a particular tool
tool_choice = {"type": "tool", "name": "get_weather"}
When to use any? When you’re certain the user’s question can only be answered through a tool. For example, checking order status—without querying the database, there’s no way to answer, so force AI to use the tool.
Error Handling
Tool call failures are common. API timeouts, network issues, parameter errors… Claude provides an elegant error handling mechanism:
tool_result = {
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": "API call failed: connection timeout", # Tell AI why it failed
"is_error": True # Mark as error
}
When AI sees an error, it will try other approaches or give users a friendly message. This is much better than throwing exceptions directly—users won’t see a bunch of technical errors, but rather “Sorry, weather service is temporarily unavailable. Please try again later.”
MCP: The Future of Tool Standardization
When talking about tool calling, we can’t ignore MCP (Model Context Protocol).
Why Do We Need MCP?
The current tool ecosystem is too fragmented. Want to connect a GitHub tool for Claude and a Slack tool for ChatGPT? Each requires separate development. MCP’s goal is to establish a unified standard—write a tool once, use it everywhere.
The architecture is simple:
MCP Client (Claude Code/Claude Desktop)
↕
MCP Server (Tool Provider)
↕
External Tool/API
Claude Code’s MCP Practice
Claude Code currently has the best MCP support. Use the /mcp command to configure tools:
# Add a remote MCP server
claude mcp add my-server --transport sse --url https://api.example.com/mcp
# Add a local tool
claude mcp add local-tool --command node ./my-tool.js
Once configured, Claude Code automatically discovers tools provided by the server. Mention something relevant in the conversation, and it will call the tool automatically.
For example, I configured a get-github-issues tool, then asked Claude Code: “What are the open issues for this project?” It called the tool directly and organized the results for me.
The whole process—I don’t even notice the tool exists. That’s the ideal state of tool calling.
Production Pitfalls to Avoid
Tool calling sounds simple, but once you hit production, there are quite a few traps.
Security: Don’t Let AI Run Wild
AI might call APIs you don’t want it to call. Solutions:
- Tool permission levels: Only give AI necessary tools; sensitive operations require human confirmation
- Input validation: Always validate AI-generated parameters before passing to APIs
- Call auditing: Log every tool call’s parameters and results for traceability
I’ve seen a counterexample: AI passed user’s random input directly to a database query interface, resulting in… SQL injection. The AI didn’t inject it directly, but it passed the malicious input through. So, never trust AI-generated parameters.
Timeouts and Retries
Tool calls can fail. Set reasonable timeout periods with retry mechanisms:
async def call_tool_with_retry(tool_func, args, max_retries=3):
for attempt in range(max_retries):
try:
return await asyncio.wait_for(
tool_func(**args),
timeout=10.0 # 10 second timeout
)
except asyncio.TimeoutError:
if attempt == max_retries - 1:
return {"error": "Tool call timeout"}
await asyncio.sleep(1) # Wait 1 second before retry
Token Costs
Tool definitions and returned results both consume tokens. Especially when returning large amounts of data, costs can be high. A few suggestions:
- Keep descriptions concise: Make description short but clear
- Limit returned data: Filter API returns to only keep necessary fields
- Use caching: Cache results for identical queries for a few minutes
Final Thoughts
Tool calling is a core capability of AI Agents. Without it, AI can only theorize; with it, AI can actually help you get work done.
OpenAI’s Function Calling is simple and easy, suitable for beginners and simple scenarios; Claude’s Tool Use is more powerful, suitable for complex agent systems; MCP is the trend for tool standardization, worth long-term attention.
Which one to choose? It depends on your needs. But whichever you choose, make sure you think through these three pitfalls in advance: security, error handling, and performance optimization.
References
- Claude Tool Use Official Documentation
- OpenAI Function Calling Guide
- Model Context Protocol Specification
- Claude Code MCP Tool Configuration
- Anthropic Advanced Tool Use Blog
FAQ
What's the difference between OpenAI Function Calling and Claude Tool Use?
When should I use MCP instead of direct Function Calling?
How should I handle tool call failures?
How can I prevent AI from calling sensitive APIs?
What is Strict Mode and should I enable it?
8 min read · Published on: Mar 21, 2026 · Modified on: Mar 22, 2026
Related Posts
Computer-Use Agent: Let AI Operate Your Computer
Computer-Use Agent: Let AI Operate Your Computer
RAG + Agent: Next-Generation AI Application Architecture
RAG + Agent: Next-Generation AI Application Architecture
AI Agent Development in Practice: Architecture Design and Implementation Guide

Comments
Sign in with GitHub to leave a comment