Français
Applied AI · Advanced 🔴 · Session 2
📝 Teacher's Guide
← Return to program 📄 Source .md

Teacher Guide — Advanced Level, Session 2

“Advanced tool use”

Program : Applied AI — Yann Isola Audience : Solutions architects preparing for Claude Certified Architect certification Duration : 2 hours Prerequisites: Session 1 (Messages API architecture, context management), intermediate Python, basic JSON Schema


Educational objectives

At the end of the session, each participant should be able to:

  1. Describe the complete life cycle of a tool call (tool use): user message → block tool_use → client-side execution → message tool_result → continuation of the model.
  2. Design a definition of a certification quality tool : naming, prompt-description, input_schema documented field by field.
  3. Choose the right one tool_choice (auto , any , tool targeted) depending on the use case.
  4. Force a structured exit via the “fake tool” pattern.
  5. Distinguish and deal with syntactic and semantic errors , including via is_error: true .
  6. Apply advanced patterns : tool chaining, conditional selection, results caching.
  7. Answer certification questions on stop_reason: "tool_use" and edge cases of message flow.

Session plan (120 min)

Block Duration Content Format
0 5 mins Welcome, reminder Session 1, objectives Plenary
1 20 mins The tool_use life cycle, step by step Live demo + slides
2 20 mins Anatomy of a tool definition Slides + review code
3 10 mins tool_choice : the three modes Slides + mini-demo
4 15 mins Structured output: the “false tool” pattern Live demo
5 15 mins Error management (syntactic, semantic, is_error ) Slides + demo
5 mins Break
6 10 mins Advanced multi-tools and patterns Slides
7 15 mins Workshop: flow debugger (interactive web page) Guided practical work
8 5 mins Certification points and pitfalls Plenary

The exercises (file exercises.md ) are given in guided work or at home depending on the time remaining. The quiz closes the session or serves as an asynchronous assessment.


Block 1 — The tool_use life cycle (20 min)

Key message to convey

The model never executes anything. He request an execution producing a block of content tool_use . It is YOUR code, with YOUR identifiers and YOUR permissions, that executes. The model only sees what you send back to it.

This is the most important safety point of the entire session — and an almost certain question in certification.

The 5-step cycle (to draw on the board)

┌─────────────┐   1. Requête + tools[]        ┌─────────────┐
│             │ ────────────────────────────► │             │
│  VOTRE CODE │   2. stop_reason:"tool_use"   │   MODÈLE    │
│  (client)   │ ◄──────────────────────────── │  (Claude)   │
│             │                               │             │
│  3. Vous    │   4. tool_result (role:user)  │             │
│  exécutez   │ ────────────────────────────► │             │
│  l'outil    │   5. Réponse finale           │             │
│             │ ◄──────────────────────────── │             │
└─────────────┘   stop_reason:"end_turn"      └─────────────┘
  1. Request : you send the messages + the table tools (definitions).
  2. Model decision : if the model wants to use a tool, the response contains a block tool_use and the field stop_reason worth "tool_use".
  3. Client-side execution : your code reads name And input of the block, executes the corresponding function.
  4. Returning the result : you add a message to the conversation role: "user" containing a block tool_result with the tool_use_id corresponding.
  5. Pursuit : the model integrates the result and responds (or requests another tool — loop).

Live demo — Raw API (JSON)

Show the HTTP (Application Programming Interface) request in REST format:

POST https://api.anthropic.com/v1/messages
{
  "model": "claude-sonnet-4-5",
  "max_tokens": 1024,
  "tools": [
    {
      "name": "get_weather",
      "description": "Récupère la météo actuelle pour une ville donnée. Retourne la température en Celsius et les conditions. À utiliser dès que l'utilisateur pose une question sur la météo actuelle ou demande s'il doit prendre un parapluie, un manteau, etc.",
      "input_schema": {
        "type": "object",
        "properties": {
          "city": {
            "type": "string",
            "description": "Nom de la ville, en français, sans le pays. Exemple : \"Paris\", \"Genève\"."
          },
          "unit": {
            "type": "string",
            "enum": ["celsius", "fahrenheit"],
            "description": "Unité de température. Par défaut : celsius."
          }
        },
        "required": ["city"]
      }
    }
  ],
  "messages": [
    {"role": "user", "content": "Il fait combien à Lyon ?"}
  ]
}

