Français

Advanced tool use

Applied AI — Advanced Level, Session 2

Yann Isola
Preparation Claude Certified Architect

Session objectives

  1. Master the complete lifecycle of a tool call
  2. Design tool definitions of certification quality
  3. Use tool_choice wisely (auto / any / tool)
  4. Force a structured exit with the “false tool” pattern
  5. Dealing with errors: syntactic vs semantic, is_error
  6. Advanced patterns: chaining, conditional selection, caching
  7. Understand the security model

The founding principle

The model never executes anything.
It requests an execution by producing a tool_use block.
It is your code, with your authentications, which executes.

Architect consequences:

  • Validation of entries: at home
  • Access control: at your home
  • Irreversible actions: human confirmation at home

The life cycle in 5 stages

┌─────────────┐  1. request + tools[]          ┌──────────┐
│             │ ─────────────────────────────► │          │
│  YOUR CODE  │  2. stop_reason:"tool_use"     │  MODEL   │
│  (client)   │ ◄───────────────────────────── │ (Claude) │
│             │                                │          │
│ 3. execute  │  4. tool_result (role:user)    │          │
│   the tool  │ ─────────────────────────────► │          │
│             │  5. final response (end_turn)  │          │
└─────────────┘ ◄───────────────────────────── └──────────┘

Cycle 2→4 can loop: it is the heart of any agent.

Step 1 — The query with tools

POST /v1/messages
{
  "model": "claude-sonnet-4-5",
  "max_tokens": 1024,
  "tools": [{
    "name": "get_weather",
    "description": "Gets the current weather for a city...",
    "input_schema": {
      "type": "object",
      "properties": {
        "city": {"type": "string",
                 "description": "City, e.g. \"Paris\"."}
      },
      "required": ["city"]
    }
  }],
  "messages": [{"role": "user", "content": "What's the temperature in Lyon?"}]
}

⚠ Model name volatile — check documentation.

Step 2 — The response tool_use

{
  "role": "assistant",
  "stop_reason": "tool_use",
  "content": [
    {"type": "text", "text": "Let me check the weather in Lyon."},
    {"type": "tool_use",
     "id": "toolu_01XyZ...",
     "name": "get_weather",
     "input": {"city": "Lyon"}}
  ]
}
  • stop_reason: "tool_use" → the signal to detect
  • text may precede the block → never read content[0] blindly
  • id (toolu_...): essential for correlation

Steps 3-4 — Run and Resend

{
  "role": "user",
  "content": [{
    "type": "tool_result",
    "tool_use_id": "toolu_01XyZ...",
    "content": "18°C, cloudy with sunny spells"
  }]
}

Certification Pitfalls:

  • role: "user" — no role tool at Anthropic (≠ OpenAI)
  • The wizard message with tool_use must remain intact in history
  • A tool_use without corresponding tool_resulterror 400

The agentic loop (Python SDK)

messages = [{"role": "user", "content": question}]
while True:
    resp = client.messages.create(model=MODEL, max_tokens=1024,
                                  tools=tools, messages=messages)
    if resp.stop_reason != "tool_use":
        break                                   # final response
    messages.append({"role": "assistant",
                     "content": resp.content})  # historique intact
    results = []
    for block in resp.content:                  # multi-outils !
        if block.type == "tool_use":
            out = run_tool(block.name, block.input)
            results.append({"type": "tool_result",
                            "tool_use_id": block.id,
                            "content": out})
    messages.append({"role": "user", "content": results})

Anatomy of a tool definition

{
  "name": "search_client_contracts",
  "description": "…this is a PROMPT, not documentation…",
  "input_schema": { "type": "object", "properties": {} }
}
Field Rule
name snake_case, verb + object, self-descriptive
description 3-4 sentences: what / when / when NOT / back
input_schema JSON Schema, root object, every field documented

Description IS a prompt

It conditions: triggering, filling of parameters, interpretation of feedback.

Template in 4 sentences:

  1. What the tool does — action verb
  2. When to use it — triggers, including indirect formulations
  3. When NOT to use it — boundaries with other tools
  4. What it returns — format, units, empty case

Counterexample

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

Looking for what? q in what syntax? What does it return?
The model guesses → it guesses wrong.

Corrected version

{
  "name": "search_client_contracts",
  "description": "Full-text search across signed client
    contracts. Use when the user mentions a contract, a
    clause or a deadline. Does NOT cover quotes or
    (utiliser search_invoices). Retourne max 10 contrats
    {id, title, client, excerpt} ; empty list if no result.",
  "input_schema": {
    "type": "object",
    "properties": {
      "query": {"type": "string",
        "description": "Natural language, no boolean operators."},
      "max_results": {"type": "integer", "minimum": 1, "maximum": 10,
        "description": "Default: 5."}
    },
    "required": ["query"]
  }
}

Design principles (certification)

  1. Single Responsibility — no catch-all parameter tool action
  2. Clear naming — the name alone must be sufficient
  3. Document borderline cases — empty, not found, too many results
  4. Minimum of required — each required field = one value hallucination risk
  5. Constrain by the diagramenum, minimum, maximum > instructions in prose

tool_choice — the three modes

Fashion Syntax Warranty Use cases
auto (default) {"type":"auto"} none general assistant
any {"type":"any"} a tool will be called intent router
tool {"type":"tool","name":"x"} this tool will be called structured extraction

Also: {"type": "none"} — definitions visible, calls prohibited.

