Tool use is the moment an LLM stops being a calculator-that-talks and becomes something that can search, query, transact, and act. Every production agent in 2025 — Claude Code, Cursor, ChatGPT with Operator, every SaaS Copilot — is built on the seven steps in this lesson.
Learning Objectives
After this lesson, you will be able to:
Follow the complete 7-step tool use flow: user asks a question, the AI decides which tool to call, your code runs the tool, and the AI explains the result
Write a clear tool schema in JSON format so the AI knows what the tool does and what inputs it needs
Understand the critical separation: the AI *decides* which tool to use, but *your code* actually runs it (the AI never touches the real API)
Describe tool chaining -- how agents call one tool, use the result to decide the next tool, and build multi-step workflows on the fly
See how the quality of your tool descriptions directly determines whether the AI picks the right tool
Lesson scope note: This lesson covers the 7-step tool use flow, JSON Schema tool design, and tool chaining — the conceptual and practical layer that most developers work with daily. For the token-level generation mechanics, parallel execution with asyncio, and Pydantic validation patterns, see Function Calling Internals (Lesson 3).
Tool use is where agents go from "smart talker" to "useful worker." This lesson is the mechanical heart of everything in this track -- once you understand these 7 steps, every agent framework and every tool-using AI will make perfect sense.
LLMs do the same thing. They are powerful text generators, but they have hard limits: they cannot browse the web, they do not know today's date, they cannot query your database, and their math is unreliable past simple arithmetic. Tool use is how LLMs overcome these limits -- by learning when to stop generating text and instead request that an external function be called on their behalf. Tools are an LLM's hands.
Tool use (also called function calling) is the mechanism that transforms an LLM from a text generator into something that can interact with the real world. Without it, agents are all talk and no action. This is the bridge between reasoning and capability.
The flow has seven distinct steps. Understanding each one is critical because a common misconception is that the LLM "runs" the tool. It absolutely does not. Here is what actually happens:
Step 1 -- User sends a message. "What is the weather in San Francisco?" This is a natural language request that the LLM receives.
Step 2 -- System sends message + tool schemas to the LLM. The tools are described as JSON schemas: name, description, parameters. The LLM knows what tools exist and what they can do, but it cannot execute them. Think of this as reading a menu -- you know what dishes are available, but you cannot cook them yourself.
Step 3 -- LLM decides: respond directly or call a tool. If the LLM can answer from its training data ("What is the capital of France?"), it responds directly. If it needs external information or capabilities, it decides to call a tool.
Step 4 -- LLM outputs a structured tool call. Instead of generating natural language, the LLM outputs structured JSON: the tool name and its arguments. Example: {"tool": "get_weather", "args": {"city": "San Francisco"}}. This is machine-readable, not human prose.
Figure
The work splits across a boundary that matters. On one side the LLM decides: it emits a structured tool call such as {"tool": "get_weather", "args": {"city": "SF"}}. On the other side your code executes: it receives that JSON, validates it, calls the real API, and returns the result. The model never touches the API itself — it only ever asks. Everything that actually runs stays under your control, which is what makes tool use safe to deploy.
The key insight: the LLM does NOT execute the tool. YOUR code does.
Step 5 -- Your code executes the tool. This is the key step that most beginners misunderstand. The LLM did not run anything. Your application receives the structured request, validates the arguments, calls the actual function (an API request, a database query, a file read), and captures the result.
Step 6 -- Tool result sent back to the LLM. Your code takes the tool's output and sends it back to the LLM as a new message in the conversation. The LLM now has the external data it needed.
Step 7 -- LLM processes result and responds (or calls another tool). The LLM reads the tool output, synthesizes it with the original question, and generates a natural language response for the user. Or, if it needs more information, it calls another tool -- and the flow loops back to step 4.
The user asks: "What is the weather in San Francisco right now?" This is a question the LLM cannot answer from training data because weather changes constantly. The LLM needs real-time data.
The system sends the user's message along with descriptions of available tools: get_weather, search_web, send_email. Each tool includes its name, description, and parameter schema in JSON format. The LLM reads all of this before deciding what to do.
The LLM reasons: "The user wants current weather, which I do not have. I have a get_weather tool that takes a location parameter. That is exactly what I need." It decides to call get_weather rather than respond directly or use search_web.
Instead of generating text, the LLM outputs: {"tool": "get_weather", "arguments": {"location": "San Francisco, CA", "units": "fahrenheit"}}. This is a precise, machine-readable request -- not a natural language description of what it wants.
Your application code catches this tool call. It validates the arguments (is "San Francisco, CA" a valid location?), then calls the real weather API: GET https://api.weather.service/v1/current?location=San+Francisco,CA. The API returns: {"temperature": 62, "condition": "Partly cloudy", "humidity": 73}.
Your code packages the API response and sends it back to the LLM as a "tool result" message in the conversation. The LLM now has concrete data to work with.
The LLM reads the data and generates: "It is currently 62 degrees F and partly cloudy in San Francisco, with 73% humidity." The user sees a natural language answer. They never need to know a tool was called behind the scenes.
Step through the complete tool use flow above. Click each node in the flow diagram or use the play controls. Notice the different message types: user messages (cyan), LLM thinking (purple), structured JSON (amber), tool execution (green).
The user sends a natural language message: "What is the current stock price of AAPL?" This question requires real-time data that the LLM does not have. The tool use flow begins here -- the user has no idea tools exist behind the scenes.
The LLM receives the message along with tool schemas. It reasons: "Stock prices change every second. I cannot answer this from training data. I have a get_stock_price tool that takes a ticker symbol. I should use it." The decision to call a tool vs. respond directly is the first critical junction.
Instead of generating a text response, the LLM outputs a structured tool call: {"tool": "get_stock_price", "arguments": {"ticker": "AAPL"}}. This is machine-readable JSON -- the tool name plus properly typed arguments matching the schema. The LLM's job is done for now.
Your application receives the structured JSON from the LLM API response. Your code validates the tool name exists, checks that required arguments are present, verifies argument types match the schema, and sanitizes inputs. This validation layer is your first line of defense against hallucinated or malicious tool calls.
Your code calls the actual stock price API: GET https://api.stockdata.com/v1/price?ticker=AAPL. This is real code running on your server -- with your API keys, your error handling, your rate limiting, and your security controls. The LLM never touches your systems directly.
The API responds: {"ticker": "AAPL", "price": 227.43, "change": "+1.2%", "volume": "52.3M"}. Your code captures this result. You can filter, transform, or truncate the data before sending it back -- you control exactly what the LLM sees.
Your code sends the tool result back to the LLM as a new message in the conversation with the role "tool". The LLM now has the concrete, real-time data it needed. The context has grown: user message + tool call + tool result.
The LLM reads the tool result and generates a natural language response: "Apple (AAPL) is currently trading at $227.43, up 1.2% today on volume of 52.3 million shares." The user sees a clean, conversational answer. The entire tool use flow -- from JSON schema to API call to formatted response -- happened invisibly.
The LLM does not magically know about your tools. You have to describe them -- and the quality of your descriptions directly affects how well the LLM uses them. Tool schemas follow a standard format: a name, a description, and a JSON Schema for the parameters.
pythonplayground.py · Pyodide
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Tests · Write the calculator tool schema with both properties, clear descriptions with examples, and the correct required fields.
Notice the attention to detail in the descriptions. The tool description says when to use it and when not to. Each parameter description includes concrete examples and edge cases. This is not optional polish -- it is the difference between an agent that picks the right tool 60% of the time and one that picks it 95% of the time.
Try it! Open the Anthropic or OpenAI API docs and try sending a simple tool call. Define a tool called "get_weather" with one parameter "city", send the message "What is the weather in Paris?", and watch the model respond with a structured JSON tool call instead of text. You will instantly see the 7-step flow in action.
{
"name": "get_weather",
"description": "Get current weather conditions for a location. Use when the user asks about current weather, temperature, or forecast. Do NOT use for historical weather or climate data.",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City and state/country, e.g. 'San Francisco, CA' or 'London, UK'"
},
"units": {
"type": "string",
"enum": ["fahrenheit", "celsius"],
"description": "Temperature units. Default to fahrenheit for US, celsius otherwise."
}
},
"required": ["location"]
}
}
The bad schema leaves the LLM guessing. What does "loc" mean? Is it a city name, a zip code, coordinates? When should the model use this tool? The good schema removes all ambiguity. The description tells when to use it and when not to. The parameter descriptions include format examples. The enum constrains valid values. Every detail reduces the chance of a wrong tool call.
Name: Use clear, action-oriented names. get_weather is better than weather or weatherAPI. search_products beats products_endpoint. The name is the first signal the model uses for tool selection.
Description: Include three things: (1) what the tool does, (2) when to use it, and (3) when NOT to use it. The "when not" part prevents false positives. Example: "Search for products in the catalog. Use when the user wants to find or compare products. Do NOT use for order status or return requests."
Parameter descriptions: Each parameter needs a description with examples. Not just "location" but "City and state/country, e.g. 'San Francisco, CA' or 'Tokyo, Japan'". The examples serve as few-shot demonstrations embedded in the schema itself.
What Do You Think?
You give an LLM access to 50 tools instead of 5 well-chosen tools. What happens to its tool selection accuracy?
What Do You Think?
The model emits {"tool": "get_weather", "arguments": {"location": 12345}}. The schema says location is a string. What should your runtime do?
Research consistently shows that tool selection accuracy degrades as the number of tools increases. With 5 well-described tools, models pick the right one almost every time. With 50, they start confusing similar tools, hallucinating parameters, or defaulting to the first tool in the list. The practical solution: give agents only the tools they need for the current task, or use a two-stage approach where one LLM call selects the relevant tools and a second one uses them. Some frameworks call this "tool routing."
Real tasks often require multiple tools working together. The output of one tool feeds into the next. This is tool chaining, and it is where the agent loop truly shines.
Consider the question: "What is the weather like where my next meeting is?"
Respond: "Your next meeting is at 2pm at Stanford. It will be 72 degrees and sunny -- no need for a jacket!"
The agent decided on its own to chain two tools. It extracted the location from the first tool's output and used it as input for the second. This is not hardcoded logic -- the LLM figured out the dependency at runtime. No one told it to use the calendar before the weather tool. It inferred the chain from the user's question.
A more complex chain might look like: search for a product, check inventory at the user's nearest store, calculate a price with tax, and generate a summary. Four tools, each output feeding the next, all orchestrated by the LLM's reasoning in real time.
Modern LLM APIs support calling multiple tools simultaneously. If the user asks "What is the weather in New York and San Francisco?", the agent can issue both calls at once rather than waiting for the first result before sending the second:
json
[
{"tool": "get_weather", "arguments": {"location": "New York, NY"}},
{"tool": "get_weather", "arguments": {"location": "San Francisco, CA"}}
]
Both API calls execute in parallel, the results come back together, and the agent synthesizes them into a single response. This is a significant latency optimization for tasks that require multiple independent pieces of information. The LLM decides whether tools can be called in parallel or must be sequential based on whether there are data dependencies between them.
In production, tool calls fail more often than they succeed on the first try. Common failure patterns:
Hallucinated parameters. The LLM invents a parameter that does not exist in the schema, or passes the wrong type (string instead of number). Your validation layer catches these before execution.
Overspecification. The LLM fills in optional parameters with guessed values instead of leaving them at defaults. Example: specifying "units: celsius" when the user did not mention a preference and the default would have been correct.
Wrong tool selection. With similar tools (search_products vs. search_orders), the LLM picks the wrong one. Better descriptions and "when NOT to use" guidance fix this.
Stale data assumptions. The LLM uses training data to fill arguments instead of asking the user. "Your nearest store is in Palo Alto" when the user never said where they are.
The LLM decides what to call; your code executes it. This critical separation means the LLM generates structured tool call requests (function name + arguments) but never runs code directly, maintaining safety and control
Tool description quality directly impacts agent performance. Clear, specific descriptions with examples help the LLM choose the right tool and pass correct arguments; vague descriptions cause misuse and errors
Tool chaining enables complex multi-step workflows. Agents compose simple tools (search, calculate, write) into sophisticated pipelines at runtime, with each tool's output feeding into the next decision
JSON Schema defines the contract between LLM and tools. Well-structured schemas with types, descriptions, enums, and required fields prevent argument errors and make tool behavior predictable
In the tool use flow, who actually executes the tool function?
Now you know how agents extend their capabilities with tools -- the 7-step flow, JSON schemas, tool chaining, and the critical LLM-decides-code-executes separation. But a tool-using agent that forgets everything between sessions is severely limited. Next up: Agent Memory Systems, where we explore how agents remember, learn, and build knowledge that persists across conversations.