Model response (to be dissected line by line):

{
  "id": "msg_01AbC...",
  "role": "assistant",
  "stop_reason": "tool_use",
  "content": [
    {
      "type": "text",
      "text": "Je vérifie la météo à Lyon."
    },
    {
      "type": "tool_use",
      "id": "toolu_01XyZ...",
      "name": "get_weather",
      "input": {"city": "Lyon", "unit": "celsius"}
    }
  ]
}

Points of attention to be verbalized:

Then returning the result:

{
  "role": "user",
  "content": [
    {
      "type": "tool_result",
      "tool_use_id": "toolu_01XyZ...",
      "content": "18°C, nuageux avec éclaircies"
    }
  ]
}

Certification trap: THE tool_result is sent in a message role: "user", NOT role: "tool" (unlike the OpenAI API). The wizard message containing the tool_use must be returned as is in the history, otherwise error 400.

Live demo — Python SDK

SDK = Software Development Kit.

import anthropic

client = anthropic.Anthropic()  # clé lue dans ANTHROPIC_API_KEY

tools = [{
    "name": "get_weather",
    "description": (
        "Récupère la météo actuelle pour une ville donnée. "
        "Retourne la température en Celsius et les conditions. "
        "À utiliser dès que l'utilisateur pose une question météo."
    ),
    "input_schema": {
        "type": "object",
        "properties": {
            "city": {"type": "string",
                     "description": "Nom de la ville, ex. \"Paris\"."},
            "unit": {"type": "string", "enum": ["celsius", "fahrenheit"],
                     "description": "Unité. Défaut : celsius."},
        },
        "required": ["city"],
    },
}]

def get_weather(city: str, unit: str = "celsius") -> str:
    # Ici : appel à une vraie API météo, avec VOTRE clé d'API météo.
    return f"18°{'C' if unit == 'celsius' else 'F'}, nuageux"

messages = [{"role": "user", "content": "Il fait combien à Lyon ?"}]

# Boucle agentique minimale
while True:
    response = client.messages.create(
        model="claude-sonnet-4-5",   # ⚠ nom de modèle volatile, vérifier la doc
        max_tokens=1024,
        tools=tools,
        messages=messages,
    )

    if response.stop_reason != "tool_use":
        break  # réponse finale

    # 1. Renvoyer le message assistant TEL QUEL dans l'historique
    messages.append({"role": "assistant", "content": response.content})

    # 2. Exécuter chaque bloc tool_use (il peut y en avoir plusieurs !)
    results = []
    for block in response.content:
        if block.type == "tool_use":
            if block.name == "get_weather":
                output = get_weather(**block.input)
                results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": output,
                })

    # 3. Les résultats repartent dans UN message user
    messages.append({"role": "user", "content": results})

print(response.content[0].text)

To highlight:


Block 2 — Anatomy of a tool definition (20 min)

The description IS a prompt

To insist heavily: the description is not documentation for humans, it is a prompt injected into template context . It conditions:

Good writing heuristic (3-4 sentences):

  1. What the tool does (a sentence, action verb).
  2. When to use it (explicit triggers, including indirect wording).
  3. When NOT to use it (borders with other tools).
  4. What it returns (format, units, empty cases).

Counterexample vs good example (collective code review)

Bad :

{
  "name": "search",
  "description": "Recherche",
  "input_schema": {
    "type": "object",
    "properties": {"q": {"type": "string"}},
    "required": ["q"]
  }
}

Problems to be identified by the group: generic name (searching for what?), unnecessary description, parameter q undocumented (what syntax? what language? Boolean operators?).

Good :

