Tutorials·tutorial

Tool Calling Explained: How AI Agents Use External Tools

The landscape of Artificial Intelligence is rapidly evolving, with Large Language Models (LLMs) at its forefront. While LLMs excel at understanding and generating human-like text, their true power is...

June 21, 202613 min read
Featured image for Tool Calling Explained: How AI Agents Use External Tools

The landscape of Artificial Intelligence is rapidly evolving, with Large Language Models (LLMs) at its forefront. While LLMs excel at understanding and generating human-like text, their true power is unleashed when they can interact with the outside world. This interaction is made possible through a crucial capability known as tool calling, transforming static language models into dynamic, intelligent agents capable of performing complex tasks.

In this tutorial, you'll embark on a deep dive into the mechanics of tool calling, understanding how AI agents leverage external tools to overcome inherent LLM limitations. We'll explore the underlying principles, walk through a practical implementation example, and discuss best practices to build robust and effective AI agents. By the end, you'll have a solid grasp of how to empower your LLMs to not just generate text, but to act upon the world.

Prerequisites: Basic understanding of Large Language Models (LLMs) and Python programming. No advanced AI knowledge is required, making this guide accessible for beginners eager to explore agentic AI.

Time Estimate: Approximately 30-45 minutes to read and understand, plus additional time for hands-on experimentation.

What is Tool Calling in Large Language Models?

Tool calling, at its core, is the ability of a Large Language Model (LLM) to identify when it needs to use an external function or API to fulfill a user's request, and then to generate the correct arguments to call that function. Think of it as giving a highly intelligent assistant access to a suite of specialized gadgets and instructing them on when and how to use each one. Instead of just answering questions based on its training data, the LLM can now perform actions, retrieve real-time information, or execute complex calculations.

This capability is fundamental because LLMs, by themselves, have several inherent limitations. They lack real-time information, cannot perform complex mathematical operations with perfect accuracy, and cannot interact with external systems like databases, web services, or user interfaces. Tool calling bridges this gap, allowing the LLM to delegate specific tasks to external, purpose-built tools. This delegation transforms the LLM from a mere text generator into an active problem-solver, extending its reach far beyond its original training data and enabling it to engage with the dynamic, real-world environment.

The process typically involves the LLM receiving a user prompt, analyzing its intent, and then deciding if any of the pre-defined tools can help achieve the goal. If a tool is deemed necessary, the LLM constructs a "tool call" – essentially a structured output specifying the tool's name and the parameters it needs. This tool call is then intercepted by an external orchestrator (often a developer's code), which executes the actual tool. The output of the tool is then fed back to the LLM, providing it with updated context to continue the conversation or generate a final response. This iterative loop of thought, action, and observation is what makes tool calling so powerful for building sophisticated AI agents.

Key Concept: Tool calling empowers LLMs to transcend their knowledge boundaries by leveraging external functions, allowing them to access real-time data, perform precise computations, and interact with the outside world.

How Do AI Agents Decide Which Tool to Use?

The decision-making process for an AI agent in selecting and utilizing tools is a fascinating blend of prompt engineering, internal reasoning, and structured data. It's not magic; rather, it's a carefully designed interaction where the LLM is given explicit instructions and descriptions of the available tools. The agent's ability to choose the right tool hinges on the quality of these descriptions and the clarity of its guiding system prompt.

At the heart of this decision lies the concept of tool descriptions. For each available tool, developers provide the LLM with a clear, concise natural language description of what the tool does, along with a schema defining its expected input parameters (their names, types, and descriptions). This information is typically embedded within the system message or as part of the tool definitions passed to the LLM API. When the LLM receives a user's query, it compares the intent of the query against the descriptions of all available tools. For instance, if a user asks "What's the weather like in London today?", the LLM will scan its tool descriptions and likely find a tool like get_current_weather(location: str) with a description such as "Retrieves the current weather conditions for a specified city."

Beyond matching keywords, the LLM often employs an internal reasoning process, sometimes referred to as "chain-of-thought" (CoT) or an "internal monologue." Before outputting a tool call, the LLM might internally generate thoughts about why a particular tool is appropriate, what parameters it needs, and how the tool's output will contribute to the overall goal. This internal deliberation helps the LLM to not only select the correct tool but also to accurately extract the necessary arguments from the user's prompt. For example, from "What's the weather like in London today?", the LLM would deduce that the location parameter for the get_current_weather tool should be "London". The quality of the LLM's decision-making is directly proportional to the clarity and specificity of the tool definitions and the robustness of the prompt engineering that guides its reasoning.

Step-by-Step Guide: Implementing a Basic Tool-Calling Agent

