Claude Platform Docs
MessagesOutils

Exécuteur d'outils (SDK)

Utilisez l'exécuteur d'outils du SDK pour gérer automatiquement la boucle agentique, l'encapsulation des erreurs et la sûreté de typage.

Le « tool runner » (exécuteur d'outils) gère la boucle agentique, l'encapsulation des erreurs et la sûreté de typage pour que vous n'ayez pas à le faire. Lorsque vous avez besoin d'une approbation humaine dans la boucle, d'une journalisation personnalisée ou d'une exécution conditionnelle, utilisez plutôt la boucle manuelle.

Au lieu de gérer manuellement les appels d'outils, les résultats d'outils et la gestion de la conversation, l'exécuteur d'outils effectue automatiquement les opérations suivantes :

  • Exécute les outils lorsque Claude les appelle
  • Gère le cycle requête/réponse
  • Gère l'état de la conversation
  • Fournit la sûreté de typage et la validation

Utilisation de base

Définissez les outils à l'aide des helpers du SDK, puis utilisez l'exécuteur d'outils pour les exécuter.

Selon la signature d'outil du SDK, un outil renvoie son résultat sous forme de chaîne de caractères ou de blocs de contenu (blocs de texte, d'image ou de document), de sorte qu'un outil peut renvoyer des résultats multimodaux. Une chaîne renvoyée devient un unique bloc de contenu texte. Pour renvoyer des données structurées, telles qu'un objet JSON ou un nombre, encodez-les d'abord sous forme de chaîne.

Utilisez le décorateur @beta_tool pour définir des outils avec des annotations de type et des docstrings.

import json
from anthropic import Anthropic, beta_tool

client = Anthropic()


@beta_tool
def get_weather(location: str, unit: str = "fahrenheit") -> str:
    """Get the current weather in a given location.

    Args:
        location: The city and state, e.g. San Francisco, CA
        unit: Temperature unit, either 'celsius' or 'fahrenheit'
    """
    return json.dumps({"temperature": "20°C", "condition": "Sunny"})


@beta_tool
def calculate_sum(a: int, b: int) -> str:
    """Add two numbers together.

    Args:
        a: First number
        b: Second number
    """
    return str(a + b)


runner = client.beta.messages.tool_runner(
    model="claude-opus-5",
    max_tokens=1024,
    tools=[get_weather, calculate_sum],
    messages=[
        {
            "role": "user",
            "content": "What's the weather like in Paris? Also, what's 15 + 27?",
        }
    ],
)
for message in runner:
    print(message)

Le décorateur @beta_tool inspecte les arguments de la fonction et la docstring pour en déduire le schéma JSON à votre place.

Itérer sur l'exécuteur d'outils

L'exécuteur d'outils est un itérable qui produit les messages de Claude. À chaque itération, l'exécuteur vérifie si Claude a demandé une utilisation d'outils. Si c'est le cas, il exécute l'outil et renvoie automatiquement le résultat à Claude, puis produit le message suivant de Claude pour poursuivre votre boucle.

Vous pouvez terminer la boucle à n'importe quelle itération avec une instruction break. L'exécuteur boucle jusqu'à ce que Claude renvoie un message sans utilisation d'outils, ou jusqu'à atteindre max_iterations si vous l'avez défini.

Si vous n'avez pas besoin des messages intermédiaires, vous pouvez obtenir directement le message final :

Utilisez runner.until_done() pour obtenir le message final.

client = anthropic.Anthropic()

runner = client.beta.messages.tool_runner(
    model="claude-opus-5",
    max_tokens=1024,
    tools=[get_weather, calculate_sum],
    messages=[
        {
            "role": "user",
            "content": "What's the weather like in Paris? Also, what's 15 + 27?",
        }
    ],
)
final_message = runner.until_done()
for block in final_message.content:
    if block.type == "text":
        print(block.text)

Utilisation avancée

Dans la boucle, vous pouvez lire chaque message de réponse et modifier l'état de l'exécuteur avant le prochain appel API. Chaque itération suit ce cycle de vie :

  1. L'exécuteur envoie une requête à l'API Messages avec son état actuel.
  2. L'exécuteur produit le message de réponse pour le corps de votre boucle.
  3. Le corps de votre boucle s'exécute. Vous pouvez lire le message et éventuellement modifier l'état de l'exécuteur.
  4. Lorsque le corps de votre boucle se termine, l'exécuteur vérifie si vous avez modifié son historique de messages.
    • Si vous n'avez pas modifié l'historique de messages : si le message contient des appels d'outils, l'exécuteur ajoute le message de l'assistant et les résultats d'outils, puis continue. S'il n'y a pas d'appels d'outils, la boucle se termine.
    • Si vous avez modifié l'historique de messages : l'exécuteur ignore son ajout automatique et utilise votre état tel quel. Consultez Prendre le contrôle de l'historique de messages.

