Accueil / Articles

Outil Claude_use Fin à Fin : Le cycle agentiel en trois itérations

Outil Claude use bout en bout : une seule requête API Messages à travers le cycle agentique, avec des règles de définition d’outil, de choix d’outil, d’appels parallèles et de résultat d’outil.

Éditeur : REACTAPP.TOP19 min de lecture4379 mots

Sources: Part III | ‘tool_use’, End to End: The Agentic Loop in Three Iterations

Pourquoi c’est à vous d’écrire la boucle

Travailler avec tool_use directement via l’API Messages (anthropic.Anthropic() suivi de client.messages.create(...)) vous met la boucle entre les mains. Votre code déclare les outils, examine la raison d’arrêt, analyse les demandes de Claude, les exécute et renvoie le résultat obtenu. Écrire cette boucle manuellement constitue la base de tout système utilisant des outils, et c’est un sujet standard dans le programme de formation Claude Certified Architect.

Tout d’abord, un rappel : l’API Messages est sans état. Rien n’est mémorisé entre les appels, donc chaque demande contient toute la conversation, et une étape impliquant des outils nécessite généralement plusieurs demandes plutôt qu’une seule, répétées jusqu’à ce que Claude ait ce dont il a besoin. Le reste suit une seule demande pour effectuer tout ce cycle complet.

L’exemple en cours est un petit assistant de planification. Un utilisateur demande s’il doit emporter un parapluie pour une réunion à Londres demain, et à quelle heure partir de chez lui pour arriver à l’heure. Pour y répondre, il faut trois informations distinctes : les détails de la réunion, la prévision météorologique et le temps de trajet. Observez comment Claude divise la question en trois itérations, et quelle partie du travail revient à votre code plutôt qu’au modèle.

1. La structure du boucle

Chaque échange impliquant l’utilisation d’outils suit un cycle en quatre phases :

  1. Déclarer les outils requis dans la demande.
  2. Claude demande l’un ou plusieurs d’entre eux, ce qui est indiqué par stop_reason: "tool_use".
  3. Votre code exécute ces outils et collecte les résultats.
  4. Vous renvoyez les résultats et appelez à nouveau l’API.

Les phases deux à quatre se répètent, et chaque passage compte comme une itération. La boucle se termine lorsque Claude cesse de demander des outils et renvoie une fin normale (end_turn) ou une autre raison d’arrêt. Comme l’API est sans état, vous construisez vous-même la conversation et la renvoyez à chaque fois. Rien ne progresse à moins que votre code ne le fasse.

2. Déclarer les outils

Claude n’a accès qu’aux outils que vous spécifiez. Ceux-ci se trouvent dans le paramètre de niveau supérieur tools de la requête, qui contient un tableau de définitions. Chaque fois que tools est présent, l’API assemble un prompt système caché à partir d’eux avant le message de l’utilisateur. Ce prompt caché est facturé : chaque nom, description et schéma consomme des tokens d’entrée à chaque requête, même si Claude n’utilise jamais l’outil en question.

Chaque outil client nécessite trois champs :

  • name est un identifiant unique qui doit respecter la forme ^[a-zA-Z0-9_-]{1,64}$. Il s’agit de la chaîne de caractères retournée dans le bloc tool_use, et votre code l’utilise pour appeler la fonction appropriée.
  • description est une explication en langage courant de la fonction du outil, du moment opportun pour l’utiliser, ainsi que de la signification de chaque paramètre. Rien d’autre n’influence autant une utilisation correcte, il convient donc de la rédiger comme une spécification plutôt que comme une simple étiquette.
  • input_schema est un JSON Schema décrivant les arguments : leurs types, indiquant s’ils sont required, et ce qu’ils représentent.

Voici les trois outils utilisés dans l’exemple :

