Patterns d'Utilisation des Outils : Interfaces Agent-Outil Fiables
Votre agent a appelé un outil et a reçu un blob JSON de 40 lignes — réponse API brute, objets imbriqués, codes d’erreur enfouis dans un champ status. Le modèle l’a lu, a choisi une valeur vraisemblable et a continué. La valeur était incorrecte. Trois étapes plus tard, l’agent a rédigé en toute confiance un rapport basé sur des données erronées.
L’outil a fonctionné. L’interface a échoué.
L’utilisation des outils est le mécanisme qui transforme un modèle de langage en agent. Chaque capacité de votre agent — recherche en base de données, écriture de fichiers, appels API, interrogation de services — arrive via une interface d’outil. Si l’interface est mal conçue, le modèle prend de meilleures décisions même quand le service sous-jacent fonctionne correctement. Ce guide couvre cinq patterns pour construire des interfaces d’outils précises, fiables et prêtes pour la production.
Prérequis : Familiarité avec Python et l’API Claude. Pour le contexte sur MCP comme couche de transport d’outils, voir Construire votre Premier Serveur MCP.
Pourquoi la Conception de l’Interface Importe
Quand un agent choisit et utilise un outil, il prend deux décisions :
- Quel outil appeler — piloté par le
nameet ladescriptionde l’outil - Quels arguments passer — piloté par le
input_schemade l’outil
Les descriptions ambiguës mènent à une mauvaise sélection d’outils. Les schémas trop souples permettent au modèle de passer des entrées malformées. Les résultats non structurés le forcent à deviner ce qui s’est passé. La plupart des bugs d’agents ne résident pas dans le raisonnement — ils résident à la frontière de l’outil.
Pattern 1 : Conception Schéma-en-Premier
Écrivez le schéma JSON avant d’écrire l’implémentation. Un schéma strict contraint le comportement du modèle au stade des entrées — avant que quoi que ce soit ne s’exécute.
import anthropic
client = anthropic.Anthropic()
tools = [ { "name": "search_products", "description": ( "Recherche dans le catalogue de produits par mot-clé. " "Retourne une liste de produits correspondants avec IDs, noms et prix. " "Utilisez-le quand l'utilisateur veut trouver ou parcourir des produits." ), "input_schema": { "type": "object", "properties": { "query": { "type": "string", "description": "Mots-clés à rechercher" }, "category": { "type": "string", "enum": ["electronics", "clothing", "food", "home", "all"], "description": "Catégorie de produit à filtrer. Utilisez 'all' si non spécifié." }, "max_results": { "type": "integer", "minimum": 1, "maximum": 20, "description": "Nombre de résultats à retourner. Défaut : 5" } }, "required": ["query", "category"] } }]
response = client.messages.create( model="claude-sonnet-4-6", max_tokens=1024, tools=tools, messages=[{"role": "user", "content": "Trouve-moi de l'électronique à moins de 100€"}])Règles de schéma qui réduisent les erreurs :
- Utilisez
enumpour tout champ avec un ensemble fixe de valeurs valides - Définissez
minimum/maximumsur les champs numériques - Marquez les champs
requireduniquement quand l’outil ne peut pas s’exécuter sans eux - Rédigez les descriptions du point de vue du modèle : “Utilisez-le quand…”
Quand utiliser : Pour chaque définition d’outil.
Pattern 2 : Résultats d’Outil Structurés
Retournez des résultats typés et lisibles par machine. Ne retournez jamais des réponses API brutes.
import jsonfrom dataclasses import dataclass, asdictfrom typing import Any, Optional
@dataclassclass ToolResult: success: bool data: Optional[Any] = None error: Optional[str] = None
def to_content(self) -> str: return json.dumps(asdict(self))
def search_products( query: str, category: str, max_results: int = 5,) -> ToolResult: try: raw_results = _query_database(query, category, limit=max_results) products = [ {"id": r["product_id"], "name": r["title"], "price": r["price_usd"]} for r in raw_results ] return ToolResult(success=True, data={"products": products, "count": len(products)}) except ConnectionError as e: return ToolResult(success=False, error=f"Base de données indisponible : {e}") except Exception as e: return ToolResult(success=False, error=f"Recherche échouée : {type(e).__name__}: {e}")
def _query_database(query, category, limit): return []L’enveloppe consistante {success, data, error} signifie que le modèle sait toujours où chercher. Pour la gestion gracieuse des pannes, voir Patterns de Récupération d’Erreurs.
Pattern 3 : Appels d’Outils en Parallèle
Claude peut demander plusieurs outils dans une seule réponse. Traitez-les en parallèle plutôt que séquentiellement.
from concurrent.futures import ThreadPoolExecutor, as_completed
TOOL_REGISTRY = { "search_products": search_products,}
def dispatch_tool(name: str, inputs: dict) -> ToolResult: handler = TOOL_REGISTRY.get(name) if not handler: return ToolResult(success=False, error=f"Outil inconnu : {name}") return handler(**inputs)
def process_tool_calls(response: anthropic.types.Message) -> list[dict]: tool_uses = [ block for block in response.content if block.type == "tool_use" ] if not tool_uses: return []
def execute(tool_use): result = dispatch_tool(tool_use.name, tool_use.input) return { "type": "tool_result", "tool_use_id": tool_use.id, "content": result.to_content(), }
with ThreadPoolExecutor(max_workers=len(tool_uses)) as executor: futures = {executor.submit(execute, tu): tu for tu in tool_uses} results = [] for future in as_completed(futures): results.append(future.result())
return results
def run_agent(user_message: str) -> str: messages = [{"role": "user", "content": user_message}]
while True: response = client.messages.create( model="claude-sonnet-4-6", max_tokens=4096, tools=tools, messages=messages, )
if response.stop_reason == "end_turn": for block in response.content: if hasattr(block, "text"): return block.text return ""
if response.stop_reason == "tool_use": tool_results = process_tool_calls(response) messages.append({"role": "assistant", "content": response.content}) messages.append({"role": "user", "content": tool_results}) else: break
return ""Pour orchestrer plusieurs agents appelant chacun des outils, voir Patterns Multi-Agent.
Pattern 4 : Enveloppe d’Appel d’Outil Sécurisée
Ne laissez jamais les exceptions d’outils atteindre la boucle d’agent sans être gérées.
import signal
def timeout_handler(signum, frame): raise TimeoutError("Délai d'exécution de l'outil dépassé")
def safe_tool_call( name: str, inputs: dict, timeout_seconds: int = 30,) -> ToolResult: """ Exécute un outil avec délai, capturant toutes les exceptions. Retourne toujours un ToolResult — ne lève jamais d'exception. """ signal.signal(signal.SIGALRM, timeout_handler) signal.alarm(timeout_seconds) try: return dispatch_tool(name, inputs) except TimeoutError: return ToolResult( success=False, error=f"L'outil '{name}' a dépassé le délai de {timeout_seconds}s" ) except Exception as e: return ToolResult( success=False, error=f"L'outil '{name}' a levé {type(e).__name__}: {e}" ) finally: signal.alarm(0)Quand un outil retourne success: false, le modèle peut décider de réessayer, d’essayer une alternative ou de signaler l’échec. Pour des stratégies de retry plus larges, voir Patterns de Récupération d’Erreurs.
Pattern 5 : Validation et Troncature des Résultats
Validez les résultats d’outils avant de les retourner au modèle.
MAX_TOOL_RESULT_CHARS = 8000
def validate_result(result: ToolResult, expected_keys: list[str]) -> ToolResult: if not result.success or not isinstance(result.data, dict): return result missing = [k for k in expected_keys if k not in result.data] if missing: return ToolResult( success=False, error=f"Champs manquants dans la réponse : {missing}" ) return result
def truncate_result(result: ToolResult) -> ToolResult: content = result.to_content() if len(content) <= MAX_TOOL_RESULT_CHARS: return result
truncated_data = { "truncated": True, "chars_omitted": len(content) - MAX_TOOL_RESULT_CHARS, "content": content[:MAX_TOOL_RESULT_CHARS], } return ToolResult( success=result.success, data=truncated_data, error="Résultat tronqué — trop grand pour la fenêtre de contexte", )
def safe_tool_call_validated( name: str, inputs: dict, expected_keys: list[str] | None = None,) -> ToolResult: result = safe_tool_call(name, inputs) if expected_keys: result = validate_result(result, expected_keys) result = truncate_result(result) return resultPour observer et déboguer ces échecs en production, voir Débogage et Observabilité.
Erreurs Courantes
Erreur 1 : Retourner des Réponses API Brutes
Le modèle reçoit un objet imbriqué avec 30 champs, la plupart non pertinents. Il choisit le mauvais.
Solution : Formatez la réponse avant de la retourner. Ne retournez que ce dont le modèle a besoin pour sa prochaine décision.
Erreur 2 : Outils avec Effets de Bord sans Confirmation
Un outil qui envoie un email ou supprime un enregistrement ne doit pas s’exécuter silencieusement.
Solution : Pour les actions irréversibles, utilisez un pattern à deux outils : plan_email retourne un aperçu, send_email envoie réellement.
Erreur 3 : Responsabilités d’Outils qui se Chevauchent
Deux outils qui font des choses similaires forcent le modèle à deviner lequel utiliser.
Solution : Chaque outil doit avoir un but distinct et non chevauchant.
Erreur 4 : Pas de Délai sur les Outils Externes
Un appel API tiers lent bloque indéfiniment toute la boucle d’agent.
Solution : Définissez toujours un délai (Pattern 4).
Liste de Contrôle pour la Production
- Chaque outil a une description du point de vue du modèle (“Utilisez-le quand…”)
- Champs enum pour toutes les entrées à valeurs fixes
- Chaque outil retourne
{success, data, error}— jamais des réponses brutes - Appels d’outils enveloppés dans
safe_tool_call - Exécution parallèle pour les réponses multi-outils
- Délai défini sur chaque outil externe
- Troncature des résultats pour les charges de taille variable
- Validation pour les outils appelant des APIs externes
Prochaines Étapes
- Commencez par le schéma — écrivez votre
input_schemaavant le corps de la fonction - Ajoutez l’enveloppe
ToolResultà chaque outil existant - Intégrez
safe_tool_callpour renforcer votre boucle d’agent - Configurez la validation pour les outils qui appellent des APIs externes
Guides associés :
- Construire votre Premier Serveur MCP
- Patterns Multi-Agent
- Systèmes de Mémoire pour Agents
- Patterns de Récupération d’Erreurs
- Débogage et Observabilité