# Building your first agent

> Define one tool, run the loop that lets a model call it, and see why the loop is the whole architecture.

Page: https://schubergphilis.github.io/ai-training/building-agents/agent-loop/
License: CC BY-SA 4.0, https://creativecommons.org/licenses/by-sa/4.0/

An agent is a model in a loop with tools. That sentence is the whole
architecture, and in this lesson we make it concrete. We define one tool,
write the loop, and watch a model use the tool to answer a question it
couldn't answer alone.

Every example below runs against a fixture: a fake model that behaves
deterministically, so you can predict what happens and check yourself. The
complete program is `site/examples/building-agents/agent-loop/agent.py` in
the repository. Swapping in a real model changes one function.

## A tool is a function plus a description

The model can't run code. What it can do is emit text that says "call
this function with these arguments". So a tool has two halves: the
function you run, and the description the model reads to decide when
to ask for it.

```python
def get_weather(city: str) -> str:
    data = {"Amsterdam": "14°C, rain", "Lisbon": "27°C, sun"}
    if city not in data:
        raise LookupError(f"no data for {city}")
    return data[city]

TOOLS = {
    "get_weather": {
        "fn": get_weather,
        "description": "Current weather for a city. Args: city (str).",
    },
}
```

Predict before you run.

#### Checkpoint: Predict the output

What does this print?

```python
print(TOOLS["get_weather"]["fn"]("Lisbon"))
```

The description matters as much as the code. The model chooses tools by
reading descriptions, so a vague one ("weather stuff") gets called at the
wrong times and with the wrong arguments.

#### Pitfall: Describing the implementation instead of the use

A first draft often reads "Calls the OpenWeather API with an HTTP GET".
The model can't use that to choose the tool. It needs to know *when* and *with what*.
Rule: write the description for the caller; name each argument and its
type, and say what comes back.

## The loop

Now the fixture model. It looks at the last message and either asks for a
tool or gives a final answer.

```python
def fake_model(messages):
    last = messages[-1]
    if last["role"] == "user" and "weather" in last["content"]:
        city = last["content"].split(" in ")[-1].rstrip("?")
        return {"tool": "get_weather", "args": {"city": city}}
    if last["role"] == "tool":
        if last["content"].startswith("error:"):
            return {"answer": f"I could not check. The tool said: {last['content']}"}
        return {"answer": f"It is {last['content']} there."}
    return {"answer": "I can only help with weather."}
```

And the loop itself. Read it slowly. Every agent framework you ever
use is this with more error handling.

```python
def run(question, model=fake_model, max_steps=5):
    messages = [{"role": "user", "content": question}]
    for _ in range(max_steps):
        reply = model(messages)
        if "answer" in reply:
            return reply["answer"]
        tool = TOOLS.get(reply["tool"])
        if tool is None:
            result = f"error: unknown tool {reply['tool']}"
        else:
            try:
                result = tool["fn"](**reply.get("args", {}))
            except Exception as exc:
                result = f"error: {exc}" if str(exc) else f"error: {type(exc).__name__}"
        messages.append({"role": "assistant", "content": str(reply)})
        messages.append({"role": "tool", "content": str(result)})
    return "stopped: step limit"
```

This reuses `TOOLS` and `fake_model` from above.

#### Checkpoint: Predict the loop

What does this print?

```python
print(run("What is the weather in Amsterdam?"))
```

A real model API returns the same two cases in a different format. The
reply has a stop reason field that says whether the model finished or
wants a tool, and each tool result names the id of the request it
answers, so the model can match them up when it asked for more than one
tool at once (Handle tool calls, Claude Platform documentation). With a stateless model API,
the usual kind, every round sends the whole message list and the tool
definitions again, because the model doesn't keep state between calls
(Building with the Claude API, Claude Academy) (Messages, Claude Platform documentation). The
`answer` key in `fake_model`
plays the role of the stop reason, and translating that field is most of
the work of swapping in a real model.

Notice `max_steps`. Without it, a model that keeps asking for tools runs
forever and spends your money. A real loop has a budget, and when the
budget runs out it returns a string that names the limit
(`stopped: step limit`) rather than an empty answer. The lesson
[Stopping the loop on purpose](https://schubergphilis.github.io/ai-training/building-agents/stopping-the-loop/) replaces the bare string with a dict that
has a stop reason field.

The loop also keeps going when a tool fails. A tool name that isn't in
`TOOLS`, or a function that raises, becomes a tool result that starts with
`error:` and goes back to the model like any other result. The model can
then retry with better arguments or tell the user what went wrong. A tool
error never escapes the loop, so the run always ends with something to show.
The same path handles a reply that names a tool but has no `args`. The
loop calls the function with no arguments. Because `get_weather` has a
required parameter, Python reports the missing argument, and that report
goes back to the model as a tool error it can repair. An exception with
no message is named by its type. The model then reads `error: ValueError`
rather than a bare `error:`. The loop stores every tool result as text.
Text is what the model reads.

#### Checkpoint: Predict the error path

What does this print?

```python
print(run("What is the weather in Oslo?"))
```

A vendor SDK (software development kit) can run this loop for you.
The Claude API SDKs call it the tool runner. You hand it your functions and
it returns the final answer. In Python a decorator builds each tool
definition from the function's arguments and docstring, and the runner
runs the request and result rounds (Claude Platform 101, Claude Academy)
(Tool runner (SDK), Claude Platform documentation). That saves the code above and hides the
same decisions: how many steps it takes, what it does on a tool error, when
it stops, and what it puts in the message list. Having written the loop
once, you know which of those to check in the SDK's version.

#### Checkpoint: Order one iteration

Put the steps of one iteration of the agent loop in order.

- Append the tool call and the tool result to the messages
- Check whether the reply is a final answer; if so, return it
- Look up the requested tool by name
- Run the tool function with the model's arguments
- Send the message list to the model

## Exercise

Copy `agent.py` from the repository. Add a second tool, `get_time(city)`,
returning a fixed string per city. Extend `fake_model` so a question
containing "time" asks for it. Run the loop on "What is the time in
Lisbon?" and check the answer.

A good result: the new tool has a description written for the caller, the
loop itself is unchanged, and the question returns the fixed string for
Lisbon in one tool round.

Stretch: Replace fake_model with a call to a real model API that supports tool use, keeping run unchanged. The only new work is translating the API's tool-call format into the shape the loop already understands.

## Recap

1. A tool is a function plus a description written for the model (Building your first agent: the loop from scratch, then with an SDK, Agent Engineer Course).
2. The loop is: ask the model, run the tool it asks for, append the result,
   repeat until it answers or the budget runs out.
3. The model never runs anything; your loop does. That is where safety
   controls go.

## References

- Handle tool calls, Claude Platform documentation, https://platform.claude.com/docs/en/agents-and-tools/tool-use/handle-tool-calls
- Building with the Claude API, Claude Academy, https://academy.claude.com/courses/building-with-the-claude-api
- Messages, Claude Platform documentation, https://platform.claude.com/docs/en/api/messages
- Claude Platform 101, Claude Academy, https://academy.claude.com/courses/claude-platform-101
- Tool runner (SDK), Claude Platform documentation, https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-runner
- Building your first agent: the loop from scratch, then with an SDK, Agent Engineer Course, https://lsimons.github.io/agent-engineer-course/13-building-your-first-agent/