{
  "name": "search_client_contracts",
  "description": "Recherche en texte intégral dans la base des contrats clients signés. À utiliser quand l'utilisateur mentionne un contrat, une clause, un client ou une échéance contractuelle. Ne couvre PAS les devis ni les factures (utiliser search_invoices). Retourne au maximum 10 contrats avec id, titre, client et extrait pertinent ; retourne une liste vide si aucun résultat.",
  "input_schema": {
    "type": "object",
    "properties": {
      "query": {
        "type": "string",
        "description": "Termes de recherche en langage naturel. Pas d'opérateurs booléens. Exemple : \"clause de résiliation Acme 2025\"."
      },
      "client_id": {
        "type": "string",
        "description": "Optionnel. Identifiant client (format CLI-XXXX) pour restreindre la recherche à un seul client."
      },
      "max_results": {
        "type": "integer",
        "minimum": 1,
        "maximum": 10,
        "description": "Nombre maximal de résultats. Défaut : 5."
      }
    },
    "required": ["query"]
  }
}

Design principles (to be retained for certification)

  1. Single responsibility : one tool = one action. No manage_database(action, ...) catch-all: the model is wrong more often on polymorphic tools.
  2. Clear naming : snake_case , verb + object (create_invoice , get_user_profile ). The name alone should be enough to guess the function.
  3. Document borderline cases : what happens if empty, if too many results, if the entity does not exist? The model manages better what it anticipates.
  4. Minimum fields required : each mandatory field is an opportunity for error or hallucination of value. Make anything with a reasonable defect optional — and document that defect.
  5. enum and JSON Schema constraints (minimum , maximum , format ) rather than instructions in prose: the diagram constrains better than the description.

JSON Schema — express callback

JSON Schema (JavaScript Object Notation Schema — JSON data description schema): input_schema must be an object ("type": "object") at the root level. Useful subset in certification: type , properties , required , enum , description , items (arrays), nested objects, minimum /maximum , format (indicative, not strictly validated).


Block 3 — tool_choice : the three modes (10 min)

Fashion Syntax Behavior Use cases
car (default) {"type": "auto"} The model decides: tool or text response General conversational assistant
any {"type": "any"} The model MUST call a tool, but chooses which one Intent router, dispatch
tool {"type": "tool", "name": "extract_data"} The model MUST call THIS tool Structured extraction, deterministic pipeline

Certification Points:

Mini-demo: same question (“Analyze this support ticket”) launched with auto Then tool forced on an extraction tool, compare the outputs.


Block 4 — Structured output: the “false tool” pattern (15 min)

The problem

You want guaranteed JSON that conforms to a schema (to insert into the database, feed a pipeline, etc.). Asking “reply in JSON” in the prompt often works, but without guarantee: stray text around, missing field, renamed key.

The solution

Define a tool that does not execute anything — it only serves to constrain the shape of the output — and force its call with tool_choice .

extraction_tool = {
    "name": "record_ticket_analysis",
    "description": "Enregistre l'analyse structurée d'un ticket de support client.",
    "input_schema": {
        "type": "object",
        "properties": {
            "sentiment": {
                "type": "string",
                "enum": ["positif", "neutre", "negatif"],
                "description": "Sentiment global du client."
            },
            "urgence": {
                "type": "integer", "minimum": 1, "maximum": 5,
                "description": "Urgence de 1 (faible) à 5 (critique)."
            },
            "categorie": {
                "type": "string",
                "enum": ["facturation", "technique", "commercial", "autre"],
                "description": "Catégorie principale du ticket."
            },
            "resume": {
                "type": "string",
                "description": "Résumé en une phrase, max 25 mots."
            }
        },
        "required": ["sentiment", "urgence", "categorie", "resume"]
    }
}

response = client.messages.create(
    model="claude-sonnet-4-5",  # ⚠ volatile
    max_tokens=1024,
    tools=[extraction_tool],
    tool_choice={"type": "tool", "name": "record_ticket_analysis"},
    messages=[{"role": "user", "content": f"Analyse ce ticket :\n{ticket}"}],
)

data = next(b for b in response.content if b.type == "tool_use").input
# data est un dict Python déjà parsé : {"sentiment": "negatif", "urgence": 4, ...}

To highlight:


Block 5 — Error handling (15 min)

Taxonomy: syntactic vs semantic

