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:
- Describe the complete life cycle of a tool call (tool use): user message → block
tool_use→ client-side execution → messagetool_result→ continuation of the model. - Design a definition of a certification quality tool : naming, prompt-description,
input_schemadocumented field by field. - Choose the right one
tool_choice(auto,any,tooltargeted) depending on the use case. - Force a structured exit via the “fake tool” pattern.
- Distinguish and deal with syntactic and semantic errors , including via
is_error: true. - Apply advanced patterns : tool chaining, conditional selection, results caching.
- 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" └─────────────┘
- Request : you send the messages + the table
tools(definitions). - Model decision : if the model wants to use a tool, the response contains a block
tool_useand the fieldstop_reasonworth"tool_use". - Client-side execution : your code reads
nameAndinputof the block, executes the corresponding function. - Returning the result : you add a message to the conversation
role: "user"containing a blocktool_resultwith thetool_use_idcorresponding. - 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:
stop_reason: "tool_use"— this is THE signal that your loop must detect.- The block
contentmay contain text Before the blocktool_use(visible chain of thought). Never assume thatcontent[0]is thetool_use. - L'
idof the blocktool_use(toolu_...) is mandatory to correlate the result.
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:
- The loop
while: it is the skeleton of any agent. The model can perform several turns of the tool before responding. for block in response.content: natively manages the multi-tool case.- All the
tool_resultin the same turn go in only one messageuser.
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:
- When the model chooses the tool (trigger),
- how it fulfills the parameters,
- what he wait in return.
Good writing heuristic (3-4 sentences):
- What the tool does (a sentence, action verb).
- When to use it (explicit triggers, including indirect wording).
- When NOT to use it (borders with other tools).
- 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)
- Single responsibility : one tool = one action. No
manage_database(action, ...)catch-all: the model is wrong more often on polymorphic tools. - Clear naming :
snake_case, verb + object (create_invoice,get_user_profile). The name alone should be enough to guess the function. - Document borderline cases : what happens if empty, if too many results, if the entity does not exist? The model manages better what it anticipates.
- 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. enumand 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:
- With
anyOrtool, the response contains always Atool_useAndstop_reasonworth"tool_use". - With
any/tool, the model does not produce free text reasoning before the call in the same way as inauto— settings can be slightly less thoughtful. For complex tasks,auto+ prompt directive is sometimes more reliable thantoolstrength. - ⚠ Interaction with extended “thinking”: forced modes (
any,tool) are incompatible with extended thinking at the time of writing this guide — check current documentation. - There is also
{"type": "none"}to prohibit any tool calls while keeping definitions in context.
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:
- No need to return a
tool_result: we don't continue the conversation, we just recoverinput. It’s a “one-way call.” - The SDK parses the JSON for you —
block.inputis already a dictionary. - THE
enum+minimum/maximumgive strong structural validation, but not semantic (the model can enter the wrong category while respecting the enum) → perfect transition to Block 5. - ⚠ Mention that native structured output modes are rapidly evolving on the API side; the “false tool” pattern remains the portable reference technique and on the certification program.
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:
- Written for the model , not for a log: actionable, with suggested correction.
- Never return a complete raw stack trace (noise, wasted tokens, information leak).
- Always return ONE
tool_resultbytool_usereceived, even in case of failure — atool_useorphan withouttool_resultcauses a 400 error on the next request.
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:
- iterate over ALL blocks
tool_useof the response, - return ALL
tool_resultcorrespondents in the messageusernext, each with the correcttool_use_id.
Three architectural patterns
- 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 returnsclient_id, B takesclient_idin parameter). - Conditional selection : expose different tools depending on the state of the session (authenticated user or not, active module, etc.). The table
toolsis sent EACH request: you can vary it dynamically. This is client-side access control, more reliable than “don’t use it” prompt. - 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
- The tools run in your code, with your authentications . The model holds no keys and executes nothing.
- Consequence: any validation, authorization and limitation must be in your code . Treat each
inputtool as unreliable input (like a web form): SQL injection, file paths (../), amounts, permissions. - Principle of least privilege: the tool
get_invoicequeries the database with a read-only account, restricted to the tenant of the current user — not with the admin account. - Irreversible actions (payment, deletion, email sending): human confirmation (human-in-the-loop) on the client side before execution.
Block 7 — Workshop: flow debugger (15 min)
Open webpage/index.html (works offline). Two modules:
- 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_idincorrect, runtime error) to observe the expected processing. - 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:
-
stop_reason: "tool_use"= the model expects results from tools. -
tool_resultleaves in a messagerole: "user", withtool_use_idOBLIGATORY. - The wizard message containing the(s)
tool_usemust be kept intact in the history. - A
tool_resultbytool_use, all in the same user message, otherwise error 400. - The tool description is a prompt;
input_schemais JSON Schema with rootobject. -
tool_choice:auto/any/tooltarget /none;anyAndtoolguarantee a call. - Fake tool +
tool_choice: tool= syntactically guaranteed structured output. - Syntactic → retry; semantics → refine the prompt.
-
is_error: trueto report an execution failure without breaking the loop. - Security: client-side execution, least privilege, input validation, human-in-the-loop for the irreversible.
Material and logistics
- Project:
slides/slides.md(Marp format/markdown compatible). - Demos: API account with test key, Python ≥ 3.10,
pip install anthropic pydantic. ⚠ Model names and prices are changing: check https://docs.anthropic.com before the session. - Interactive page:
webpage/index.html— no server required. - Have an offline plan B: screenshots of API responses if the room network is down.
Frequent errors of participants (field feedback)
- Forgot to resend the assistant message in the history → error 400 misunderstood. Reproducing the error voluntarily is the best vaccine.
- Seek
response.content[0]instead of iterating over blocks. - To confuse
is_error: true(runtime error returned TO model) with Anthropic API HTTP error. - Write tool descriptions for humans (“This function allows you to…”) instead of operational prompts.
- Set all fields
required“for security” — it’s the opposite of security.