Let's build a practical example to illustrate how tool calling works. We'll create a simple agent that can answer questions about the current time using a Python function as its external tool. For this tutorial, we'll use the OpenAI API, but the concepts are transferable to other LLM providers that support tool/function calling.

Prerequisites:

  • Python 3.7+ installed
  • An OpenAI API key (you can get one from OpenAI's platform)
  • Install the OpenAI Python library: pip install openai

Step 1: Define Your Tool(s)

First, we need a Python function that represents our external tool. This function will get the current time. Then, we need to describe this tool to the LLM using a structured format, typically a JSON schema.


import datetime
import pytz # for timezone awareness

def get_current_time(timezone: str = "UTC") -> str:
    """
    Returns the current time in a specified timezone.

    Args:
        timezone (str): The timezone to get the current time for (e.g., "America/New_York", "Europe/London", "Asia/Tokyo").
                        Defaults to "UTC" if not specified.
    Returns:
        str: A string representing the current time.
    """
    try:
        tz = pytz.timezone(timezone)
        now = datetime.datetime.now(tz)
        return now.strftime("%Y-%m-%d %H:%M:%S %Z%z")
    except pytz.UnknownTimeZoneError:
        return f"Error: Unknown timezone '{timezone}'. Please provide a valid timezone (e.g., 'America/New_York')."

# Define the tool description for the LLM
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_current_time",
            "description": "Get the current time in a specified timezone. Useful for answering questions about the current time.",
            "parameters": {
                "type": "object",
                "properties": {
                    "timezone": {
                        "type": "string",
                        "description": "The timezone (e.g., 'America/New_York', 'Europe/London'). Defaults to 'UTC'.",
                    }
                },
                "required": [], # timezone is optional
            },
        },
    }
]

[IMAGE: Screenshot of the Python function definition and the JSON schema for the get_current_time tool]

Notice how the tools list contains a dictionary describing our function. The name must match our Python function's name, the description is crucial for the LLM to understand its purpose, and parameters define the expected inputs using JSON Schema conventions.

Step 2: Initialize Your LLM Client

Next, we set up our connection to the OpenAI API.


import os
from openai import OpenAI

# Ensure you have your API key set as an environment variable or replace os.getenv with your key directly
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

[IMAGE: Screenshot of the OpenAI client initialization code]

It's best practice to store your API key as an environment variable for security reasons.

Step 3: Craft the System Prompt

The system prompt sets the context and persona for the LLM. It's where you instruct the LLM on its role and, implicitly, how to use the tools it has access to. For tool calling, the tool definitions themselves often provide sufficient instruction, but a good system prompt can enhance its reasoning.


messages = [
    {
        "role": "system",
        "content": "You are a helpful AI assistant. You have access to tools to get real-time information. Use them wisely."
    }
]

[IMAGE: Screenshot of the initial system prompt structure]

We'll add user messages and tool outputs to this messages list as the conversation progresses.

Step 4: Engage the LLM and Process Tool Calls

This is the core logic. We send a user query to the LLM, check if it wants to call a tool, execute the tool if it does, and then send the tool's output back to the LLM.


import json # Import json for parsing tool call arguments

def run_conversation(user_message: str):
    # Add the user's message to the conversation history
    messages.append({"role": "user", "content": user_message})

    # First API call: Send user message and available tools to the LLM
    response = client.chat.completions.create(
        model="gpt-4o", # Or "gpt-3.5-turbo", "gpt-4-turbo", etc.
        messages=messages,
        tools=tools, # Pass the defined tools to the LLM
        tool_choice="auto", # Allow the LLM to decide whether to call a tool or respond directly
    )

    response_message = response.choices[0].message
    messages.append(response_message) # Add LLM's response to history

    # Check if the LLM decided to call a tool
    if response_message.tool_calls:
        print(f"DEBUG: LLM wants to call a tool: {response_message.tool_calls}")
        for tool_call in response_message.tool_calls:
            function_name = tool_call.function.name
            function_args = json.loads(tool_call.function.arguments) # Parse arguments from JSON string

            # Execute the tool function
            if function_name == "get_current_time":
                # Dynamically call the function with its arguments
                available_functions = {
                    "get_current_time": get_current_time,
                }
                function_to_call = available_functions[function_name]
                function_response = function_to_call(**function_args) # Call with unpacked arguments

                print(f"DEBUG: Tool '{function_name}' executed with args {function_args}, result: {function_response}")

                # Second API call: Send tool output back to the LLM for final response generation
                messages.append(
                    {
                        "tool_call_id": tool_call.id,
                        "role": "tool",
                        "name": function_name,
                        "content": function_response,
                    }
                )
                second_response = client.chat.completions.create(
                    model="gpt-4o",
                    messages=messages,
                )
                return second_response.choices[0].message.content
            else:
                return f"Error: Unknown tool '{function_name}' requested by LLM."
    else:
        # If no tool call, the LLM generated a direct text response
        return response_message.content