"tools": [
  {
    "name": "get_calendar_event",
    "description": "Look up a calendar event by a natural-language query. Returns the event's start time and location.",
    "strict": true,
    "input_schema": {
      "type": "object",
      "properties": {
        "query": { "type": "string", "description": "What to search for, e.g. 'meeting in London tomorrow'" }
      },
      "required": ["query"],
      "additionalProperties": false
    }
  },
  {
    "name": "get_weather",
    "description": "Get the forecast for a city on a given date. Returns condition and temperature in Celsius.",
    "strict": true,
    "input_schema": {
      "type": "object",
      "properties": {
        "city": { "type": "string", "description": "City name, e.g. 'London'" },
        "date": { "type": "string", "description": "ISO date, e.g. '2026-07-14'" }
      },
      "required": ["city", "date"],
      "additionalProperties": false
    }
  },
  {
    "name": "get_travel_time",
    "description": "Estimate door-to-door travel time between two places. Returns minutes.",
    "strict": true,
    "input_schema": {
      "type": "object",
      "properties": {
        "origin": { "type": "string", "description": "Starting location" },
        "destination": { "type": "string", "description": "Ending location" }
      },
      "required": ["origin", "destination"],
      "additionalProperties": false
    }
  }
]

Définissez-les avec soin. Les conseils d’Anthropic concernant des outils efficaces se résument à quelques habitudes :

  • Soyez précis quant aux noms des outils. get_current_weather en dit plus à Claude que weather.
  • Donnez des noms et des descriptions des paramètres sans ambiguïté. Préférez user_id à user afin que la bonne valeur soit fournie, et décrivez chaque paramètre, car un champ bien nommé mais dont la description est vague attire toujours des entrées mal formatées.
  • Gardez l’ensemble d’outils limité. Un ensemble trop volumineux encombre le prompt système généré et rend le choix du modèle moins clair.
  • Vérifiez tout. Un schéma contrôle la structure, pas l’intention, donc vérifiez chaque entrée reçue par votre code.

Deux ajouts dans ces définitions méritent une attention particulière.

  1. strict: true est le plus important. Il se trouve au niveau le plus élevé de chaque outil, à côté de name, description et input_schema, et active un échantillonnage soumis à des contraintes grammaticales pour les arguments de l’outil, de sorte que le modèle ne peut pas générer d’input qui contreviendrait à votre schéma. Si cette option n’est pas activée, Claude génère généralement des arguments valides mais peut parfois dévier, par exemple en utilisant "2" là où un nombre 2 était requis, une valeur en dehors d’un enum, ou un champ obligatoire manquant. Chaque déviation provoque une erreur en temps de exécution dans le code qui utilise l’appel. En activant strict: true, l’input contenu dans tout bloc tool_use correspondra certainement à input_schema, et le nom sera assurément celui de l’un de vos propres outils.
  2. Une règle de schéma est associée à une garantie : chaque objet doit avoir la valeur additionalProperties: false, d’où sa présence dans tous les outils mentionnés ci-dessus. Une utilisation stricte de ces outils fait partie de la fonctionnalité de sorties structurées d’Anthropic. Au moment de la rédaction de ce texte, cette fonctionnalité était généralement disponible sur les modèles actuels tels qu’Opus 4.8, tandis que les modèles plus anciens comme Sonnet 4.5 exigeaient l’en-tête bêta structured-outputs-2025-11-13. Les schémas compilés restent en cache pendant 24 heures, ce qui évite tout surcoût de compilation grâce à un cache prêt à l’emploi.

Vous pouvez également ajouter cache_control aux définitions d’outils afin de les mettre en cache entre les requêtes et d’éviter les frais de retraitraitement, ce qui s’avère avantageux lorsque l’ensemble des outils est volumineux et stable.

Les définitions ne décrivent que les entrées