Prendre le contrôle de l'historique de messages

Par défaut, l'exécuteur gère l'état de la conversation pour vous : après chaque tour comportant un appel d'outil, il ajoute le message de l'assistant et les éventuels résultats d'outils à son propre historique de messages. Vous prenez le contrôle de l'historique de messages lorsque vous souhaitez réessayer un tour (rejeter la réponse et renvoyer la requête), injecter un message de suivi ou construire vous-même le résultat d'outil.

Vous prenez le contrôle en modifiant les messages de l'exécuteur depuis le corps de la boucle. La méthode exacte dépend du SDK. Consultez les onglets par langage ci-dessous.

Lorsque vous prenez le contrôle pour une itération, l'exécuteur n'ajoute pas le message de l'assistant ni les résultats d'outils de ce tour. Vous devenez responsable du maintien de la validité de la conversation : ajoutez vous-même le message de l'assistant et un résultat d'outil (si vous voulez que le tour compte), modifiez l'état de manière conditionnelle afin que la boucle puisse toujours se terminer lorsqu'il n'y a pas d'appels d'outils, et passez max_iterations pour borner la boucle. Les sept SDK prennent tous en charge max_iterations.

Utilisez generate_tool_call_response() pour inspecter ou calculer le résultat d'outil. Appeler append_messages() dans la boucle indique à l'exécuteur que vous gérez vous-même l'historique ; incluez donc le message de l'assistant et le résultat d'outil dans ce que vous ajoutez.

runner = client.beta.messages.tool_runner(
    model="claude-opus-5",
    max_tokens=1024,
    max_iterations=10,
    tools=[get_weather],
    messages=[{"role": "user", "content": "What's the weather in San Francisco?"}],
)

for message in runner:
    tool_response = runner.generate_tool_call_response()
    if tool_response is not None:
        # append_messages() marque l'état comme modifié, donc le runner ignore son
        # ajout automatique pour cette itération. Ajoutez vous-même le message de
        # l'assistant et le résultat d'outil, plus tout suivi éventuel.
        runner.append_messages(
            message,
            tool_response,
            {"role": "user", "content": "Please be concise."},
        )
    # Sans appel d'outil, laissez l'état intact pour que la boucle se termine.

Pour modifier des paramètres de requête tels que max_tokens sans prendre le contrôle de l'historique de messages, utilisez set_messages_params(). L'exécuteur ajoute toujours automatiquement le message de l'assistant et le résultat d'outil.

for message in runner:
    runner.set_messages_params(lambda params: {**params, "max_tokens": 2048})

Gestion automatique du contexte

Pour les tâches agentiques de longue durée, les exécuteurs d'outils TypeScript et Ruby prennent en charge la compaction automatique, qui génère des résumés lorsque l'utilisation de jetons dépasse un seuil afin que la conversation puisse se poursuivre au-delà des limites de la « context window » (fenêtre de contexte). Les deux SDK ont déprécié cette option côté client au profit de la compaction côté serveur, qui fonctionne avec l'exécuteur d'outils de chaque SDK via le paramètre de requête context_management. Le SDK Python (v1.0 et ultérieures) ainsi que les exécuteurs d'outils Go, Java, C# et PHP n'incluent pas la compaction côté client.

Déboguer l'exécution des outils

Lorsqu'un outil lève une exception, l'exécuteur d'outils l'intercepte et renvoie l'erreur à Claude sous forme de résultat d'outil avec is_error: true. Le résultat d'outil contient le message de l'exception (en Python, son type et son message), et non la trace de pile complète.

Ce que le SDK journalise dépend du langage. Le SDK Python journalise l'exception complète, y compris sa trace de pile, via le module standard logging chaque fois qu'un outil lève une exception non gérée. Les SDK Python, TypeScript et Java lisent la variable d'environnement ANTHROPIC_LOG pour activer la journalisation du SDK, qui inclut le détail des requêtes et des réponses :

# Journaliser au niveau info
export ANTHROPIC_LOG=info

# Journaliser au niveau debug pour une sortie plus détaillée
export ANTHROPIC_LOG=debug

Les SDK Go, Ruby, C# et PHP ne lisent pas ANTHROPIC_LOG. En dehors de Python, aucun SDK ne journalise un outil en échec : pour savoir pourquoi un outil a échoué, interceptez et journalisez l'exception dans la fonction de l'outil avant de la renvoyer ou de la relancer.

