Tool Calling¶
Tool calling is how LLMs invoke external functions. The model generates a structured JSON request (tool name + arguments), your code executes it, and you send the result back. Every major provider supports this — the mechanics are similar but the APIs differ.
Table of Contents¶
- Installation / Setup
- Core Syntax
- Common Operations
- Practical Examples
- Common Patterns
- Best Practices
- Common Mistakes
- Troubleshooting
- Related Cheat Sheets
- Official Documentation
Installation / Setup¶
Core Syntax¶
Tool calling flow¶
sequenceDiagram
participant U as User
participant LLM as LLM
participant App as Your App
participant Tool as Tool
U->>LLM: "What's the weather in London?"
LLM->>App: tool_call: get_weather(city="London")
App->>Tool: get_weather("London")
Tool->>App: "72°F, sunny"
App->>LLM: tool_result: "72°F, sunny"
LLM->>U: "The weather in London is 72°F and sunny."
OpenAI tool calling¶
from openai import OpenAI
client = OpenAI(api_key="YOUR_API_KEY")
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather for a city",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name"},
},
"required": ["city"],
},
},
}]
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Weather in London?"}],
tools=tools,
)
# check for tool calls
msg = response.choices[0].message
if msg.tool_calls:
for tc in msg.tool_calls:
print(f"Tool: {tc.function.name}")
print(f"Args: {tc.function.arguments}")
Anthropic tool calling¶
import anthropic
client = anthropic.Anthropic(api_key="YOUR_API_KEY")
tools = [{
"name": "get_weather",
"description": "Get weather for a city",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name"},
},
"required": ["city"],
},
}]
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=[{"role": "user", "content": "Weather in London?"}],
tools=tools,
)
# check for tool use blocks
for block in response.content:
if block.type == "tool_use":
print(f"Tool: {block.name}, Input: {block.input}")
Gemini tool calling¶
from google import genai
from google.genai import types
client = genai.Client(api_key="YOUR_API_KEY")
def get_weather(city: str) -> dict:
"""Get weather for a city."""
return {"temperature": 72, "condition": "sunny"}
response = client.models.generate_content(
model="gemini-2.0-flash",
contents="Weather in London?",
config=types.GenerateContentConfig(
tools=[types.Tool(function_declarations=[
types.FunctionDeclaration(
name="get_weather",
description="Get weather for a city",
parameters=types.Schema(
type=types.Type.OBJECT,
properties={"city": types.Schema(type=types.Type.STRING)},
required=["city"],
),
),
])],
),
)
LangChain tool calling¶
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
@tool
def get_weather(city: str) -> str:
"""Get weather for a city."""
return f"Weather in {city}: 72°F"
llm = ChatOpenAI(model="gpt-4o").bind_tools([get_weather])
response = llm.invoke("Weather in London?")
# execute tools
for tc in response.tool_calls:
result = get_weather.invoke(tc["args"])
Common Operations¶
Handling tool results (OpenAI)¶
import json
def handle_tool_calls(response, tools_map):
messages = [{"role": "assistant", "content": response.choices[0].message.content or ""}]
messages[-1]["tool_calls"] = [
{"id": tc.id, "type": "function", "function": {"name": tc.function.name, "arguments": tc.function.arguments}}
for tc in response.choices[0].message.tool_calls
]
for tc in response.choices[0].message.tool_calls:
result = tools_map[tc.function.name](**json.loads(tc.function.arguments))
messages.append({
"role": "tool",
"tool_call_id": tc.id,
"content": str(result),
})
return messages
Handling tool results (Anthropic)¶
def handle_anthropic_tools(response, tools_map):
messages = [{"role": "user", "content": "Weather in London?"}]
messages.append({"role": "assistant", "content": response.content})
for block in response.content:
if block.type == "tool_use":
result = tools_map[block.name](**block.input)
messages.append({"role": "user", "content": [{
"type": "tool_result",
"tool_use_id": block.id,
"content": str(result),
}]})
return messages
Parallel tool calls¶
# OpenAI supports parallel tool calls by default
# model can request multiple tools in one response
for tc in response.choices[0].message.tool_calls:
# execute all tool calls
pass
Practical Examples¶
Example 1 — Full tool-calling loop (OpenAI)¶
from openai import OpenAI
import json
client = OpenAI(api_key="YOUR_API_KEY")
tools_map = {
"get_weather": lambda city: f"{city}: 72°F, sunny",
"search": lambda query: f"Results for: {query}",
}
tools = [{"type": "function", "function": {"name": n, "description": d,
"parameters": {"type": "object", "properties": {"city": {"type": "string"}, "query": {"type": "string"}}, "required": []}}}
for n, d in [("get_weather", "Get weather"), ("search", "Search web")]]
messages = [{"role": "user", "content": "What's the weather in Paris and search for restaurants?"}]
for _ in range(5):
response = client.chat.completions.create(model="gpt-4o", messages=messages, tools=tools)
msg = response.choices[0].message
if not msg.tool_calls:
print(msg.content)
break
messages.append({"role": "assistant", "content": msg.content or "", "tool_calls": [
{"id": tc.id, "type": "function", "function": {"name": tc.function.name, "arguments": tc.function.arguments}}
for tc in msg.tool_calls
]})
for tc in msg.tool_calls:
result = tools_map[tc.function.name](**json.loads(tc.function.arguments))
messages.append({"role": "tool", "tool_call_id": tc.id, "content": str(result)})
Common Patterns¶
Pattern: tool error handling¶
def safe_tool_call(tool_func, args):
try:
return tool_func(**args)
except Exception as e:
return f"Error: {type(e).__name__}: {e}"
Pattern: tool result caching¶
cache = {}
def cached_tool(tool_func, args):
key = f"{tool_func.__name__}:{json.dumps(args, sort_keys=True)}"
if key not in cache:
cache[key] = tool_func(**args)
return cache[key]
Pattern: tool_choice control¶
# force tool use
response = client.chat.completions.create(
model="gpt-4o", messages=messages, tools=tools,
tool_choice={"type": "function", "function": {"name": "get_weather"}},
)
# force no tools
response = client.chat.completions.create(
model="gpt-4o", messages=messages, tools=tools,
tool_choice="none",
)
Best Practices¶
- Tool schemas matter — good descriptions + types = correct tool selection.
- Always handle errors — return error messages to the LLM, don't crash.
- Limit tool call recursion — max 5–10 rounds to prevent loops.
- Cache tool results — same inputs = same outputs; save API calls.
- Use
tool_choicewhen you need deterministic tool use. - Validate tool outputs — truncate long results, sanitize dangerous content.
Common Mistakes¶
| Mistake | Fix |
|---|---|
| Not handling tool errors | Catch exceptions; return error strings |
| No recursion limit | Add max rounds to prevent infinite loops |
| Ignoring parallel tool calls | Process all tool_calls in one response |
| Hard-coding tool results | Always execute tools dynamically |
| Missing required parameters | Check parameter schema matches tool signature |
Troubleshooting¶
| Symptom | Cause / Fix |
|---|---|
| Model doesn't call tools | Tool descriptions are vague — rewrite them |
| Model calls wrong tool | Tool names/descriptions overlap — differentiate |
| Tool call fails | Check argument types match schema |
| Infinite tool loop | Add max recursion limit |
tool_call_id mismatch |
Ensure IDs match between call and result |
Related Cheat Sheets¶
- tools.md — tool design and patterns
- ai-agents.md — agent loop overview
- openai-api — OpenAI API
- anthropic-api — Anthropic API
- google-gemini — Gemini API
- langchain — LangChain tools
Official Documentation¶
- OpenAI function calling — https://platform.openai.com/docs/guides/function-calling
- Anthropic tool use — https://docs.anthropic.com/en/docs/build-with-claude/tool-use
- Gemini function calling — https://ai.google.dev/gemini-api/docs/function-calling
Last Updated¶
2026-09-06 — Initial version.