tool_choice — pitfalls

  • With any / tool: stop_reason is always "tool_use"
  • Forced modes = less prior reasoning in free text
    → for complex tasks, auto + directive prompt can beat forced tool
  • ⚠ any / tool incompatible with extended thinking — check the current doc

Structured output: the “false tool”

Problem: “reply in JSON” in the prompt = no guarantee.

Solution: a tool never executed, which only serves to mold the output:

resp = client.messages.create(
    model=MODEL, max_tokens=1024,
    tools=[extraction_tool],
    tool_choice={"type": "tool", "name": "record_ticket_analysis"},
    messages=[{"role": "user", "content": f"Analyse :\n{ticket}"}],
)
data = next(b for b in resp.content
            if b.type == "tool_use").input   # dict already parsed!

No tool_result to return: one-way call.

The diagram as a mold

{
  "sentiment": {"type": "string",
                "enum": ["positif", "neutre", "negatif"]},
  "urgence":   {"type": "integer", "minimum": 1, "maximum": 5},
  "resume":    {"type": "string",
                "description": "Une phrase, max 25 mots."}
}
  • Syntactic guarantee: types, enums, required fields ✔
  • Semantic guarantee: ✘ — the model can choose the wrong enum value

→ Hence the following question: how to classify errors?

Errors: syntactic vs semantic

Syntactic Semantics
Definition format is violated OK format, wrong content
Examples JSON truncated, incorrect type wrong category, wrong amount
Typical cause max_tokens too low, schema too complex ambiguous description, poorly framed task
Remedy retry, ↑ max_tokens, validate then re-request refine the prompt: descriptions, examples, breakdown

Mnemo: syntactic → retry; semantics → rewriting.

The max_tokens case in full tool_use

{"stop_reason": "max_tokens",
 "content": [ ..., {"type": "tool_use", "input": {"city": "Ly

The JSON of input may be truncated in mid-flight.

Architect's reflex:

if resp.stop_reason == "max_tokens":
    # DO NOT parse: retry with a larger budget
    ...

Always check stop_reason before parsing.

Runtime error: is_error: true

Does your tool fail (404, timeout, exception)? Do not break the loop.

{
  "type": "tool_result",
  "tool_use_id": "toolu_01XyZ...",
  "content": "Error: city \"Lyno\" not found.
              Nearby cities: Lyon, Lens.",
  "is_error": true
}

The model knows how to catch up: correct the parameter, change the tool, or explain the failure to the user.

Write a good error message

✅ For the model: actionable, with suggested correction
✅ Always a tool_result by tool_use, even in failure
❌ Raw stack trace (noise, tokens, internal information leak)
❌ Orphan tool_use → 400 error on next query

Production pattern: validate input with Pydantic before executing, return validation errors in is_error: true.

Multiple tools in one lathe

The model can emit several tool_use blocks in a single response:

"content": [
  {"type": "tool_use", "id": "toolu_A", "name": "convert_currency", ...},
  {"type": "tool_use", "id": "toolu_B", "name": "get_team_budget", ...}
]

Rules:

  • Iterate over all blocks
  • All tool_result in one following user message
  • Each correlated by its tool_use_id

Pattern 1 — Tool chaining

search_client ──► get_client_contracts ──► summarize_contract
   (client_id)          (contract_id)

The model orchestrates over several turns of the loop.

Design key: A's output contains exactly the identifiers that B expects as input. Otherwise the model tinkers — and hallucinates.

Pattern 2 — Conditional selection

The tools array is sent on each request → vary it:

tools = PUBLIC_TOOLS
if session.user.is_authenticated:
    tools += ACCOUNT_TOOLS
if session.user.role == "admin":
    tools += ADMIN_TOOLS

Remove tool from table = structural access control.
“Don't use it” in prompt = decorative access control.

Pattern 3 — Results cache

key = (tool_name, json.dumps(args, sort_keys=True))
if key in cache:
    return cache[key]          # hit: 0 latency, 0 external cost
  • Short TTL (Time To Live) for volatile data
  • Never cache on a is_error: true result
  • prompt caching API (context prefix cache, including tool definitions, Anthropic side)

Security — architect summary

Threat Parade (in YOUR code)
Malicious/delusional settings Strict validation (Pydantic, regex, bounds)
SQL injection, path traversal Parameterized queries, allow-lists
Privilege Escalation Least privilege: read-only accounts, tenant scoping
Irreversible action Human-in-the-loop (confirmation) before execution

Treat each input as an untrusted web form.

Certification checklist

  • stop_reason: "tool_use" = execution request
  • tool_result → message role: "user" + tool_use_id
  • Assistant message kept intact in history
  • 1 tool_result per tool_use, grouped, otherwise 400
  • tool_choice: auto / any / tool / none
  • False tool = guaranteed syntactic, not semantic
  • Syntactic → retry; semantics → rewriting
  • is_error: true: failure remains in the loop
  • Security: client-side execution, least privilege

Workshop (15 min)

Open webpage/index.html:

  1. Flow debugger — unfold the conversation step by step, JSON at each step, then inject the 3 errors: for each, classify syntactic/semantic/protocol + remedy
  2. Schema Validator — pass your definitions from exercise 1

In pairs. Return: 1 trap discovered per pair.

Next session

Session 3 — Agents and orchestration

Multi-turn loops in production, planning,
subagents, state management

Work to be submitted: exercise 3 (multi-tool pipeline) + quiz

Questions?