The Agent-Tool Loop

First lesson of the AI Agents and Automation course, free to read: about 20 minutes, 3 exercises and 3 quiz questions. This is the catalogue's base lesson; in the student space, it is rewritten for your profile.

Objectives

  • Explain what distinguishes an agent from a simple model call
  • Write a loop that alternates model decisions and tool execution
  • Bound the loop and handle invalid tool calls

Agent-tool loopTools and schemasStop conditions

From a Model Call to an Agent

A classic model call is a function: text goes in, text comes out. The model only knows what is in its request and in its training data. It can neither look up your customer database, nor calculate reliably, nor send an email.

An agent adds a loop around the model. At each turn, the model receives the conversation history and chooses between two actions:

  1. call a tool (a function of your program) with arguments;
  2. give the final answer.

When the model requests a tool, your program executes it, adds the result to the history, then calls the model again. The loop stops when the model answers, or when a limit is reached.

An essential point: the model executes nothing itself. It produces a structured request, for example {"tool": "meteo", "args": {"ville": "Lyon"}}. It is your code that decides whether to execute it. This separation is the basis of all the guardrails seen later.

Describing the Tools

Model APIs that support tool calling (tool calling or function calling) expect for each tool a name, a natural-language description, and an argument schema, often in JSON Schema format. The description matters as much as the code: it is what lets the model choose the right tool.

TOOLS_SPEC = [
    {"name": "meteo",
     "description": "Current temperature in °C for a French city.",
     "parameters": {"type": "object",
                    "properties": {"ville": {"type": "string"}},
                    "required": ["ville"]}},
]

The model can be wrong: invent a tool that does not exist, forget an argument, pass text instead of a number. Your code must therefore validate each call before executing it.

The Loop, Step by Step

def run_agent(question, model, tools, max_steps=5):
    messages = [{"role": "user", "content": question}]
    for step in range(max_steps):
        decision = model(messages)              # the model proposes
        if "answer" in decision:
            return decision["answer"]           # final answer
        name, args = decision["tool"], decision.get("args", {})
        if name not in tools:
            result = f"ERREUR: outil inconnu {name}"
        else:
            try:
                result = tools[name](**args)    # your code executes
            except Exception as e:
                result = f"ERREUR: {e}"
        messages.append({"role": "tool", "name": name, "content": str(result)})
    return None                                 # limit reached

Three remarks:

  • the history (messages) is the model’s only memory from one turn to the next: everything it needs to know must be in it;
  • errors are turned into readable messages: the model can then fix its call on the next turn;
  • the loop is bounded by max_steps. Without a limit, a model that keeps calling the same tool over and over would consume time and money indefinitely.

Simulating the Model for Testing

To fine-tune the loop, there is no need to call a real model: a local function that returns predictable decisions is enough. It makes tests reproducible, free, and runnable without a network.

def fake_model(messages):
    resultats = [m for m in messages if m["role"] == "tool"]
    if not resultats:
        return {"tool": "meteo", "args": {"ville": "Lyon"}}
    return {"answer": f"It is {resultats[-1]['content']} °C."}

This way you test the nominal path, but also the hard cases: a model that loops, invents a tool, or passes bad arguments. The real model is only plugged in once these behaviors are under control.

When Not to Use an Agent

An agent is useful when the sequence of steps depends on intermediate results. If the steps are always the same (extract, classify, save), a hardcoded workflow is simpler, cheaper, and easier to test. Start with the most deterministic solution that meets the need, and reserve autonomy for the parts that truly need it.

Key Takeaways

  • Agent = “the model proposes, the code executes” loop.
  • Each tool call is validated before execution; errors go back to the model as text.
  • The loop is always bounded.
  • A simulated model lets you test the loop deterministically.

Exercises

Solutions and tests stay on the server. In the student space, your code is run and checked, and the tutor gives hints before the solution.

  1. CodeDifficulty 2Python in the browser2 hints

    Write the agent loop

    Complete run_agent(question, model, tools, max_steps=5).

    • The history starts with {"role": "user", "content": question}.
    • At each turn, call model(messages). If it returns an answer key, return {"answer": ..., "steps": n} where n is the number of tools executed.
    • Otherwise, execute the tool decision["tool"] with decision["args"] and add {"role": "tool", "name": ..., "content": str(resultat)} to the history.
    • Unknown tool: the content added is "ERREUR: outil inconnu <name>" (this counts as a step).
    • After max_steps model calls without an answer, return {"answer": None, "steps": max_steps, "error": "max_steps"}.
    def meteo(ville):
        donnees = {"Lyon": 18, "Lille": 12}
        return donnees.get(ville, "inconnue")
    
    
    def convertir_f(celsius):
        return round(celsius * 9 / 5 + 32, 1)
    
    
    TOOLS = {"meteo": meteo, "convertir_f": convertir_f}
    
    
    def fake_model(messages):
        """Modèle simulé : il décide d'après les résultats d'outils déjà présents."""
        resultats = [m for m in messages if m["role"] == "tool"]
        if not resultats:
            return {"tool": "meteo", "args": {"ville": "Lyon"}}
        if len(resultats) == 1:
            return {"tool": "convertir_f", "args": {"celsius": int(resultats[0]["content"])}}
        return {"answer": f"Il fait {resultats[-1]['content']} °F à Lyon."}
    
    
    def run_agent(question, model, tools, max_steps=5):
        messages = [{"role": "user", "content": question}]
        # à compléter
        ...
  2. CodeDifficulty 2Python in the browser2 hints

    Validate a tool call

    Write validate_call(call, schemas) which returns the list of errors of a call {"tool": ..., "args": {...}} (empty list if the call is valid). schemas maps each tool to a dictionary argument -> expected type (a type or a tuple of types).

    • Tool absent from schemas: return only ["outil inconnu: <name>"].
    • For each expected argument that is absent: "argument manquant: <arg>".
    • For each unexpected argument: "argument inattendu: <arg>".
    • For each argument present with the wrong type: "type invalide: <arg>".

    The order of the errors does not matter.

    SCHEMAS = {
        "meteo": {"ville": str},
        "convertir_f": {"celsius": (int, float)},
    }
    
    
    def validate_call(call, schemas):
        errors = []
        # à compléter
        return errors
  3. Multiple choiceDifficulty 11 hint

    Who executes the tool?

    In an agent-tool loop, what happens when the model “calls” the rembourser tool?

    • The model itself performs the refund on the provider's server
    • The model returns a structured request; your program decides whether to execute it after verification
    • The provider's API executes the Python function remotely

End-of-lesson quiz

  1. Why bound an agent’s loop?

    • To speed up the model
    • To avoid infinite loops and control cost and duration
    • Because APIs refuse more than 5 calls
  2. What should you do when a tool raises an exception?

    • Stop the agent with an error trace
    • Send the model a readable error message as the result
    • Ignore the call and repeat the same request
  3. What is the benefit of a simulated model (local function)?

    • It is smarter than the real model
    • It makes loop tests deterministic, free, and network-free
    • It permanently replaces the model in production

The rest of the course

  1. Planning and Memory20 min
  2. Guardrails and Human Approval20 min
  3. Task Orchestration20 min
  4. Failures and Recovery20 min

AI Agents and Automation · Advanced · 1 h 40 min