Complete "Lesson 3: From Prompts to Agent Workflows" first to unlock this lesson.Go to previous lesson

Introduction

An AI agent becomes genuinely useful only when it can reach outside its own context window. Tool calling is the mechanism that lets a language model decide it needs to fetch a row from a database, hit a weather API, or trigger a server action, then receive the result back as part of its next reasoning step. In this lesson we focus on how to design that loop so it is reliable inside a web application.

We will treat a tool as a plain function with three things: a stable name, a JSON schema describing its parameters, and a description that tells the model when to use it. Everything else is plumbing on the frontend and backend.

diagram showing the loop User message -> LLM decides tool call -> Backend executes function -> Tool result returned to LLM -> Final answer to user

Tool Schema and Function Calling Basics

Most modern LLM APIs expose the same shape. You pass an array of tool definitions, each one containing a name, a description, and a parameter schema. The model can respond with a structured request to call one of those tools, which your backend then executes.

Here is a minimal tool declaration in the style used by the OpenAI Python SDK. The schema is what the model actually reads to decide what arguments to produce.

python
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a city. Use when the user asks about weather, temperature, or conditions in a specific location.",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name, e.g. 'Hanoi'"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["city"]
}
}
}
]

Notice three design choices that matter. First, the description is a short instruction telling the model when to fire the tool, written in the same voice as your system prompt. Second, parameters use standard JSON Schema so enums and required fields are enforced. Third, defaults are left out of the schema unless you want the model to think they are optional.

Connecting Tools to a Web App Backend

In a real web app the agent does not run inside the browser. The browser sends the conversation to your backend, the backend calls the LLM with the tool list, and when the model returns a tool call your server actually executes it against your own services. The result is sent back to the model as a tool message, then the model produces the final reply.

A minimal FastAPI handler looks like this. It detects a tool call in the response, runs the matching Python function, and feeds the output back into the conversation.

python
from fastapi import FastAPI
from openai import OpenAI
app = FastAPI()
client = OpenAI()
def get_weather(city: str, unit: str = "celsius") -> dict:
# call your real weather provider here
return {"city": city, "unit": unit, "temp": 30, "condition": "sunny"}
dispatch = {"get_weather": get_weather}
@app.post("/chat")
def chat(messages: list):
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
tools=tools,
)
msg = response.choices[0].message
if msg.tool_calls:
for call in msg.tool_calls:
fn = dispatch[call.function.name]
args = json.loads(call.function.arguments)
result = fn(**args)
messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)})
response = client.chat.completions.create(model="gpt-4o-mini", messages=messages, tools=tools)
return {"reply": response.choices[0].message.content}

The dispatch dictionary is the cleanest pattern for small projects. For larger systems you move tool registration to a registry object so tools can be added without editing the handler.

Parameter Design Patterns

Most tool failures come from bad parameter design, not from the model. Two patterns reduce this dramatically.

First, prefer flat parameters over nested objects. A tool that takes separate lat and lon fields is easier for a model to fill correctly than one that takes a single nested location object. Second, mirror the vocabulary your users actually use. If users say zip code, expose a parameter called zip_code, not postal_code.

json
{
"name": "search_products",
"description": "Search the product catalog by keyword and optional filters.",
"parameters": {
"type": "object",
"properties": {
"keyword": {"type": "string", "description": "Search term, e.g. 'running shoes'"},
"max_price_usd": {"type": "number", "description": "Optional upper price limit in USD"},
"in_stock_only": {"type": "boolean", "description": "If true, only return items currently in stock"}
},
"required": ["keyword"]
}
}

Three flat parameters, one required, the rest with sensible defaults that the model can leave empty.

Common Pitfalls

A few mistakes show up in almost every integration. The model hallucinates a tool name that does not exist, so always validate call.function.name against your registered tools before dispatching. The model passes a string where your function expects an integer, so coerce arguments based on the schema before calling. And the agent enters an infinite loop of tool calls because it thinks it still needs more information, so cap the loop at three to five iterations and surface a fallback answer if it never converges.

Logging every tool call with its arguments and result is the single highest leverage debugging habit you can build. When something breaks in production, that log is how you will see whether the model picked the wrong tool, picked the right tool with the wrong arguments, or your backend returned an unexpected shape.

Summary

Tool calling turns a chatbot into an agent. You define tools as JSON Schema, pass them on every LLM call, execute whatever the model requests on your backend, and feed the result back as a tool message. Good parameter design is flat, uses user vocabulary, and clearly marks required fields. Always validate tool names, cap the loop, and log everything. In the next lesson we layer planning and multi step reasoning on top of this loop.

Lesson practice

Lesson Checkpoint

Review what you learned and get feedback on your work.

0/1
Answered · 0/1