Tu veux les meilleurs outils IA avant les autres ?
On teste et on décrypte les nouveaux outils IA chaque soir, en 5 min. Gratuit.
Inclus dès l'inscription : notre sélection des meilleurs guides & comparatifs IA.
Choisis ton rythme
Gratuit · Pas de spam · Désabonnement en 1 clic
Introduction
Dans le monde des agents d'intelligence artificielle, les échecs sont souvent attribués à des erreurs de modèle, telles que le choix d'un mauvais outil ou une mauvaise gestion des erreurs. Cependant, ces problèmes sont souvent le reflet d'une conception d'outil inadéquate. En effet, un modèle ne peut fonctionner qu'avec les informations fournies par l'interface de l'outil, comme le nom, la description, et les paramètres. Lorsque ces éléments sont mal conçus, les échecs deviennent inévitables.
Les problèmes de conception, tels que des noms d'outils vagues, des instructions ambiguës ou des schémas incohérents, augmentent la probabilité d'échec. Même les modèles les plus avancés ne peuvent pas compenser une interface défectueuse. Cet article explore les pratiques de conception qui améliorent la fiabilité des outils, les modes d'échec qui échappent aux démonstrations mais échouent en conditions réelles, et comment une conception rigoureuse peut réduire les erreurs à la frontière de l'outil. Chaque modèle est associé à son homologue d'échec, car comprendre pourquoi un design échoue est aussi important que de savoir quoi le remplacer.
Ce qui fonctionne dans la conception d'outils d'agents IA
1. Un outil, une responsabilité
Dans les systèmes d'agents, chaque outil doit être dédié à une opération spécifique et claire. Lorsqu'un outil est conçu pour gérer plusieurs comportements via un paramètre d'action, le modèle doit d'abord déterminer quel mode utiliser avant de résoudre la tâche. Cela complique inutilement le processus.
Prenons l'exemple d'un outil multi-action qui gère plusieurs comportements :
@tool
def manage_customer(
action: str,
customer_id: str | None = None,
data: dict | None = None
):
"""
action: create | get | update | delete | suspend
"""
...
À l'inverse, des outils à responsabilité unique permettent une fonction sans ambiguïté et une meilleure gestion des erreurs :
@tool
def create_customer(data: CustomerInput) -> Customer:
"""Créer un nouvel enregistrement client."""
...
@tool
def get_customer(customer_id: str) -> Customer:
"""Récupérer un client par ID."""
...
@tool
def suspend_customer(customer_id: str, reason: str) -> SuspensionResult:
"""Suspendre un compte client."""
...
Les outils à responsabilité unique offrent une meilleure clarté et facilitent l'observabilité des erreurs. Cependant, certains domaines, tels que les outils de shell, de système de fichiers, de navigateur ou de calendrier, peuvent bénéficier d'une interface multi-action contrainte, car l'espace d'action fait partie de l'abstraction sous-jacente.
2. Schémas qui rendent les états invalides impossibles
Les agents qui appellent des outils construisent les arguments d'appel en se basant sur le schéma fourni. Un schéma lâche oblige le modèle à deviner les contraintes, tandis qu'un schéma strict encode ces contraintes, éliminant ainsi les suppositions.
Voici un exemple de schéma strict :
from pydantic import BaseModel, Field
from enum import Enum
class Priority(str, Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
class CreateTaskInput(BaseModel):
title: str = Field(
description="Titre de tâche court et actionnable. Utilisez la forme impérative : 'Revoir PR', pas 'PR Review'.",
min_length=5,
max_length=100
)
priority: Priority = Field(
description="Priorité de la tâche. Utilisez HIGH uniquement pour les bloqueurs affectant d'autres travaux.",
default=Priority.MEDIUM
)
due_date: str = Field(
description="Date d'échéance au format ISO 8601 : AAAA-MM-JJ. Doit être une date future.",
pattern=r"^\d{4}-\d{2}-\d{2}$"
)
Les énumérations sont particulièrement utiles pour les champs avec un ensemble limité de valeurs valides, car elles éliminent les résultats plausibles mais invalides. Les échecs de validation apparaissent à la frontière de l'outil plutôt que sous forme d'erreurs cryptiques en aval.
3. Descriptions qui définissent le champ d'application, pas seulement le but
Les descriptions d'outils servent de documentation orientée modèle. Elles doivent non seulement expliquer quand utiliser l'outil, mais aussi quand ne pas l'utiliser. La plupart des descriptions ne font que la première.
- Faible : explique ce qu'il fait, pas quand ne pas l'utiliser
"""Rechercher des documents dans la base de connaissances."""
- Forte : définit le but, le champ d'application et les limites
"""
Rechercher dans la base de connaissances interne des documents, politiques et matériels de référence.
Utilisez ceci lorsque l'utilisateur pose des questions sur les procédures de l'entreprise, les spécifications des produits ou les flux de travail documentés.
NE PAS utiliser ceci pour des données en temps réel (prix, disponibilité, statut actuel) — utilisez get_live_data() à la place.
Retourne jusqu'à 5 résultats classés par pertinence. Si aucun résultat n'est retourné, l'information n'est pas dans la base de connaissances.
"""
Sans cette clarification, le modèle infère le champ d'application uniquement à partir du nom de l'outil, ce qui est souvent une source d'erreurs de sélection à grande échelle. Une bonne définition d'outil inclut des limites claires par rapport à d'autres outils, pas seulement des instructions d'utilisation.
4. Retours d'erreurs structurés et actionnables
Lorsqu'un outil échoue, le modèle lit l'erreur et décide de la suite à donner. Une exception non gérée ou une trace de pile produit un comportement de suivi aléatoire. Une erreur structurée donne au modèle une base solide pour décider de la suite.
Les erreurs structurées doivent non seulement signaler ce qui a échoué, mais aussi aider l'agent à décider quoi faire ensuite. Un bon format d'erreur rend le comportement de réessai explicite et donne au modèle un chemin de récupération clair :
class ToolError(BaseModel):
error_code: str # lisible par machine, pour que le modèle puisse se ramifier
message: str # description lisible par l'homme
recoverable: bool # l'agent peut-il réessayer ?
suggested_action: str # que doit faire l'agent ensuite
- Enregistrement non trouvé : réessayable
return ToolError(
error_code="RECORD_NOT_FOUND",
message="Aucun enregistrement utilisateur trouvé avec l'ID 'usr_123'.",
recoverable=True,
suggested_action="Utilisez list_users() pour obtenir des ID utilisateur valides avant d'appeler get_user()."
)
- Quota dépassé : non réessayable
return ToolError(
error_code="QUOTA_EXCEEDED",
message="Le quota [API](/glossaire/api) pour cet outil a été atteint pour aujourd'hui.",
recoverable=False,
suggested_action="Informer l'utilisateur et arrêter. Ne pas réessayer cet outil aujourd'hui."
)
Le drapeau récupérable et le champ suggested_action sont ce qui change le comportement de l'agent. Sans eux, les modèles réessaient des erreurs non réessayables ou abandonnent celles qui le sont.
5. Opérations de changement d'état idempotentes
Chaque outil qui modifie l'état — crée un enregistrement, envoie un message, transfère des fonds — doit être sûr d'être appelé deux fois. En pratique, les agents réessaient, les réseaux échouent et la boucle LLM peut émettre un deuxième appel parce que la confirmation du premier n'est jamais arrivée.
Une manière simple de prévenir les effets secondaires dupliqués est d'exiger une clé d'idempotence pour chaque opération d'écriture :
@tool
def send_email(
to: str,
subject: str,
body: str,
idempotency_key: str
):
...