Skip to content

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

pip install openai anthropic google-genai

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_choice when 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

Official Documentation

Last Updated

2026-09-06 — Initial version.