LLM Agents Academy
Module 3: Tool Use & Function Calling

Tool Use Lab

Hands-on: build an agent that searches the web, reads files, and calls APIs

~15 min readIncludes code

Overview

You have spent six lessons building up every piece of the tool-use puzzle: why tools matter, how function calling works, how to design clean tool interfaces, how to handle errors gracefully, what the Model Context Protocol is, and how to build your own MCP servers. Now it is time to put all of those pieces together.

In this capstone lab, you will build a research assistant agent -- an agent that takes a research question, searches the web for information, reads the content of relevant pages, and synthesizes its findings into a structured report. This is not a toy calculator. It is a multi-tool, multi-turn agent that mirrors what production research agents actually do.

By the end of this lab, you will have a working agent that demonstrates every concept from this module in a single cohesive system. You will also see exactly where this approach hits its limits -- which sets the stage for Module 4, where we learn structured agent architectures that solve those problems.

What We Are Building

The research assistant takes a question like "What are the main approaches to reducing LLM hallucinations?" and autonomously gathers information to answer it. Here is the full flow of a research session:

sequenceDiagram
  participant U as User
  participant A as Agent (LLM)
  participant S as search_web
  participant R as read_webpage
  participant D as get_current_date
  participant W as save_report

  U->>A: "What are the main approaches<br/>to reducing LLM hallucinations?"
  A->>D: get_current_date()
  D-->>A: "2025-01-15"
  A->>S: search_web("LLM hallucination<br/>reduction techniques 2025")
  S-->>A: [result1, result2, result3]
  A->>R: read_webpage(result1.url)
  R-->>A: page content (truncated)
  A->>R: read_webpage(result2.url)
  R-->>A: page content (truncated)
  A->>S: search_web("RAG retrieval augmented<br/>generation hallucination")
  S-->>A: [result4, result5, result6]
  A->>R: read_webpage(result4.url)
  R-->>A: page content (truncated)
  A->>W: save_report(title, sections, sources)
  W-->>A: "Report saved: hallucination_reduction.md"
  A->>U: "I have researched your question<br/>and saved a report with my findings."

Notice the pattern: the agent does not follow a hardcoded script. The LLM decides when to search, what to read, whether to search again with refined terms, and when it has gathered enough information to write the report. This is autonomous, goal-directed behavior driven by tools.

Stage 1: Define the Tools

We need four tools. Each one follows the design principles from lesson 03: clear names, specific descriptions that tell the LLM when to use the tool, and well-typed parameter schemas.

tools.py

A few design choices worth calling out:

  • Simulated tools keep this lab self-contained. Swapping in real implementations (Tavily for search, httpx for web reading) only requires changing the function bodies -- the schemas and agent loop stay identical.
  • read_webpage truncates long content. This is a real-world concern. Web pages can be massive, and sending tens of thousands of characters to the LLM wastes tokens and degrades performance. Production agents typically use readability extraction and chunking.
  • save_report takes structured input. Rather than accepting a raw string, it takes a title, summary, sections, and sources. This forces the LLM to organize its findings before writing -- a technique from lesson 03 on designing tool interfaces that shape model behavior.
  • Every tool returns JSON. Consistent return formats make it easier for the LLM to parse results and for you to debug the agent's behavior.

Stage 2: Write the Tool Definitions

Now we describe each tool to the LLM. Remember from lesson 03: descriptions should explain what the tool does and when to use it. Parameter descriptions should eliminate ambiguity.

tool_definitions.py

Compare these definitions to the calculator tools from Module 1. The descriptions are longer and more directive because the tasks are more complex. The save_report tool uses a nested schema with arrays of objects -- this is how you guide the LLM toward producing structured, multi-part output through the tool interface itself.

Stage 3: Write the System Prompt

The system prompt is where we apply everything from Module 2. It defines the agent's role, its research methodology, behavioral guidelines, and stopping conditions. A research agent needs significantly more guidance than a calculator agent because the task is open-ended.

system_prompt.py

This prompt applies three key techniques from Module 2:

  • Structured methodology gives the agent a step-by-step process without being so rigid that it cannot adapt. The agent can skip steps or repeat them based on what it finds.
  • Behavioral boundaries set limits (5 pages max, 12 tool calls max) that prevent the agent from spiraling into endless research loops. Without these guardrails, a research agent will keep searching indefinitely.
  • An explicit stopping condition tells the model when to stop acting. This is critical for any agent that runs in a loop -- without a clear stopping rule, the loop may never terminate.

Stage 4: Implement the Agent Loop with Error Handling

