Tool Use (Function Calling): How Transformers Learned to Use Calculators, APIs, and Python

Introduction: The Problem of LLMs in a Vacuum

Large Language Models (LLMs) are masterful linguists. They can generate fluent, coherent text, summarize vast documents, and even craft creative prose. However, despite their impressive cognitive abilities, raw LLMs are fundamentally isolated from the real world. They cannot browse the web for up-to-the-minute news, query a database for specific user information, execute Python code to perform complex calculations, or interact with external APIs to book a flight or send an email. They operate in a linguistic vacuum, limited by the knowledge encoded in their training data.

The core engineering problem is this: How can we bridge this gap, transforming LLMs from mere text predictors into powerful, actionable agents that can access external information, perform real-world actions, and leverage specialized tools to achieve complex goals?

The Engineering Solution: LLM as an Intelligent Tool Router

The answer lies in Tool Use, also known as Function Calling. This mechanism enables LLMs to interact with external tools, APIs, and systems by intelligently determining when an external tool is needed, which tool to use, and what arguments to pass to it. The LLM's role shifts from being solely a text generator to an intelligent router that mediates between natural language requests and external functionalities.

Core Principle: Structured Output for Action. The key insight is that an LLM can be trained or prompted to generate structured output (typically JSON) that specifies a function to be called and its arguments. This JSON is then intercepted by an application layer, which executes the actual tool.

The Workflow of Tool Use:

  1. Tool Definition: Developers provide the LLM with a list of available tools. Each tool is described with its name, a natural language description of its purpose, and a precise input/output schema (often in OpenAPI JSON Schema format).
  2. User Prompt: A user provides a natural language request to the LLM (e.g., "What's the weather like in London tomorrow?").
  3. LLM Decision: The LLM processes the prompt, reasons about the user's intent, and determines that an external "get_weather" tool is needed to fulfill the request.
  4. Function Call Generation: Instead of generating a natural language answer directly, the LLM generates a structured JSON object specifying the name of the function to be called and the arguments derived from the user's prompt (e.g., {"name": "get_weather", "args": {"location": "London", "date": "tomorrow"}}).
  5. External Execution: The application (not the LLM) receives this JSON, parses it, and executes the actual get_weather function (e.g., making an API call to a weather service).
  6. Response Integration: The result from the executed function (e.g., {"temperature": "15C", "conditions": "partly cloudy"}) is fed back to the LLM. The LLM then uses this real-world information to formulate a natural language response to the user ("The weather in London tomorrow will be 15 degrees Celsius and partly cloudy.").

+------------+       +-------------------+       +--------------------+       +-------------------+
| User Prompt|-----> | LLM (Intelligent  |-----> | Structured JSON    |-----> | Application Logic |
|            |       |  Tool Router)     |       | (Function Name, Args)|       | (Executes Tool)   |
+------------+       |                   |       +--------------------+       +-------+-----------+
                     +-------------------+                                            |
                                                                                      v
                                                                             +---------------+
                                                                             | External Tool |
                                                                             | (API, Code, DB)|
                                                                             +-------+-------+
                                                                                     |
                                                                                     v
                     +-------------------+       +---------------------+       +---------------+
| Final Response|<----| LLM (Formulates  |<-----| Tool Output         |<------| Tool Result   |
|            |       |  Natural Language)|       | (e.g., Weather Data)|       | (e.g., 15C, Cloudy)|
+------------+       +-------------------+       +---------------------+       +---------------+

Implementation Details: Making LLMs Actionable

Platforms like OpenAI and Google Gemini provide robust APIs for implementing function calling, abstracting away much of the underlying complexity.

1. Tool Definition for the LLM

Developers define the available tools for the LLM using a JSON Schema format. This definition includes the function's name, a description (which the LLM uses for reasoning), and a schema for its parameters.

# Example Tool Definition: weather_tool
{
    "type": "function",
    "function": {
        "name": "get_current_weather",
        "description": "Get the current weather in a given location. Provides temperature and conditions.",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "The city and state, e.g., San Francisco, CA"
                }
            },
            "required": ["location"]
        }
    }
}

2. Orchestrating the Execution (Python Snippet)

The application layer mediates between the LLM and the actual external tools.

import json
from openai import OpenAI # Or Google's Gemini API

client = OpenAI() # Initialize LLM client

# Define actual external tool implementations (these are normal Python functions)
def get_current_weather(location: str):
    """
    Makes an API call to a real weather service.
    """
    # ... actual API call to weather service ...
    # For demonstration:
    if "london" in location.lower():
        return {"temperature": "15C", "conditions": "partly cloudy"}
    elif "boston" in location.lower():
        return {"temperature": "10C", "conditions": "rainy"}
    else:
        return {"error": "Location not found"}

# Map of available functions for the application to call
available_functions = {
    "get_current_weather": get_current_weather,
    # Add other tools like 'calculate_tax', 'book_flight', 'search_database', etc.
}

# The LLM's tool definitions (provided to the API)
tools_for_llm = [
    {
        "type": "function",
        "function": {
            "name": "get_current_weather",
            "description": "Get the current weather in a given location (city and state).",
            "parameters": {"type": "object", "properties": {"location": {"type": "string"}}, "required": ["location"]}
        }
    }
]

def chat_with_tool_use(user_message: str):
    messages = [{"role": "user", "content": user_message}]

    # Step 1: LLM decides if a tool call is needed
    response = client.chat.completions.create(
        model="gpt-4o", # Or "gemini-1.5-pro", etc.
        messages=messages,
        tools=tools_for_llm, # Provide the tool definitions
        tool_choice="auto"   # Allow LLM to decide whether to call a tool
    )

    response_message = response.choices[0].message

    # Step 2: Extract function call if LLM decided to make one
    if response_message.tool_calls:
        tool_call = response_message.tool_calls[0] # Assume one tool call for simplicity
        function_name = tool_call.function.name
        function_args = json.loads(tool_call.function.arguments)

        if function_name in available_functions:
            # Step 3: Execute the function externally
            function_output = available_functions[function_name](**function_args)

            # Step 4: Send function output back to LLM for final natural language response
            messages.append(response_message) # Add LLM's tool call decision to history
            messages.append( # Add tool output to history
                {
                    "role": "tool",
                    "tool_call_id": tool_call.id,
                    "content": json.dumps(function_output) # LLM uses this to formulate answer
                }
            )
            final_response = client.chat.completions.create(
                model="gpt-4o",
                messages=messages
            )
            return final_response.choices[0].message.content

    # If no tool call, return regular LLM response
    return response_message.content

# Example usage:
# print(chat_with_tool_use("What's the weather like in Boston today?"))
# print(chat_with_tool_use("Tell me a story about a dragon.")) # No tool call needed

Performance & Security Considerations

Performance:

Security:

Conclusion: The ROI of Actionable AI

Tool Use fundamentally transforms LLMs from intelligent conversationalists into powerful, actionable agents. It is the critical bridge that connects natural language understanding to real-world capabilities.

The return on investment for implementing Tool Use is substantial:

Tool Use is not just a feature; it is a fundamental shift in AI, bridging the gap between natural language understanding and real-world action, and is a cornerstone of modern agentic systems.