Conversations multi-tours avec outils
Sur cette page
Le cycle de la leçon 27 suppose que Claude demande toujours un outil. Ce n’est pas le cas : à « Combien font 1+1 ? », il répond directement, et ajouter des résultats d’outils inexistants produirait un message vide que l’API refuse. Il faut donc savoir, à chaque réponse, si Claude attend un outil ou s’il a fini.
La raison d’arrêt
Chaque réponse de Converse porte un champ stopReason qui dit pourquoi la génération s’est arrêtée :
stopReason | Signification |
|---|---|
tool_use | Claude attend le résultat d’un ou de plusieurs outils |
end_turn | Claude a fini sa réponse |
max_tokens | La réponse a atteint la limite de tokens |
stop_sequence | Une séquence d’arrêt a été rencontrée |
L’API Converse en définit d’autres, plus rares, comme guardrail_intervened, content_filtered ou model_context_window_exceeded. Seul tool_use relance la boucle.
chat renvoie tout ce qu’il faut
def chat(messages, system=None, tools=None, tool_choice=None):
params = {"modelId": model_id, "messages": messages}
if system:
params["system"] = [{"text": system}]
if tools:
params["toolConfig"] = {"tools": tools}
if tool_choice:
params["toolConfig"]["toolChoice"] = {"tool": {"name": tool_choice}}
response = client.converse(**params)
parts = response["output"]["message"]["content"]
return {
"parts": parts,
"stop_reason": response["stopReason"],
"text": "\n".join(part["text"] for part in parts if "text" in part),
}
Un dictionnaire plutôt qu’un couple : les blocs bruts pour l’historique, la raison d’arrêt pour la boucle, le texte pour l’affichage. Le texte réunit tous les blocs de texte, sans jamais supposer que le premier bloc en est un.
La boucle de conversation
def run_conversation(messages, tools, max_turns=10):
for _ in range(max_turns):
result = chat(messages, tools=tools)
add_assistant_message(messages, result["parts"])
if result["text"]:
print(result["text"])
if result["stop_reason"] != "tool_use":
return messages
add_user_message(messages, run_tools(result["parts"]))
raise RuntimeError(f"Pas de réponse finale après {max_turns} tours")
À chaque tour, la fonction appelle Claude, garde sa réponse intacte dans l’historique et affiche son texte éventuel. Si Claude n’attend pas d’outil, la conversation est finie. Sinon, elle exécute les outils, ajoute les résultats et recommence. Le cours officiel écrit une boucle while True ; la limite max_turns protège d’une boucle sans fin, par exemple si un outil renvoie toujours une erreur que Claude retente.
tools = [get_current_datetime_schema]
messages = []
add_user_message(messages, "Quelle heure est-il ?")
run_conversation(messages, tools)
messages = []
add_user_message(messages, "Combien font 1+1 ?")
run_conversation(messages, tools)
Pour l’heure, Claude appelle l’outil puis répond ; pour le calcul, il répond directement, en un seul tour. La même boucle sert toutes les combinaisons d’outils et de réponses directes : c’est elle qui accueillera les deux outils suivants.
Si la réponse est coupée
Avec max_tokens, la réponse est incomplète : la boucle s’arrête sur un texte coupé. En production, traitez ce cas à part, par exemple en le signalant ou en relançant avec une limite plus haute. Sur Haiku 5.5, la réflexion compte dans les tokens de sortie : laissez de la marge si vous fixez maxTokens.
À vous de jouer
Compter les tours. Ajoutez un compteur à run_conversation et affichez, à la fin, le nombre d’appels à Claude et la liste des outils demandés. Comparez « Quelle heure est-il ? », « Combien font 1+1 ? » et « Dans combien de minutes sera-t-il midi ? ».
Sources officielles : Multi-turn conversations with tools (Claude Academy) · API Converse (documentation AWS : valeurs de
stopReason)
Points clés à retenir
stopReasonàtool_use: Claude attend un résultat ; toute autre valeur termine la boucle.chatrenvoie les blocs, la raison d’arrêt et le texte réuni de tous les blocs de texte.- La boucle garde chaque réponse intacte, exécute les outils, renvoie les résultats et recommence.
- Une limite de tours protège d’une boucle sans fin ; une réponse
max_tokensse traite à part.