Une définition d’outil ne concerne que l’entrée, c’est-à-dire ce que Claude vous envoie. Cela surprend les utilisateurs de frameworks qui déclarent des types de retour. En termes simples :

  • Une définition comprend obligatoirement name, description et input_schema, tandis que strict, input_examples et cache_control sont des éléments optionnels. Il n’existe ni output_schema ni error_schema.
  • Les valeurs de retour sont déterminées en temps de exécution par votre code, puis renvoyées à l’intérieur du bloc tool_result décrit à la section 6. Le champ content peut contenir soit une chaîne de caractères, soit une liste de blocs, et l’API ne vérifie rien, car il n’y a pas de schéma de validation. Une précaution pratique : une outil peut renvoyer un objet DTO, et le parseur standard le transforme en une simple chaîne de caractères via sa méthode toString. Utiliser cette forme simplifiée fonctionne, mais ce n’est pas idéal. Préférez convertir les DTO en JSON structuré.
  • Les erreurs relèvent également du temps de exécution. Signalez une appel échoué en indiquant is_error: true dans tool_result ainsi qu’un message informatif dans content ; la définition ne prévoit aucun type d’erreur déclaratif. Par défaut, le SDK transmet l’exception levée à Claude en tant que résultat d’erreur, et Claude s’adapte en conséquence.
  • La déséquilibre est intentionnel. Les définitions servent à montrer à Claude comment invoquer une outil. Le résultat n’est qu’un contexte supplémentaire qui rejoint le cycle ; par conséquent, à part le tool_use_id qui le lie à sa demande, aucun contrat n’est nécessaire.
  • Vous souhaitez un format de sortie garanti ? Vérifiez-le vous-même dans le code avant d’envoyer quoi que ce soit, ou appliquez des sorties structurées (output_config.format) à la réponse finale que Claude rédige à partir de ces résultats. Les outils MCP disposent de leur propre outputSchema, mais il s’agit d’une fonctionnalité de la couche MCP et non des définitions natives.

Contrôler si Claude appelle un outil

tool_choice détermine si des outils peuvent être appelés à chaque tour et lesquels.

  • Ce champ se trouve au niveau supérieur de la demande, à côté de tools et messages, et n’appartient à aucun outil en particulier.
  • Ce qui est restreint, c’est la sortie que Claude peut produire. La logique reste inchangée, car Claude lit l’intégralité de la demande quel que soit le cas.
  • Son champ d’application concerne une seule demande, jamais de manière globale. L’API est sans état, donc tool_choice ne gouverne que la réponse qui l’accompagne, ce qui correspond à une seule itération. Pour la conserver entre les itérations, il faut la renvoyer à chaque fois. Varier cette valeur à chaque itération est légitime : forcer l’utilisation d’un outil au début, utiliser auto plus tard, et none pour une réponse textuelle finale.

L’objet ressemble à ceci :

{
  "type": "auto" | "any" | "tool" | "none",
  "name": "get_weather",              // required ONLY when type is "tool"
  "disable_parallel_tool_use": false  // optional; default false
}

type est obligatoire et peut prendre l’une de quatre valeurs. name n’est requis que lorsque type vaut "tool". disable_parallel_tool_use est optionnel et limite le nombre d’outils qui peuvent être appelés à la fois.

auto. Claude décide : zéro, un ou plusieurs outils, ou une réponse textuelle. Les appels en parallèle sont autorisés. Une réponse en texte brut est possible, et Claude peut donner des explications avant les appels. C’est le mode par défaut lorsque des tools sont fournis. En définissant disable_parallel_tool_use: true, on limite le nombre d’outils à un seul, bien que zéro reste possible. Ce mode convient aux assistants de conversation et aux boucles agentes où différentes étapes nécessitent des actions différentes. C’est le seul mode qui permet à Claude de réfléchir à voix haute avant d’agir, et le seul mode, à part none, qui fonctionne avec la réflexion étendue.

any. Claude doit appeler un outil mais choisit lequel. Au moins une appel est effectuée, et comme l’utilisation en parallèle est activée par défaut, plusieurs blocs tool_use peuvent être renvoyés, pas nécessairement un seul. Un seul bloc est généré uniquement avec disable_parallel_tool_use: true. Aucun texte brut n’est autorisé, il n’y a pas de raisonnement préalable, et cela n’est jamais la valeur par défaut. L’utilisez lorsque le résultat obtenu via un outil est obligatoire mais que l’outil varie, comme c’est le cas pour les routeurs et les classificateurs.

outil. Claude doit appeler l’outil que vous avez nommé, donc name est obligatoire. Au moins une appel à cet outil est effectuée (il peut l’appeler plusieurs fois en parallèle), et exactement une fois avec disable_parallel_tool_use: true. Aucun texte brut, aucune explication préalable. Cela convient à l’extraction structurée en une seule étape, à une étape initiale obligatoire, ou au schéma JSON via outil, par exemple {"type": "tool", "name": "get_weather"}. Une demande mentionnant un outil qui n’est pas dans tools renvoie une erreur 400, et forcer l’utilisation d’un outil ne la limite pas à une seule appel sauf si l’utilisation en parallèle est désactivée.