Intercepter les erreurs d'outils

Par défaut, les erreurs d'outils sont renvoyées à Claude, qui peut alors répondre de manière appropriée. Cependant, vous pourriez vouloir détecter les erreurs et les traiter différemment, par exemple pour arrêter l'exécution prématurément ou implémenter une gestion d'erreurs personnalisée.

Dans les SDK Python et TypeScript, utilisez la méthode de réponse d'outil (generate_tool_call_response() en Python, generateToolResponse() en TypeScript) pour intercepter les résultats d'outils et vérifier la présence d'erreurs avant qu'ils ne soient envoyés à Claude. Les autres SDK n'exposent pas ce hook. Leurs onglets décrivent l'alternative la plus proche :

client = anthropic.Anthropic()

runner = client.beta.messages.tool_runner(
    model="claude-opus-5",
    max_tokens=1024,
    tools=[my_tool],
    messages=[{"role": "user", "content": "Run my_tool with the query 'hello'."}],
)

for message in runner:
    tool_response = runner.generate_tool_call_response()

    if tool_response is not None:
        # tool_response est un dict : {"role": "user", "content": [...]}
        # Vérifier si un résultat d'outil contient une erreur
        for block in tool_response["content"]:
            if block.get("is_error"):
                # Option 1 : lever une exception pour arrêter la boucle
                raise RuntimeError(f"Tool failed: {json.dumps(block['content'])}")

                # Option 2 : journaliser et continuer (laisser Claude gérer)
                # logger.error(f"Tool error: {json.dumps(block['content'])}")

    # Traiter le message normalement
    print(message.content)

Modifier les résultats d'outils

Vous pouvez modifier les résultats d'outils avant qu'ils ne soient renvoyés à Claude. Cela est utile pour ajouter des métadonnées telles que cache_control afin d'activer le « prompt caching » (mise en cache des prompts) sur les résultats d'outils, ou pour transformer la sortie de l'outil. Consultez la page sur la mise en cache des prompts.

Dans les SDK Python et TypeScript, utilisez la méthode de réponse d'outil pour obtenir le résultat d'outil, puis modifiez-le avant que l'exécuteur ne poursuive. Selon le SDK, vous ajoutez explicitement le résultat modifié ou vous le mutez sur place. Consultez les commentaires de code dans chaque onglet.

client = anthropic.Anthropic()

runner = client.beta.messages.tool_runner(
    model="claude-opus-5",
    max_tokens=1024,
    tools=[search_documents],
    messages=[
        {
            "role": "user",
            "content": "Search for information about the climate of San Francisco",
        }
    ],
)

for message in runner:
    tool_response = runner.generate_tool_call_response()

    if tool_response is not None:
        # tool_response est un dict : {"role": "user", "content": [...]}
        # Modifier le résultat d'outil pour ajouter le contrôle de cache
        for block in tool_response["content"]:
            if block["type"] == "tool_result":
                # Ajouter cache_control pour mettre en cache ce résultat d'outil
                block["cache_control"] = {"type": "ephemeral"}

        # Ajouter la réponse modifiée (cela empêche l'ajout automatique de l'originale)
        runner.append_messages(message, tool_response)

    print(message.content)

Streaming

Activez le streaming pour traiter la réponse de chaque tour de manière incrémentale. Chaque itération produit un objet de flux sur lequel vous pouvez itérer pour obtenir les événements.

Définissez stream=True et utilisez get_final_message() pour obtenir le message accumulé.

client = anthropic.Anthropic()

runner = client.beta.messages.tool_runner(
    model="claude-opus-5",
    max_tokens=1024,
    tools=[calculate_sum],
    messages=[{"role": "user", "content": "What is 15 + 27?"}],
    stream=True,
)

# En streaming, le runner renvoie BetaMessageStream
for message_stream in runner:
    for event in message_stream:
        print("event:", event)
    print("message:", message_stream.get_final_message())

print(runner.until_done())

Étapes suivantes

Imposez la conformité au JSON Schema sur les entrées d'outils de Claude grâce à l'échantillonnage contraint par grammaire.

Analysez les blocs tool_use, formatez les réponses tool_result et gérez les erreurs avec is_error.

Activez, formatez et désactivez les appels d'outils parallèles, avec des conseils sur l'historique de messages et le dépannage.

Spécifiez les schémas d'outils, rédigez des descriptions efficaces et contrôlez quand Claude appelle vos outils.

Was this page helpful?