Kind Definition Example Remedy
Syntax Output violates expected format JSON truncated (max_tokens too low), required field missing, incorrect type Try again (retry), increase max_tokens , validate then re-request
Semantics The format is valid but the content is wrong Wrong category, crazy city, wrong amount Refine the prompt : better description, examples, stricter enum, division of the task

Mnemonic rule: syntactic → retry; semantics → rewriting . A retry on a semantic error will often give the same error (the model is consistent with its understanding); rewriting the prompt on a syntactic error treats the symptom, not the cause (often max_tokens or diagram too complex).

Common cause of syntactic error to be aware of for certification: stop_reason: "max_tokens" in the middle of a block tool_use → the JSON of input is truncated. Detection: check stop_reason BEFORE parsing.

Return an error to the model: is_error: true

When YOUR tool fails (exception, 404, timeout), do not break the loop: send the error back to the model, it often knows how to recover (reformulate, change parameters, try another tool, or explain the failure to the user).

{
  "role": "user",
  "content": [
    {
      "type": "tool_result",
      "tool_use_id": "toolu_01XyZ...",
      "content": "Erreur : ville \"Lyno\" introuvable. Villes proches : Lyon, Lens.",
      "is_error": true
    }
  ]
}

In Python:

try:
    output = get_weather(**block.input)
    result = {"type": "tool_result", "tool_use_id": block.id,
              "content": output}
except CityNotFoundError as e:
    result = {"type": "tool_result", "tool_use_id": block.id,
              "content": f"Erreur : {e}. Vérifie l'orthographe de la ville.",
              "is_error": True}

Error message best practices:

Architect’s anecdote

A robust pattern in production: validate block.input with Pydantic (Python data validation library) BEFORE running, and return validation errors in is_error: true . The model corrects its parameters in the next round in the vast majority of cases.


Block 6 — Advanced multi-tools and patterns (10 min)

Multiple tools in one lathe

The model can emit several blocks tool_use in the same answer (parallel calls) or chain over several turns (sequential calls). Your code must:

Three architectural patterns

  1. Tool chaining : the output of tool A feeds the input of tool B, orchestrated by the model over several turns. Example : search_client get_client_contracts summarize_contract . Design: ensure that A's output contains exactly the identifiers that B needs (e.g. A returns client_id , B takes client_id in parameter).
  2. Conditional selection : expose different tools depending on the state of the session (authenticated user or not, active module, etc.). The table tools is sent EACH request: you can vary it dynamically. This is client-side access control, more reliable than “don’t use it” prompt.
  3. Tool result caching : memorize identical calls on the client side (same tool, same parameters) during a session. Saves latency and external API costs. Beware of invalidation (TTL — Time To Live, lifespan — short for volatile data). Do not confuse with prompt caching of the API, which hides the context prefix (including tool definitions) on the Anthropic side.

Safety — the non-negotiable


Block 7 — Workshop: flow debugger (15 min)

Open webpage/index.html (works offline). Two modules:

  1. Flow debugger tool_use : participants advance step by step in a complete conversation (request → tool_use → execution → tool_result → final answer), see the exact JSON at each step, and can inject errors (truncated JSON, tool_use_id incorrect, runtime error) to observe the expected processing.
  2. Tool Schema Validator : paste a tool definition, obtain validation against the specification + quality suggestions (description too short, undocumented fields, etc.).

Workshop instructions: each pair must (a) carry out the nominal scenario, (b) inject the 3 errors and note for each whether it is syntactic or semantic and the remedy, © pass to the validator the diagram of exercise 1 if it is already written.


Block 8 — Certification points and pitfalls (5 min)

Checklist to recite:


Material and logistics

Frequent errors of participants (field feedback)

  1. Forgot to resend the assistant message in the history → error 400 misunderstood. Reproducing the error voluntarily is the best vaccine.
  2. Seek response.content[0] instead of iterating over blocks.
  3. To confuse is_error: true (runtime error returned TO model) with Anthropic API HTTP error.
  4. Write tool descriptions for humans (“This function allows you to…”) instead of operational prompts.
  5. Set all fields required “for security” — it’s the opposite of security.