none. Claude ne peut pas utiliser d’outils cette fois-ci même s’ils sont définis, et le stop_reason ne peut pas être tool_use. Le texte brut est autorisé, tout comme les explications préliminaires. C’est la valeur par défaut lorsque aucun tools n’est fourni. Utilisez-la pour forcer une étape uniquement textuelle, comme une synthèse finale, tout en laissant l’array tools inchangé. Elle désactive les outils sans modifier l’array, bien que le passage en ce mode invalide néanmoins les blocs de messages en mémoire.

Plusieurs détails importants peuvent facilement être manqués :

  • Les modes forcés suppriment le préambule. Avec any ou tool, l’API remplit à l’avance l’étape de l’assistant, de sorte que le bloc tool_use arrive vide : aucune explication ni raisonnement, quel que soit votre demande. Pour des explications visibles, utilisez auto.
  • La réflexion étendue nécessite auto ou none. L’utilisation forcée d’outils ne peut pas être combinée avec elle, et l’API rejette cette combinaison.
  • Règle de comptage pour disable_parallel_tool_use. auto signifie au plus un, any et tool signifient exactement un, tandis que la valeur par défaut false permet des appels en parallèle sur les modèles Claude 4.
  • Coût du cache. Changer tool_choice entre les requêtes invalide les blocs de messages en cache. Les définitions des outils et le prompt système restent en cache, mais la conversation est retraitée.
  • Associez-le à strict. tool_choice s’assure qu’un outil est exécuté, et strict s’assure que ses arguments respectent le schéma. Ensemble, cela permet d’obtenir à chaque tour un résultat structuré et typé.

Dans une demande :

response = client.messages.create(
    model="claude-opus-4-8",
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "any", "disable_parallel_tool_use": True},  # must call exactly one tool
    messages=messages,
)

Deux règles de conception en découlent. Réservez l’utilisation forcée des outils aux cas où un résultat concret est vraiment nécessaire, et restez sur auto si une réponse en prose est acceptable. De plus, lorsque l’utilisation d’un outil de classification est imposée, ajoutez une option "autre" à son énumération afin que Claude puisse rejeter une catégorie au lieu d’étiqueter incorrectement les données qu’il ne peut classer.

3. Première itération : Claude demande des outils

La demande contient l’array des outils, une tool_choice de votre choix ainsi que la question de l’utilisateur :