# Example usage:
print("Agent: " + run_conversation("What time is it in New York?"))
print("Agent: " + run_conversation("What's the current time?")) # Defaults to UTC
print("Agent: " + run_conversation("Tell me a joke.")) # No tool needed
print("Agent: " + run_conversation("What time is it in a fake timezone?")) # Error handling example

[IMAGE: Screenshot of the Python code demonstrating the LLM interaction loop and tool execution]

This loop is critical: the LLM suggests a tool, your code executes it, and the result is fed back. This allows the LLM to incorporate real-world data into its final answer, making it a true agent.

Step 5: Putting It All Together (Full Example)

Here's the complete script for a runnable example. Save it as time_agent.py.


import datetime
import pytz
import os
import json
from openai import OpenAI

# Step 1: Define Your Tool(s)
def get_current_time(timezone: str = "UTC") -> str:
    """
    Returns the current time in a specified timezone.

    Args:
        timezone (str): The timezone to get the current time for (e.g., "America/New_York", "Europe/London", "Asia/Tokyo").
                        Defaults to "UTC" if not specified.
    Returns:
        str: A string representing the current time.
    """
    try:
        tz = pytz.timezone(timezone)
        now = datetime.datetime.now(tz)
        return now.strftime("%Y-%m-%d %H:%M:%S %Z%z")
    except pytz.UnknownTimeZoneError:
        return f"Error: Unknown timezone '{timezone}'. Please provide a valid timezone (e.g., 'America/New_York')."

# Define the tool description for the LLM
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_current_time",
            "description": "Get the current time in a specified timezone. Useful for answering questions about the current time.",
            "parameters": {
                "type": "object",
                "properties": {
                    "timezone": {
                        "type": "string",
                        "description": "The timezone (e.g., 'America/New_York', 'Europe/London'). Defaults to 'UTC'.",
                    }
                },
                "required": [], # timezone is optional
            },
        },
    }
]

# Step 2: Initialize Your LLM Client
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

# Step 3: Craft the System Prompt (and initialize message history)
messages = [
    {
        "role": "system",
        "content": "You are a helpful AI assistant. You have access to tools to get real-time information. Use them wisely."
    }
]

# Step 4: Engage the LLM and Process Tool Calls
def run_conversation(user_message: str):
    global messages # Modify the global messages list

    messages.append({"role": "user", "content": user_message})

    # First API call: Send user message and available tools to the LLM
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=messages,
        tools=tools,
        tool_choice="auto",
    )

    response_message = response.choices[0].message
    messages.append(response_message) # Add LLM's response to history

    if response_message.tool_calls:
        print(f"\n[DEBUG] LLM wants to call a tool: {response_message.tool_calls}")
        for tool_call in response_message.tool_calls:
            function_name = tool_call.function.name
            function_args = json.loads(tool_call.function.arguments)

            if function_name == "get_current_time":
                available_functions = {
                    "get_current_time": get_current_time,
                }
                function_to_call = available_functions[function_name]
                function_response = function_to_call(**function_args)

                print(f"[DEBUG] Tool '{function_name}' executed with args {function_args}, result: {function_response}")

                messages.append(
                    {
                        "tool_call_id": tool_call.id,
                        "role": "tool",
                        "name": function_name,
                        "content": function_response,
                    }
                )
                second_response = client.chat.completions.create(
                    model="gpt-4o",
                    messages=messages,
                )
                messages.append(second_response.choices[0].message) # Add the final LLM response to history
                return second_response.choices[0].message.content
            else:
                return f"Error: Unknown tool '{function_name}' requested by LLM."
    else:
        return response_message.content

# Example usage:
print("--- Conversation Start ---")
print("User: What time is it in America/New_York?")
print("Agent: " + run_conversation("What time is it in America/New_York?"))
print("\nUser: And what about in Europe/London?")
print("Agent: " + run_conversation("And what about in Europe/London?"))
print("\nUser: Just tell me the current time.")
print("Agent: " + run_conversation("Just tell me the current time."))
print("\nUser: Tell me a fun fact about time zones.")
print("Agent: " + run_conversation("Tell me a fun fact about time zones.")) # No tool needed
print("\nUser: What time is it in a non-existent timezone?")
print("Agent: " + run_conversation("What time is it in a non-existent timezone?")) # Error handling
print("--- Conversation End ---")

[IMAGE: Screenshot of the complete Python script and its expected output in a console]

When you run this script, observe the debug messages. You'll see the LLM's intention to call a tool, the execution of your Python function, and then the LLM's final, informed response incorporating the tool's output.