The agent loop is the same pattern from Module 1's calculator agent, but with two important additions from lesson 04: error handling around tool execution and a maximum iteration guard to prevent runaway loops.

agent_loop.py

The execute_tool function wraps every tool call in a try/except block. This is the error handling pattern from lesson 04: catch exceptions, return structured error messages that the LLM can understand and reason about, and let the agent decide how to recover.

execute_tool.py

Two key design decisions here:

  • Structured error messages include a hint field that gives the LLM actionable guidance. From lesson 04, we know that vague errors like "something went wrong" cause the agent to either give up or retry blindly. A hint like "Check the parameter names and types" helps the LLM self-correct.
  • The available_tools list in the unknown-tool error helps the LLM recover if it hallucinates a tool name. Rather than failing silently, it shows the LLM what is actually available.

Stage 5: Run the Agent

Let's put everything together and run the research assistant on a sample question.

run_agent.py

Here is what the output looks like when the agent runs (abbreviated for readability):

output

The agent made its own decisions at every step: it checked the date first, searched with a topical query, read two pages, refined its search to focus on RAG specifically, read one more page, and then decided it had enough information to write the report. Eight iterations, seven tool calls, all driven by the LLM's judgment.

Anatomy of the Full Agent

Here is the complete, runnable file that combines all the stages.

research_agent.py

What This Lab Taught You

Look at what you built and trace each piece back to the lesson where you learned it:

  • Tool implementations with clear return formats (lesson 01: why tools matter)
  • Tool definitions with JSON Schema that guide the LLM's behavior (lesson 02: function calling basics)
  • Intentional interface design -- structured report output, descriptive parameter names, usage hints in descriptions (lesson 03: tool interface design)
  • Error handling with structured error messages and recovery hints (lesson 04: error handling and retries)
  • A maximum iteration guard that prevents runaway loops (lesson 04)
  • A well-structured system prompt with methodology, boundaries, and stopping conditions (Module 2: prompt engineering)
  • The agent loop that cycles between LLM reasoning and tool execution (Module 1: the agent loop)

This is the full tool-use toolkit. Every agent you build from here will use these same building blocks.

Where This Architecture Falls Short

The research agent works, but step back and look at its architecture critically. The agent's "strategy" is entirely implicit -- it lives inside the system prompt as natural language instructions, and the LLM follows them loosely. This creates several problems:

  • No explicit plan. The agent decides what to do one step at a time. It cannot look ahead, prioritize, or allocate its limited tool calls across sub-tasks. If the question has three parts, the agent might spend all its iterations on the first part and never reach the others.

  • No self-reflection. The agent never pauses to evaluate the quality of information it has gathered. It might write a report based on one mediocre source when a better source is one search away.

  • No structured reasoning. The LLM interleaves thinking and acting in whatever order feels natural. Sometimes it thinks carefully before acting; sometimes it fires off a tool call impulsively. There is no guarantee of consistency.

  • No task decomposition. For a complex research question, the agent treats everything as a single flat task. It cannot break the question into sub-questions, research each one independently, and then synthesize.

These are not bugs in our code. They are fundamental limitations of the ad-hoc agent loop -- a while loop that hands all control to the LLM with no structural guidance beyond the system prompt.

What Comes Next

In Module 4: Agent Architectures, we solve these problems with proven architectural patterns:

  • ReAct (Reason + Act) forces the agent to explicitly state its reasoning before every action, making the thought process visible and consistent.
  • Plan-and-Execute separates planning from execution -- the agent creates a full plan first, then executes each step, tracking progress and adapting as it goes.
  • Self-Reflection adds an evaluation step where the agent critiques its own output and decides whether to refine it.
  • Routing architectures let you dispatch different types of questions to specialized sub-agents, each with their own tools and prompts.

These patterns do not replace anything you built in this lab. They build on top of it. The tools, schemas, error handling, and agent loop you implemented here are the foundation. Module 4 adds the structure that makes agents reliable, predictable, and capable of handling complex tasks.

Summary

You built a research assistant agent that combines every concept from Module 3 into a working system. The agent uses four tools -- search_web, read_webpage, get_current_date, and save_report -- to autonomously research a question and produce a structured report. The implementation applies tool design principles, error handling patterns, structured system prompts, and the core agent loop.

This capstone demonstrates that tool use is the mechanism that transforms an LLM from a text generator into an agent that can interact with the world. But it also reveals that the simple while-loop architecture has real limits: no planning, no self-reflection, no task decomposition. Module 4 introduces the architectural patterns that address these limitations head-on.