messages = [{"role": "user",
             "content": "I have a meeting in London tomorrow
                         — should I bring an umbrella, and
                         when should I leave home to be on time?"}]

response = client.messages.create(
    model="claude-opus-4-8",
    max_tokens=1024,
    tools=tools,                    # the three strict definitions from Section 2
    tool_choice={"type": "auto"},   # let Claude decide whether, and what, to call
    messages=messages,
)

Claude analyse la question et constate que deux des trois besoins qu’elle comporte ne dépendent pas l’un de l’autre : la recherche dans le calendrier et la prévision peuvent être exécutées en même temps. Il demande donc les deux outils dans une seule réponse. Les appels indépendants effectués ensemble sont appelés utilisation parallèle d’outils, et ils apparaissent sous forme de plusieurs blocs tool_use au cours d’une seule manche.

{
  "id": "msg_01...",
  "role": "assistant",
  "stop_reason": "tool_use",
  "content": [
    { "type": "text", "text": "Let me check your meeting details and the London forecast." },
    { "type": "tool_use", "id": "toolu_01Cal", "name": "get_calendar_event",
      "input": { "query": "meeting in London tomorrow" } },
    { "type": "tool_use", "id": "toolu_01Wx", "name": "get_weather",
      "input": { "city": "London", "date": "2026-07-14" } }
  ]
}

Trois éléments se détachent dans cette réponse.

  1. stop_reason vaut tool_use, ce qui signifie que la tour n’est pas terminée et que Claude vous attend.
  2. Le tableau content contient un mélange de données. Un bloc text de narration précède fréquemment les blocs tool_use, de sorte que les appels aux outils ne se trouvent pas toujours à une position fixe.
  3. Chaque bloc tool_use possède un id (préfixé par toolu_), un name ainsi qu’un objet input conforme à votre schéma.

Trouvez ces appels en itérant à travers le contenu et en filtrant par type, jamais par indice :

tool_calls = [block for block in response.content if block.type == "tool_use"]

Cela entraîne deux appels ici. Chaque id sert de référence pour récupérer le résultat de cet appel, et c’est par la correspondance entre id et résultat que Claude associe les réponses aux questions lorsqu’il y en a plusieurs en attente.

Remarque pour les modèles Claude 4 : la réflexion étendue peut être intercalée, de sorte que le raisonnement peut avoir lieu entre les appels aux outils ainsi qu’avant eux. Lorsque la réflexion est activée, il faut transmettre les blocs de réflexion avec le reste de la réponse de l’assistant, sinon la suite oublie ce raisonnement.

4. Exécution des outils

Le travail passe maintenant à votre code. Claude a demandé deux outils mais n’en a exécuté aucun. Exécutez tout ce qu’il a demandé, collectez tous les résultats et envoyez une seule réponse de suivi les regroupant. Claude attend que chaque appel qu’il a effectué dispose d’un résultat correspondant.

Il vous appartient de décider si l’exécution doit être parallèle ou séquentielle, car Claude ne le spécifie pas. La réponse indique ce qu’il faut appeler, mais pas comment le planifier. Les appels indépendants, tels que les recherches de calendrier ou de météo, peuvent être exécutés en même temps pour réduire la latence. Lorsqu’un appel dépend du résultat d’un autre, exécutez-les dans l’ordre. Le modèle laisse entrevoir cette dépendance en regroupant les tâches indépendantes en blocs parallèles et en reportant les tâches dépendantes à un tour ultérieur, ce qui se produit lors de la deuxième itération. Les threads, l’asynchrone ou une file d’attente de tâches sont des choix à faire au niveau de votre boucle, et non de l’API.

Considérez l’exécution en production comme une entrée/sortie peu fiable qui peut échouer :

  • Prévoyez un délai de timeout pour chaque outil, afin qu’un appel lent ne bloque pas tout le processus.
  • Préférez autant que possible des opérations idempotentes, car les tentatives de réexécution et les redémarrages sont inévitables dans un agent.
  • Gérer les échecs un par un. Lorsqu’un des deux outils fonctionnant en parallèle génère une erreur, il faut tout de même renvoyer un résultat pour cet outil (plus d’informations sur is_error ci-dessous) au lieu d’interrompre l’exécution.
  • Mesurer le cycle. La latence par outil, le nombre d’appels, l’utilisation des tokens et les taux d’erreur permettent de déterminer si un agent est en bon état ou s’il se détériore progressivement.

5. Étapes avant et après l’exécution d’un outil

Assumer la responsabilité du cycle signifie gérer deux phases : celle qui s’étend de « Claude a demandé un outil » à « l’outil a été exécuté », et celle qui va de « l’outil a été exécuté » à « le résultat a été renvoyé ». Elles méritent d’avoir des noms, car la qualité de leur conception fait la majeure partie de la différence entre un agent sûr et un agent risqué.

Une étape préalable à l’utilisation de l’outil s’exécute avant l’exécution proprement dite. Elle examine la requête soumise et décide de l’exécuter telle quelle, de la modifier ou de la refuser. C’est là que s’effectue le contrôle, avec Claude qui propose des suggestions et votre code qui prend la décision finale.

Une étape post-utilisation de l’outil s’exécute après l’exécution et avant que le résultat ne revienne à Claude. Elle traite la sortie brute en la censurant, en la tronquant, en la reformattant ou en en enregistrant les logs.

Dans un boucle du Client SDK, il s’agit simplement de fonctions qui entourent l’exécution :

for block in tool_calls:
    if not pre_tool_use(block):                       # validate / guard
        results.append(error_result(block.id, "Blocked by policy."))
        continue
    output = execute(block.name, block.input)         # run the tool
    output = post_tool_use(block, output)             # redact / log / reshape
    results.append(tool_result(block.id, output))

La phase précédant l’utilisation de l’outil est celle où se trouvent les véritables mesures de sécurité, et c’est la responsabilité de l’opérateur, pas du modèle. Modèles de production courants :

  • Vérifier les arguments conformément à sa propre politique ainsi qu’au schéma défini.
  • Refuser catégoriquement les opérations destructrices : rm -rf pour un outil en ligne de commande, DROP TABLE pour un outil de base de données.
  • Demander l’approbation d’une personne pour toute action à fort impact, comme l’écriture dans l’environnement de production.
  • Supprimer les informations sensibles des arguments avant qu’ils ne soient transmis à un service externe.
  • Enregistrer chaque tentative d’appel afin de disposer d’un historique pour les audits et le respect des réglementations.

Ensuite, la phase suivant l’utilisation de l’outil s’occupe de ce qui est revenu, en supprimant les identifiants ou les données personnelles avant que les résultats n’entrent dans le contexte, en éliminant les en-têtes trop volumineux et en enregistrant les résultats obtenus.

6. Retour des résultats

Répondez à une étape tool_use par un message user contenant une liste de blocs tool_result, un pour chaque bloc tool_use dans la réponse, chacun étant associé à son tool_use_id. Ajoutez d’abord l’étape de l’assistant à la liste de messages en cours, puis les résultats en tant que nouvelle étape utilisateur. L’itération un se présente comme ceci :

{
  "role": "user",
  "content": [
    {
        "type": "tool_result",
        "tool_use_id": "toolu_01Cal",
        "content": "{\"start\": \"2026-07-14T15:00\", \"location\": \"Canary Wharf, London\"}"
    },
    {
        "type": "tool_result",
        "tool_use_id": "toolu_01Wx",
        "content": "{\"condition\": \"rain\", \"temp_c\": 12}"
    }
  ]
}

Deux formes de contenu. Le content d’un résultat peut être une chaîne de caractères simple, la valeur de retour sérialisée comme dans l’exemple, ou une liste de blocs de contenu. Les blocs contiennent plus que du texte, car text et image peuvent être mélangés, permettant à un outil de graphiques ou de capture d’écran de fournir à Claude une image réelle. Les chaînes conviennent aux données scalaires et JSON, tandis que les listes de blocs conviennent aux sorties multimodales.

Indiquez clairement lorsque quelque chose a échoué. Marquez une appelation échouée avec is_error: true ainsi qu’un message explicatif tel que "Erreur : aucun événement de calendrier ne correspond à cette recherche", et jamais une valeur vide ou simplement "Échec". Parmi toutes les habitudes liées aux résultats, c’est celle-ci qui est la plus importante. Une erreur claire permet à Claude de réessayer avec d’autres arguments, de choisir un outil différent ou d’informer l’utilisateur de ce qui s’est passé, tandis qu’une erreur cachée bloque la boucle. Un résultat volumineux peut également contenir cache_control, ce qui permet d’éviter des coûts de retraitement ultérieurs.

Le contrat de continuation

Trois règles strictes, chacune représentant un point de défaillance courant :

  • Un tool_result par tool_use. Omettez-en un et la demande échouera avec une erreur 400, car les modèles attendent une réponse un à un pour chaque appel d’outil qu’ils génèrent.
  • Réenvoyez le même tableau tools. Toute demande contenant des blocs tool_use ou tool_result doit définir les outils auxquels ils se réfèrent, sinon elle est rejetée.
  • Les résultats forment une étape à part entière. Fournissez les blocs tool_result comme message utilisateur, placés au début de son contenu. Les instructions supplémentaires dans cette étape ne sont pas autorisées. Toute nouvelle instruction doit être incluse dans un message utilisateur distinct une fois l’étape liée à l’outil terminée.

Chaque résultat est considéré comme une entrée non fiable

Un tool_result n’est pas de données neutres. Il s’agit de texte qui entre dans le contexte du modèle et influence la réponse suivante. Que ce texte provienne du web, d’un document fourni par l’utilisateur ou de tout système hors de votre contrôle total, il peut contenir des instructions injectées visant à prendre le contrôle de l’agent, comme l’ordre d’ignorer des instructions antérieures et d’envoyer la base de données. Le modèle pourrait obéir. C’est ce qu’on appelle l’injection de prompt, et dans un cycle d’agent, c’est un risque réel en production, et non hypothétique.

Les défenses sont structurelles et font partie du cycle que vous écrivez :

  • Accordez à l’agent les moindres privilèges nécessaires. Un outil qu’il ne possède pas est une action à laquelle on ne peut pas le tromper.
  • Exécutez les outils à risque dans un sandbox.
  • Sanitiser les sorties afin qu’elles se transforment en actions, par exemple en résolvant les chemins de fichiers par rapport à un répertoire fixe pour empêcher les traversées.
  • Permettre à l’étape post-utilisation de l’outil de supprimer tout élément qui ressemble à une instruction plutôt qu’à des données.
  • Placer tout ce qui a des conséquences réelles derrière une approbation humaine.

Un résultat d’outil ne doit jamais pouvoir autoriser ce qu’une appel d’outil lui-même ne peut pas faire.

Considérez les résultats comme un contexte que vous organisez également. Chaque token de résultat reste affiché pendant le reste de la conversation, il convient donc d’obtenir des sorties courtes et bien structurées : un JSON cohérent ou des champs étiquetés valent mieux que du texte que le modèle doit déchiffrer. Les boucles qui accumulent de nombreux résultats peuvent utiliser la fonction de suppression des résultats d’outil, une fonction bêta de gestion du contexte qui élimine les résultats obsolètes à mesure que la limite est approchée. Utilisez-la avant que la boucle ne devienne trop grande au point d’épuiser l’espace d’affichage.

7. Deuxième itération : une appel dépendant

Les deux résultats sont revenus, donc l’API est appelée une nouvelle fois en lui transmettant toute la liste de messages ainsi que les définitions des outils. La réponse du calendrier indique 15:00 à Canary Wharf, et la prévision météorologique prévoit de la pluie, mais elle ne peut toujours pas indiquer quand partir, car le temps de trajet dépend d’une localisation qu’elle vient à peine d’apprendre. C’est cette dépendance qui explique pourquoi l’appel ne pouvait pas être effectué en parallèle lors de la première itération. Il a dû être traité lors d’une étape ultérieure.

{
  "role": "assistant",
  "stop_reason": "tool_use",
  "content": [
    {
      "type": "tool_use",
      "id": "toolu_02Tt",
      "name": "get_travel_time",
      "input": {
        "origin": "home",
        "destination": "Canary Wharf, London"
      }
    }
  ]
}

Vous exécutez l’outil selon le même processus de vérification avant utilisation, d’exécution et de traitement après utilisation, et vous retournez un seul résultat :

{
  "role": "user",
  "content": [
    { "type": "tool_result", "tool_use_id": "toolu_02Tt", "content": "{\"minutes\": 45}" }
  ]
}

La structure de la boucle correspond à la première itération. Seul le nombre d’appels et leurs dépendances diffèrent. C’est le schéma à retenir : la boucle ne change pas de forme d’une itération à l’autre et se répète jusqu’à ce que Claude cesse de poser des questions.

8. Troisième itération : synthèse et end_turn

Le troisième appel à l’API montre que Claude dispose de tous les faits nécessaires. Aucun outil supplémentaire n’est demandé, donc il écrit la réponse. end_turn remplace la raison de cessation précédente, et la boucle s’arrête.

{
  "role": "assistant",
  "stop_reason": "end_turn",
  "content": [
    {
      "type": "text",
      "text": "Yes, bring an umbrella — rain is forecast in London tomorrow, around 12°C. Your meeting is at 3:00 PM in Canary Wharf, roughly 45 minutes away, so leave home by about 2:00 PM to arrive with a buffer."
    }
  ]
}

En tout, il s’agit de trois itérations : deux appels parallèles, un appel dépendant et une synthèse. Le modèle choisit ce qu’il faut récupérer et quand, tandis que votre code détermine comment chaque appel se déroule, s’il est autorisé et comment les résultats sont renvoyés. Le contexte s’agrandit à chaque tour car l’historique complet est réenvoyé, mais trois tours suffisent amplement.

9. Tout le cycle dans le code

Tout ce qui a été présenté jusqu’à présent se résume en un seul cycle dans le Client SDK. Celui-ci vérifie stop_reason, dirige chaque outil demandé via vos étapes pré- et post-utilisation, répond à chaque appel par un seul résultat, et continue jusqu’à ce que Claude ait terminé.

import anthropic
client = anthropic.Anthropic()

messages = [{"role": "user", "content": user_request}]
MAX_ITERATIONS = 10
for _ in range(MAX_ITERATIONS):
    response = client.messages.create(
        model="claude-opus-4-8", max_tokens=1024, tools=tools, messages=messages,
    )
    messages.append({"role": "assistant", "content": response.content})
    if response.stop_reason != "tool_use":
        break   # end_turn (or another reason): done
    results = []
    for block in (b for b in response.content if b.type == "tool_use"):
        if not pre_tool_use(block):   # your guard - may block the call
            results.append({"type": "tool_result", "tool_use_id": block.id,
                            "content": "Blocked by policy.", "is_error": True})
            continue
        try:
            output = execute(block.name, block.input)   # run it (concurrently if independent)
            output = post_tool_use(block, output)   # redact / log / reshape
            results.append({"type": "tool_result", "tool_use_id": block.id, "content": output})
        except Exception as e:
            results.append({"type": "tool_result", "tool_use_id": block.id,
                            "content": f"Error: {e}", "is_error": True})
    messages.append({"role": "user", "content": results})   # one result per tool_use
print(response.content[-1].text)

La boucle énonce certains points importants. Vous gérez vous-même les messages car l’API ne conserve aucune information d’état. Tout stop_reason autre que tool_use met fin au processus. Une limite stricte d’itérations empêche un agent de déraper, il ne faut donc jamais exécuter une boucle sans limite en environnement de production. Tous les types d’échecs, qu’il s’agisse d’appels bloqués ou d’exceptions, se traduisent par des résultats is_error au lieu de faire planter la séquence d’actions. Le code complet prend également en compte max_tokens (ce qui entraîne une séquence tronquée ; il faut alors augmenter la limite ou continuer) ainsi que pause_turn (une séquence entre le serveur et l’outil qui doit être renvoyée). L’un ou l’autre de ces cas peut survenir au sein de la même boucle.

Checklist à retenir

  • Les outils sont déclarés dans le tableau tools avec name, description et input_schema ; chaque requête paie des tokens d’entrée pour ces définitions.
  • tool_choice détermine si une outil est appelé : auto, any, tool ou none. Le mode de réflexion étendue ne fonctionne qu’avec auto et none, et strict: true oblige les arguments à correspondre exactement au schéma.
  • Un stop_reason de "tool_use" vous passe la parole. Repérez les appels en filtrant content pour type == "tool_use" et jamais en fonction de la position.
  • L’utilisation parallèle d’outils signifie plusieurs blocs tool_use dans une même réponse. L’ordre d’exécution, qu’il soit parallèle ou séquentiel, est à choisir par vous et non par le modèle. Les appels dépendants arrivent lors de tours ultérieurs.
  • Exécuter tous les outils demandés et répondre une seule fois : une seule action de l’utilisateur contenant un tool_result pour chaque tool_use, reliés par tool_use_id, sans modifier l’array tools. L’absence de résultat entraîne une réponse 400.
  • Vous êtes responsable des intervalles entre les exécutions. Validez et protégez avant d’exécuter, puis traitez et modifiez les résultats ensuite. Ce sont des étapes que vous écrivez, pas des fonctionnalités que vous configurez.
  • tool_result.content peut être une chaîne de caractères ou une liste de blocs. En cas d’échec, définissez is_error: true avec un message utile, et gardez les résultats courts et structurés.
  • Considérez les résultats des outils comme un contexte non fiable. Le principe du moindre privilège, les environnements isolés, la purification des données et l’approbation humaine pour les actions importantes constituent les solutions contre les injections de commandes.
  • La boucle se poursuit jusqu’à end_turn, conserve son propre état et nécessite une limite stricte au nombre d’itérations.