Aller au contenu principal
Leçon 28 sur 66 · Claude sur Google Cloud (Vertex AI / Agent Platform)

Implémenter les tours multiples

Leçon 28 sur 66 Mis à jour le 5 novembre 2026 Développeur
Sur cette page
  1. Le signal : stop_reason
  2. La boucle
  3. Les autres valeurs de stop_reason
  4. Exécuter tous les outils demandés
  5. Les erreurs d’outil vont à Claude
  6. Router vers la bonne fonction
  7. L’essai
  8. La même boucle, écrite par le SDK
  9. À vous de jouer
  10. Points clés à retenir

Avec les utilitaires de la leçon précédente, on peut écrire la boucle qui fait vivre un assistant à outils : appeler Claude, exécuter les outils qu’il demande, lui renvoyer les résultats, et recommencer jusqu’à sa réponse finale.

Le signal : stop_reason

La réponse dit elle-même si Claude attend un outil : son champ stop_reason vaut alors "tool_use". Inutile de deviner en inspectant le texte. Tant que stop_reason vaut "tool_use", on exécute et on relance ; dès qu’il vaut autre chose, la boucle s’arrête.

La boucle

La boucle d'un assistant à outils Cinq étapes : appeler Claude avec les outils, ajouter sa réponse entière à l'historique, tester stop_reason, exécuter tous les outils demandés, renvoyer les résultats ; on recommence tant que stop_reason vaut tool_use, avec un plafond de tours. BOUCLE MULTI-TOURS La boucle d'un assistant à outils 1 Appeler chat avec les outils 2 Ajouter la réponse entière à l'historique 3 Tester stop_reason vaut-il tool_use ? 4 Exécuter un résultat par appel 5 Renvoyer les résultats, en message user tant que stop_reason vaut tool_use, dans la limite de max_turns Toute autre valeur arrête la boucle : end_turn, max_tokens, refusal ou pause_turn.
D'après la leçon « Implementing multiple turns » (Claude Academy, Claude on Google Cloud) et la page How tool use works (documentation Claude).
def run_conversation(messages, tools, max_turns=10):
    for _ in range(max_turns):
        response = chat(messages, tools=tools)
        add_assistant_message(messages, response)
        text = text_from_message(response)
        if text:
            print(text)
        if response.stop_reason != "tool_use":
            return response
        add_user_message(messages, run_tools(response))
    raise RuntimeError(f"Claude demande encore des outils après {max_turns} tours")

À chaque tour, on appelle Claude avec les outils, on ajoute sa réponse entière à l’historique et on affiche son texte éventuel. S’il ne demande pas d’outil, on rend la réponse finale. Sinon, on exécute les outils et on renvoie leurs résultats dans un message user.

Le plafond max_turns n’existe pas dans la version du cours officiel, qui boucle avec while True. Il vaut mieux l’écrire dès la première version : chaque tour est un appel facturé, et un outil qui renvoie toujours un résultat insatisfaisant peut faire tourner la boucle sans fin.

Les autres valeurs de stop_reason

La boucle rend la réponse dès que stop_reason n’est plus "tool_use". À l’appelant de regarder la valeur :

ValeurSignification
end_turnClaude a terminé : c’est la réponse finale.
max_tokensLa réponse a été coupée par le plafond de jetons. Augmentez max_tokens et relancez ; si la coupure tombe au milieu d’un appel d’outil, ne l’exécutez pas.
refusalClaude a refusé de poursuivre. N’exécutez aucun outil de ce tour.
pause_turnUn outil serveur, comme la recherche web, a mis le tour en pause : renvoyez la conversation telle quelle pour qu’il reprenne (leçon 33).

Exécuter tous les outils demandés

Une réponse peut contenir plusieurs blocs tool_use. On les traite tous, et chacun reçoit son tool_result :

import json

def run_tools(message):
    tool_results = []
    for block in message.content:
        if block.type != "tool_use":
            continue
        try:
            output = run_tool(block.name, block.input)
            tool_results.append({
                "type": "tool_result",
                "tool_use_id": block.id,
                "content": output if isinstance(output, str) else json.dumps(output, ensure_ascii=False),
            })
        except Exception as e:
            tool_results.append({
                "type": "tool_result",
                "tool_use_id": block.id,
                "content": f"Erreur : {e}",
                "is_error": True,
            })
    return tool_results

Oublier un bloc ne fait pas attendre Claude en silence : l’API refuse la requête suivante avec une erreur 400 (tool_use ids were found without tool_result blocks immediately after). C’est l’erreur la plus fréquente des premières boucles, qui ne traitent que le premier appel.

Les erreurs d’outil vont à Claude

Si un outil lève une exception, on ne l’avale pas : on renvoie un tool_result avec is_error: True et le message d’erreur. Claude peut alors corriger ses arguments et réessayer, choisir une autre approche ou expliquer le problème à l’utilisateur. C’est là que servent les messages d’erreur instructifs de la leçon 23.

Le bloc except Exception attrape tout, y compris vos propres bogues. En production, journalisez aussi l’exception côté serveur : Claude saura que l’outil a échoué, et vous saurez pourquoi.

Router vers la bonne fonction

TOOLS = {
    "get_current_datetime": get_current_datetime,
}

def run_tool(name, tool_input):
    if name not in TOOLS:
        raise ValueError(f"outil inconnu : {name}")
    return TOOLS[name](**tool_input)

Le cours officiel écrit ce routeur avec une suite de if et elif. Un dictionnaire fait la même chose : ajouter un outil revient à ajouter une ligne, et un nom inconnu produit une erreur claire, renvoyée à Claude comme les autres.

L’essai

messages = []
add_user_message(messages, "Quelle heure est-il, au format HH:MM:SS ?")
final = run_conversation(messages, tools=[get_current_datetime_schema])
print(final.stop_reason)   # "end_turn"

La même boucle, écrite par le SDK

Le SDK Python propose un exécuteur d’outils (tool runner, en bêta) qui fait tourner cette boucle à votre place : on décore les fonctions avec @beta_tool, et client.beta.messages.tool_runner(...) appelle Claude, exécute les outils et renvoie les résultats jusqu’à la réponse finale. La documentation le recommande pour la plupart des applications. Écrire la boucle une fois à la main reste le meilleur moyen de comprendre ce qu’il fait, et de savoir la reprendre quand il faut un contrôle plus fin.

À vous de jouer

Mettre la boucle à l’épreuve. Faites tourner run_conversation avec get_current_datetime, puis provoquez une erreur : faites lever une ValueError à la fonction quand le format contient %H. Observez comment Claude réagit au tool_result marqué is_error. Enfin, passez max_turns=1 et vérifiez que la boucle s’arrête avec l’erreur prévue.

Sources officielles : Implementing multiple turns (Claude Academy) · How tool use works (documentation Claude : la boucle et ses sorties) · Handle tool calls (is_error) · Tool runner (SDK)

Points clés à retenir

  • La boucle relance Claude tant que stop_reason vaut "tool_use", et s’arrête sur toute autre valeur.
  • Plafonnez le nombre de tours : chaque tour est un appel facturé.
  • Chaque bloc tool_use reçoit son tool_result ; un oubli provoque une erreur 400, pas une attente.
  • Une exception d’outil repart vers Claude avec is_error: True, et dans vos journaux.
  • Le tool runner du SDK fait tourner la même boucle pour vous.