Function Calling and Tools
Complete the full lesson to earn 25 points
Work through each section, then tap “Mark as Complete” on the last one.
✦ Skip the page breaks and see fewer ads — read each lesson on a single page with Pro
Lesson: Function Calling and Tools in Large Language Models
Introduction: Bridging the Gap Between Language and Action
Large Language Models (LLMs) have revolutionized how we interact with information. At their core, these models are sophisticated pattern matchers that predict the next token in a sequence based on vast amounts of training data. While they are exceptional at reasoning, summarizing, and generating creative text, they suffer from a fundamental limitation: they are "frozen" in time. They cannot browse the live web, perform complex mathematical calculations with perfect precision, interact with private databases, or execute code on your local machine.
This is where the concept of "Function Calling" (often referred to as "Tool Use") enters the picture. Function calling is the mechanism that allows an LLM to transcend its static knowledge base. By defining a set of external tools—essentially functions or APIs that the model can "call"—we grant the model the agency to interact with the outside world. Instead of simply generating text, the model can output a structured request to execute a specific task, wait for the result, and then incorporate that result into its final answer.
Understanding function calling is essential for any developer looking to build autonomous agents. Whether you are creating a customer support bot that needs to look up order statuses in a SQL database, a financial assistant that fetches live stock quotes, or a research tool that queries a vector database, the principles remain the same. This lesson will guide you through the architecture, implementation, and best practices of building LLM-powered systems that don't just talk, but actually do.
The Core Concept: How LLMs "See" Tools
To understand how function calling works, it is helpful to demystify the interaction loop. When you send a prompt to an LLM, you are typically sending a sequence of text. However, modern LLM APIs allow you to pass an additional parameter: a list of tool definitions.
These definitions are provided in a structured format, usually JSON Schema. The model is trained to recognize when the user's intent requires information or an action that lies outside its training data. When the model detects this, it stops generating natural language and instead outputs a structured object that contains the name of the tool to be called and the arguments to pass to that tool.
The Standard Interaction Workflow
- System Prompting: You define the persona and the available tools.
- User Input: The user asks a question that requires external data (e.g., "What is the weather in Tokyo?").
- Model Reasoning: The model realizes it does not know the current weather and decides to call the
get_weathertool. - Tool Output: The model outputs a JSON object:
{"name": "get_weather", "arguments": {"location": "Tokyo"}}. - Execution: Your application intercepts this JSON, executes the actual code (like an API call), and receives a result.
- Final Response: You feed the result back to the model, which then generates a natural language response: "The weather in Tokyo is currently 22 degrees Celsius with clear skies."
Callout: The "Tool-Aware" Model It is important to distinguish between "tool-aware" models and standard prompting. While you could theoretically ask a model to "output JSON if you need to calculate something," this is brittle. Modern models (like GPT-4o, Claude 3.5, or Llama 3) have been explicitly fine-tuned on datasets containing tool-use examples. This makes them significantly more reliable at generating valid, executable function calls compared to models forced into this behavior via prompt engineering alone.
Defining Tools: JSON Schema as the Interface
The most critical part of implementing function calling is the definition of the tools themselves. Because the LLM needs to understand what a function does and what parameters it requires, you must provide a clear, machine-readable description.
Anatomy of a Tool Definition
A tool definition typically consists of three parts:
- Name: A unique identifier for the function.
- Description: A clear, concise explanation of what the function does. This is arguably the most important part, as the model uses this text to decide when to use the tool.
- Parameters: A JSON schema object describing the inputs the function expects.
Consider the following example for a simple calculator tool:
{
"name": "calculate_sum",
"description": "Adds two numbers together.",
"parameters": {
"type": "object",
"properties": {
"a": {
"type": "number",
"description": "The first number."
},
"b": {
"type": "number",
"description": "The second number."
}
},
"required": ["a", "b"]
}
}
Best Practices for Tool Descriptions
- Be Explicit: If a function only handles specific formats, state that in the description. For example, "Use this to search for movie information. Provide the title in English."
- Provide Context: Explain why the model should use the tool. "Use this tool to retrieve real-time stock prices. Do not use for historical data older than one month."
- Parameter Descriptions Matter: The descriptions for individual parameters are used by the model to understand what values to map to those fields. Use descriptive, plain language.
Practical Implementation: A Step-by-Step Example
Let us build a simple implementation using Python. We will assume you are using a standard interface (similar to the OpenAI SDK) to interact with a model.
Step 1: Define the Function
First, we write the Python function that will actually perform the work.
def get_stock_price(ticker_symbol):
# In a real app, you would call an external API here
# For demonstration, we use a mock dictionary
prices = {"AAPL": 175.20, "GOOGL": 140.50, "MSFT": 400.10}
return prices.get(ticker_symbol.upper(), "Symbol not found")
Step 2: Define the Tool Schema
Next, we map that function to the format the LLM expects.
tools = [
{
"type": "function",
"function": {
"name": "get_stock_price",
"description": "Get the current market price of a stock ticker.",
"parameters": {
"type": "object",
"properties": {
"ticker_symbol": {
"type": "string",
"description": "The stock ticker symbol (e.g., AAPL, GOOGL)."
}
},
"required": ["ticker_symbol"]
}
}
}
]
Step 3: Handling the Model's Output
In your application code, you need a loop to handle the model's decision to call a function.
import json
# Assume 'client' is your initialized LLM provider
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "What is the price of AAPL?"}],
tools=tools
)
# Check if the model wants to call a tool
tool_call = response.choices[0].message.tool_calls[0]
if tool_call:
function_name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
# Execute the local function
if function_name == "get_stock_price":
result = get_stock_price(args["ticker_symbol"])
print(f"Tool Result: {result}")
Note: Always validate the input arguments provided by the LLM. While models are generally good at following the schema, they can occasionally hallucinate parameters or pass invalid types. Treat the model's output as untrusted user input.
Advanced Tooling: Parallel Calling and Chaining
Modern models are capable of more than just a single tool call per turn. They can perform "parallel function calling," where the model determines that it needs to call multiple tools simultaneously to satisfy a request.
Parallel Function Calling
If a user asks, "What is the weather in New York and London?", a capable model will output two tool calls in a single response. Your application logic must be prepared to handle a list of tool calls rather than a single object.
Chaining (Multi-Step Reasoning)
Sometimes, one tool call is not enough. You might need to use the output of one tool as the input for another. For example, if you want to "Find the CEO of Apple and then find their age," the model must first call a search_ceo function, receive the name, and then call a get_person_age function.
To achieve this, you simply append the results of the tool calls back into the messages history as a new role (usually called tool or function role) and send the updated history back to the model.
# Pseudo-code for chaining
messages = [{"role": "user", "content": "Who is the CEO of Apple and how old are they?"}]
# ... (First call returns search_ceo tool request)
# ... (Execute search_ceo, get "Tim Cook")
messages.append({"role": "tool", "content": "Tim Cook", "tool_call_id": "..."})
# Send updated messages back to the model
# The model now knows the name and will generate the next tool call for age
Callout: Deterministic vs. Non-Deterministic Tools When designing your tool set, distinguish between tools that are deterministic (e.g., a calculator, a database query with a primary key) and those that are non-deterministic (e.g., a search engine query, a web scraper). Non-deterministic tools can change their output over time or based on external conditions. Your agent design should account for this by ensuring that the model is aware of the context in which these tools are being used.
Best Practices for Robust Tool Use
Building agents that rely on tools is significantly more complex than building simple chatbots. You are moving from a world of "text completion" to a world of "software engineering."
1. Keep Tool Descriptions Atomic
Each tool should do one thing well. If you have a tool called manage_account that can update an email, change a password, and delete an account, the model will struggle to determine which part of the function to trigger. Instead, create update_email, change_password, and delete_account.
2. Implement Error Handling
What happens if your tool fails? What if the API returns a 500 error or a timeout? The model needs to know this. Your execution logic should capture the error and pass the error message back to the LLM. This allows the model to "self-correct" or inform the user about the failure, rather than simply hanging or outputting nonsense.
3. Use Guardrails
Never give an LLM a tool that can perform destructive actions (like drop_database or delete_all_files) without a human-in-the-loop. If you must provide such tools, implement a confirmation step in your application code where the user must approve the action before it is executed.
4. Limit the Tool Surface Area
Do not overwhelm the model with 50 different tools. If you provide too many options, the model's accuracy in selecting the correct tool drops significantly. If you have a large library of functions, use a "router" approach where you only present the model with the top 5-10 tools that are relevant to the current conversation context.
Common Pitfalls and How to Avoid Them
The "Hallucinated Parameter" Trap
Sometimes a model will attempt to call a function with a parameter that you did not define in your schema.
- How to avoid: Use strict JSON schema validation in your code. If the model provides an argument that doesn't fit the schema, throw an error and provide that error back to the model so it can try again with corrected parameters.
The "Endless Loop" Trap
An agent might get stuck in a loop where it calls the same tool repeatedly, or calls two tools that pass data back and forth infinitely.
- How to avoid: Implement a "max turns" counter. If the conversation reaches a certain number of tool-call cycles, force the agent to stop and summarize its progress to the user.
The "Context Overflow" Trap
Every time you feed tool results back into the conversation, you consume more tokens. If you are doing complex chaining, your context window can fill up quickly.
- How to avoid: Summarize tool results before feeding them back into the chat history. If a tool returns a 5,000-word document, do not pass the entire document back to the model. Use an extraction or summarization tool first.
Comparison: Function Calling vs. Chain-of-Thought
It is often asked whether you should use a model's internal reasoning (Chain-of-Thought) or external tools.
| Feature | Chain-of-Thought (CoT) | Function Calling (Tools) |
|---|---|---|
| Primary Goal | Better logical reasoning | Accessing external data/actions |
| Data Source | Model's internal weights | External APIs/Databases |
| Accuracy | High for logic, low for facts | High for facts (if API is accurate) |
| Implementation | Prompt engineering | Code-level integration |
| Latency | Usually lower | Higher (due to network calls) |
Summary of Key Takeaways
- Function Calling is Agency: It is the primary mechanism that allows LLMs to interact with the real world by bridging the gap between natural language reasoning and programmatic execution.
- Schema is Everything: The quality of your tool definitions—specifically the descriptions and parameter schemas—is the most significant factor in how reliably a model will select and use your tools.
- The Loop Pattern: Effective tool use requires an iterative loop: Request (Model) -> Execute (Code) -> Return (Model). Your application logic must manage this state effectively.
- Error Handling is Mandatory: Because external tools are prone to failure (network issues, API changes), your code must be robust enough to catch errors and pass them back to the model for recovery.
- Security First: Never expose dangerous functions to an LLM without a human-in-the-loop or strict authorization layers. Treat the LLM as a user with limited permissions.
- Context Management: Be mindful of the token budget. Large outputs from tools should be summarized before being fed back into the model's message history to prevent context window exhaustion.
- Iterative Refinement: Start with a few simple tools and expand based on the model's performance. Monitor which tools are being used correctly and which ones the model consistently struggles with.
Frequently Asked Questions (FAQ)
Q: Can I use functions that aren't in the official API documentation? A: Yes, you can define any function as long as it conforms to the JSON schema format required by the model provider. The model doesn't "know" the code; it only knows the description you provide.
Q: Why does my model keep picking the wrong tool? A: This usually happens because your tool descriptions are too similar or ambiguous. Try to make the descriptions distinct and focus on the intent of the user that should trigger each tool.
Q: How do I handle authentication for my tools? A: You should handle authentication within your application code, not by passing credentials to the LLM. The model should never see API keys or passwords. Your code should hold the credentials and use them when the model triggers the function.
Q: Is function calling available on all models? A: Most modern, high-tier models support function calling. However, smaller or older open-source models may not have been fine-tuned for this behavior. Always check the model documentation before attempting to implement complex tool chains.
Final Thoughts: The Path Forward
The integration of tools into LLMs is currently one of the most active areas of research and development in artificial intelligence. As we move from static chat interfaces to "agentic" workflows, the ability to define, manage, and secure these tools will become a core competency for developers. By following the principles of clear schema design, robust error handling, and careful context management, you can build systems that are not just intelligent, but truly capable of executing tasks in the real world.
As you continue to build, remember that the goal is to create a predictable, reliable interaction between the model's reasoning capabilities and your application's capabilities. Start small, focus on the "happy path" first, and gradually add complexity as your agent's reasoning capabilities improve. The future of software is not just writing code—it is orchestrating models to write and execute code on our behalf.
Continue the course
Enjoying the courses?
Everything stays free. Pro shows fewer ads, doubles your daily points limit so you progress twice as fast, and lets you read each lesson on one page.
- ✓ Fewer advertisements
- ✓ 2× daily points limit
- ✓ Distraction-free lessons