Agents and Tool Use · The Loop · lesson 1 of 7
What makes something an agent
about 16 minutes · free · runs in your browser
A while loop with a model in it
A single tool call is a transaction: ask, run, answer. An agent is that in a loop — after each tool result the model decides whether it has finished or needs another call.
question ─→ model ─→ tool call? ─── yes ──→ run it ──→ append result ─┐
↑ │
└───────────────────────────────────────────────────────┘
no → the answer
That is the entire architectural difference, and it is also where every problem comes from. A transaction ends by construction. A loop only ends if you end it — and a loop containing a paid API call that does not end is the specific failure this course exists to prevent.
So the loop is written with its limit from the first line, never added later:
for step in range(max_steps):
...
raise RuntimeError("the agent did not finish")
Your turn: write run_agent(question, tools, registry, max_steps=4) returning
(answer, steps_used). Loop while the model keeps calling tools; stop when it answers in
prose; raise RuntimeError if it runs out of steps.
One thing you will see immediately: ask this model about the weather and it keeps asking for the tool, step after step, because the question still matches on every turn. That is not a quirk of the fake — it is the ordinary behaviour a step limit exists for, and it is why the limit is written before the loop rather than after the first incident.
You start from this, and edit it in the browser:
import json
import fake_llm
def _tools():
"""The weather tool, as the tests hand it to you."""
return [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Look up the current weather for a city",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}
]
def run_agent(question, tools, registry, max_steps=4):
"""Return (answer, steps_used). Raise RuntimeError if it never finishes."""
return ("", 0)