What Are Examples of AI Agent Tools?

The beauty of tool calling lies in its versatility; almost any function or API can be wrapped into a tool for an AI agent. This capability unlocks a vast array of possibilities, allowing agents to interact with the digital and even physical world in meaningful ways. Here are several categories and examples of common AI agent tools:

  1. Information Retrieval Tools: These tools allow agents to fetch real-time or specific data that isn't part of their training corpus.
    • Web Search: A tool that queries a search engine (like Google, Bing, DuckDuckGo) to get current events, factual information, or specific articles. E.g., web_search(query: str)
    • Database Query: Tools to interact with structured databases (SQL, NoSQL) to retrieve, insert, or update records. E.g., query_customer_db(customer_id: str)
    • File System Access: Tools to read from or write to local or cloud-based files. E.g., read_document(path: str), write_note(content: str, filename: str)
    • API Callers: General-purpose tools to interact with any external REST API, fetching data like stock prices, weather forecasts, or flight information. E.g., get_stock_price(symbol: str), get_weather_forecast(location: str, date: str)
  2. Computational & Analytical Tools: For tasks requiring precise calculations or data manipulation that LLMs are not inherently good at.
    • Calculator/Math Solver: Tools to perform arithmetic operations, solve equations, or evaluate expressions. E.g., calculate(expression: str)
    • Data Analysis Libraries: Tools that wrap functions from libraries like Pandas or NumPy for complex data manipulation and statistical analysis. E.g., analyze_csv_data(filepath: str, column: str, operation: str)
    • Unit Converter: Converting between different units of measurement. E.g., convert_units(value: float, from_unit: str, to_unit: str)
  3. Action & Automation Tools: These tools enable the agent to perform actions in external systems, making it a true agent.
    • Email Sender: A tool to compose and send emails. E.g., send_email(recipient: str, subject: str, body: str)
    • Calendar Manager: Tools to create, update, or query calendar events. E.g., create_calendar_event(title: str, start_time: datetime, end_time: datetime)
    • E-commerce/Booking: Tools to search for products, add to cart, or book flights/hotels. E.g., book_flight(origin: str, destination: str, date: str)
    • Smart Home Control: Tools to interact with smart devices (e.g., turning lights on/off, adjusting thermostats). E.g., set_light_status(room: str, status: bool)
  4. Generative & Creative Tools: While LLMs are generative, they can call specialized tools for other media types.
    • Image Generator: Tools that interface with image generation models (like DALL-E, Midjourney) to create images from text prompts. E.g., generate_image(prompt: str)
    • Code Interpreter/Executor: A sandbox environment where the agent can write and execute code (e.g., Python) to solve problems or analyze data. E.g., execute_python_code(code: str)

The key principle is that any task an LLM cannot perform directly, or cannot perform reliably, can be offloaded to a purpose-built tool. This modularity allows for the creation of incredibly powerful and flexible AI agents.

What is the Difference Between Tool Calling and Function Calling?

The terms "tool calling" and "function calling" are often used interchangeably, leading to some confusion. While closely related, it's helpful to understand the subtle but important distinction between them. Essentially, function calling is the underlying API mechanism provided by LLM providers, while tool calling is the broader conceptual framework and application of that mechanism within an AI agent architecture.

Function Calling refers to the specific API feature offered by LLM providers (like OpenAI, Google Gemini, Anthropic) that allows the model to output a structured JSON object representing a function call. When you define a tool's schema and pass it to the LLM API, and the LLM decides to use it, the API response will contain a "function_call" or "tool_calls" field with the name of the function and its arguments. This is the raw output from the LLM, indicating its intent to invoke an external function. It's the technical capability that enables the LLM to express a desire for an action in a machine-readable format.

Tool Calling, on the other hand, describes the end-to-end process and architectural pattern where an AI agent leverages this function calling capability to interact with external "tools." A "tool" is a broader concept than just a function; it's any external resource, API, or piece of code that an agent can use. The tool-calling paradigm involves not just the LLM generating a function call, but also the external orchestrator (your code) intercepting that call, executing the actual underlying function/tool, capturing its output, and then feeding that output back to the LLM for further reasoning or response generation. It encompasses the entire loop of observation, thought (LLM's decision), action (tool execution), and re-observation.

Consider this analogy: If a car has an engine, the engine is analogous to "function calling" – it's the core mechanism that allows the car to move. "Tool calling" is like the entire car itself, including the steering wheel, accelerator, brakes, and the driver's decision-making process to navigate the roads. The engine (function calling) is a

Ad — leaderboard (728x90)
Tool Calling Explained: How AI Agents Use External Tools | AI Creature Review