LLM Agents Academy
Module 3: Tool Use & Function Calling

Function Calling Basics

How LLMs select and invoke functions: schemas, parameters, and responses

~12 min readIncludes code

Overview

In the previous lesson, you learned why tools matter -- they bridge the gap between what an LLM knows and what it can do. But how does an LLM actually call a function? It cannot execute code. It cannot make HTTP requests. All it can do is generate text.

The answer is a protocol. You describe available tools using structured schemas, the LLM responds with structured output requesting a specific tool call, your code executes that call, and you feed the result back. This function calling lifecycle is the mechanical foundation beneath every tool-using agent -- including the calculator you built in Module 1.

This lesson walks through the lifecycle step by step, shows you the anatomy of a tool definition, and demonstrates single, parallel, and sequential tool calls with working Python code.

The Function Calling Lifecycle

Function calling follows a six-step cycle. Your application and the LLM take turns, with your code acting as the execution layer that the LLM cannot provide for itself.

sequenceDiagram
  participant App as Your Application
  participant LLM as Claude (LLM)
  App->>LLM: User message + tool definitions
  Note over LLM: Decides: answer directly<br/>or call a tool?
  LLM-->>App: tool_use block (name + arguments)
  Note over App: Executes the function<br/>with provided arguments
  App->>LLM: tool_result (output of function)
  Note over LLM: Incorporates result,<br/>decides next step
  LLM-->>App: Final text response (or another tool_use)

Here is each step in plain language:

  1. Define tools -- you write JSON Schema descriptions of available functions: their names, what they do, and what parameters they accept.
  2. Send to LLM -- you include both the user's message and the tool definitions in a single API call.
  3. LLM selects a tool -- the model reads the descriptions, decides which tool (if any) to use, and generates a structured tool_use content block with the tool name and input arguments.
  4. Your code executes -- you extract the tool name and arguments from the response and run the actual function in your own environment.
  5. Return the result -- you send the function's output back to the LLM as a tool_result message.
  6. LLM continues -- the model reads the result and either generates a final answer or issues another tool call.

This cycle repeats until the model decides it has enough information to respond directly. The model controls the logic (what to call and when), while your code controls the execution (actually running the function).

Text Response vs. Tool Call: How the LLM Decides

Not every message triggers a tool call. The LLM evaluates each turn and makes a decision: can I answer this directly from my training data, or do I need to use a tool?

flowchart TD
  A["Receive user message + tool definitions"] --> B{"Can I answer from<br/>my own knowledge?"}
  B -->|"Yes"| C["Generate text response<br/>stop_reason: end_turn"]
  B -->|"No, I need a tool"| D{"Which tool fits<br/>the task?"}
  D --> E["Generate tool_use block<br/>stop_reason: tool_use"]
  D --> F["Generate multiple tool_use blocks<br/>stop_reason: tool_use"]
  E --> G["Your code executes the tool"]
  F --> G
  G --> H["Send tool_result back"]
  H --> A
  style C fill:#c6f6d5,stroke:#38a169
  style E fill:#bee3f8,stroke:#3182ce
  style F fill:#bee3f8,stroke:#3182ce
  style G fill:#fefcbf,stroke:#d69e2e

The decision hinges on two factors:

  • The tool descriptions -- clear descriptions help the model recognize when a tool is relevant. If a user asks "What is the weather in Cairo?" and you provide a get_weather tool with a good description, the model will call it. Without that tool, the model will either say it cannot check live weather or answer from stale training data.
  • The system prompt -- you can bias the model toward or away from tool use. The calculator agent from Module 1 included the instruction "Always use tools for calculations rather than computing in your head," which pushed the model to call the arithmetic tools even for simple sums it could compute internally.

Anatomy of a Tool Definition

Every tool definition has three required fields: name, description, and input_schema. Together, they form a contract between your application and the LLM.

tool_definition.py

Let's break down each field:

  • name -- a short, descriptive identifier. The LLM uses this in its tool_use response to specify which tool it wants. Use snake_case and keep names self-explanatory: get_weather, search_database, send_email.
  • description -- the most important field for tool selection. This is a natural language explanation that tells the model what the tool does and when to use it. A vague description like "weather tool" will cause misuse. A specific description like the one above gives the model clear guidance.
  • input_schema -- a JSON Schema object that defines the parameters the tool accepts. Each property has a type, an optional description, and optional constraints like enum for allowed values. The required array lists which parameters the LLM must always provide.

The LLM never sees your Python function. It only sees these three fields. The quality of your tool definition directly determines how reliably the model uses your tool. We will dig much deeper into this in the next lesson on tool interface design.

Making an API Call with Tools

Here is a complete example showing how to send tools to the Anthropic API and handle the response. We will use a weather tool and a time tool to answer a question that requires both.

weather_agent.py

With the tools defined, making the API call is straightforward -- pass the tools list alongside your messages:

api_call.py

The response contains a tool_use content block with three key fields:

  • name -- which tool the LLM wants to call
  • input -- a dictionary of arguments matching the tool's input_schema
  • id -- a unique identifier for this specific tool call (the tool_use_id)

Handling Tool Results

After your code executes the function, you send the result back to the LLM as a tool_result message. The conversation grows turn by turn: the assistant's response (containing the tool_use block) followed by a user message (containing the tool_result).

handle_results.py

Two details are critical here:

  • The tool_use_id must match. Each tool_result references the exact tool_use block it responds to. If you send back the wrong ID, the LLM cannot correlate the result with its request.
  • Results are strings. The content field of a tool_result is a string (typically JSON). Your function can return any data structure internally, but you must serialize it before sending it back.

Parallel Tool Calls

Sometimes the LLM determines it needs multiple pieces of information that are independent of each other. Rather than issuing one tool call, waiting for the result, and then issuing the next, the model can return multiple tool_use blocks in a single response. This is called a parallel tool call.

parallel_calls.py

Your code must handle every tool_use block in the response and return a matching tool_result for each one. If you skip one, the LLM will not receive the data it needs and will either hallucinate or ask again.

Parallel tool calls are an optimization, not a separate mechanism. The same loop structure handles them naturally -- you iterate over all tool_use blocks regardless of whether there is one or several.

Sequential Tool Calls (Multi-Turn)

Sequential tool calls happen when the output of one tool call feeds into the next. The LLM cannot issue both calls at once because the second depends on the first result. Instead, the conversation spans multiple turns.

This is exactly what happened in the Module 1 calculator agent. When asked "What is 25 multiplied by 4, then add 10?", the model:

  1. Called multiply(25, 4) and got 100
  2. Used that result to call add(100, 10) and got 110
  3. Produced the final answer

Each call required a full round trip through the lifecycle: the model issued a tool_use, your code executed and returned a tool_result, and then the model decided what to do next. This multi-turn pattern is what the agent loop handles -- the while True loop that keeps cycling until stop_reason is end_turn.

generic_agent_loop.py

Notice that this is the same loop structure from the Module 1 calculator -- and it handles all three patterns (single, parallel, sequential) without any special-case logic. The loop iterates until stop_reason flips to end_turn, processing however many tool_use blocks appear in each turn.

Comparing the Three Patterns

| Pattern | Tool calls per turn | Turns | When it happens | |---------|-------------------|-------|-----------------| | Single | 1 | 1 | A simple request that needs one piece of external data | | Parallel | 2+ | 1 | Independent lookups that do not depend on each other | | Sequential | 1 (or more) | 2+ | Each call depends on the result of a previous call |

The LLM decides which pattern to use based on the user's request and the available tools. Your agent loop does not need to know which pattern is in play -- it handles all of them identically by iterating over content blocks and cycling until the model stops.

What Happens Under the Hood

It is worth understanding that the LLM is not "calling" anything. When Claude returns a tool_use block, it is generating structured text that describes a function call. The actual execution happens entirely in your code. The model is acting as a reasoning engine that decides what to call and with what arguments, while your application acts as the execution engine that runs the code and returns results.

This separation is what makes function calling safe and controllable. You can validate arguments before execution, restrict which tools are available, rate-limit calls, log everything, or reject calls that do not meet your criteria. The LLM proposes; your code disposes.

Summary

Function calling is a structured protocol between your application and the LLM, not a magic capability built into the model. The lifecycle is always the same: define tools with JSON Schema, send them with your message, receive tool_use blocks, execute the functions, return tool_result messages, and repeat until the model produces a final answer.

The three key concepts are:

  • Tool definitions -- name, description, and input_schema form the contract that tells the LLM what it can do and how to request it
  • The tool_use / tool_result exchange -- the LLM generates structured call requests and your code returns structured results, linked by tool_use_id
  • The agent loop -- a while True loop that naturally handles single, parallel, and sequential tool calls by iterating until stop_reason is end_turn

The calculator agent you built in Module 1 used exactly this pattern. Every tool-using agent, from a simple calculator to a production system calling dozens of APIs, follows the same lifecycle. The difference is in the complexity of the tools and the robustness of the loop around them.

Next lesson: tool definitions are the contract between your code and the LLM. How you write them determines whether the model uses your tools correctly or fails in subtle ways. In Designing Effective Tool Interfaces, we will cover naming conventions, description strategies, and schema patterns that make tools reliable.

Check Your Understanding

Question 1 of 4

When the LLM wants to call a tool, what does it return in its response?