Module 3 — Appel de fonctions et description des outils
Le module précédent a laissé un trou : la boucle appelle des outils, mais rien n'a dit comment le modèle sait qu'ils existent, quels arguments ils prennent, ni quand les utiliser. Ce module remplit ce trou, parce qu'un agent qui rate ses outils n'est pas un agent — c'est un générateur d'excuses coûteuses.
Un outil est une interface, pas une fonction
Côté code, un outil est une fonction Python. Côté modèle, c'est une description JSON que le modèle lit pour décider s'il l'appelle. Ces deux moitiés doivent rester alignées, sinon l'agent invoque lire_url avec link= alors que la fonction attend url=, et l'erreur remonte dans la trace comme si l'outil était cassé.
Voici le schéma minimal des trois outils du fil rouge, au format attendu par l'API d'appel de fonctions :
SCHEMAS = [
{
"type": "function",
"function": {
"name": "chercher_web",
"description": (
"Interroge un moteur de recherche web et renvoie une liste "
"de 5 titres et URL. À utiliser en début d'enquête pour "
"trouver des sources externes, pas pour lire leur contenu."
),
"parameters": {
"type": "object",
"properties": {
"requete": {"type": "string",
"description": "Mots-clés en langue naturelle."},
},
"required": ["requete"],
},
},
},
{
"type": "function",
"function": {
"name": "lire_url",
"description": (
"Télécharge et nettoie le texte d'une page web. Renvoie au "
"maximum 8000 caractères. À utiliser après chercher_web."
),
"parameters": {
"type": "object",
"properties": {
"url": {"type": "string", "format": "uri"},
},
"required": ["url"],
},
},
},
]
Ce qui fait qu'un outil est bien décrit
La description n'est pas de la documentation, c'est de l'aide à la décision. Elle doit dire trois choses : ce que l'outil fait, quand l'appeler, et surtout quand ne pas l'appeler. Les descriptions comme « recherche des informations » sont vides ; le modèle les utilise à tort et à travers.
Comparons deux descriptions du même outil :
Version faible : "Recherche sur le web."
Version efficace : "Interroge un moteur de recherche web et renvoie 5 titres et URL. À utiliser en début d'enquête pour trouver des sources externes, pas pour lire leur contenu ni pour interroger notre base interne (utiliser chercher_base pour cela)."
La seconde ancre l'outil dans le paysage des autres outils. C'est ce qui fait chuter les confusions du type « chercher_web appelé alors qu'on interroge une donnée interne ».
Les noms comptent autant que les descriptions. chercher_base et chercher_web sont volontairement parallèles pour que la distinction saute aux yeux. search et look_up sont un cauchemar : le modèle les utilise de manière interchangeable.
Le nombre d'outils et la confusion
Un modèle qui voit trois outils bien décrits en choisit un correctement plus de 95 % du temps. À dix outils, les erreurs de sélection commencent à se voir. À vingt, elles deviennent gênantes, et à cinquante l'agent devient inutilisable sans hiérarchie.
Deux techniques repoussent ce plafond. La première consiste à regrouper les outils sémantiquement proches derrière un outil de dispatch — plutôt que dix outils de recherche par source, un unique chercher(source="jira" | "confluence" | "gdrive", ...). La seconde consiste à filtrer la palette en fonction de l'étape : au début un outil planifier seul, puis les outils d'exécution une fois le plan validé. Nous verrons au module 5 comment cette hiérarchie se met en place.
Valider les arguments avant d'exécuter
Le schéma JSON promet un contrat, il ne le tient pas seul. Le modèle envoie parfois une chaîne là où le schéma attend un entier, ou un chemin absolu là où seul un identifiant est acceptable. Sans validation côté agent, ces incohérences deviennent des exceptions Python noyées dans la trace.
from pydantic import BaseModel, HttpUrl, ValidationError
class ArgsLireUrl(BaseModel):
url: HttpUrl
def executer_outil(nom, args_bruts):
if nom == "lire_url":
try:
args = ArgsLireUrl(**args_bruts)
except ValidationError as e:
return {"erreur": f"arguments invalides : {e.errors()[0]['msg']}"}
return telecharger(str(args.url))
Le point important est de renvoyer un message d'erreur exploitable au modèle plutôt que de lever une exception. Le modèle, informé, réessaiera avec un URL valide. Une exception coupe la boucle sans possibilité de correction.
Sécurité et bac à sable
Certains outils sont dangereux par nature : exécuter du shell, écrire dans une base, envoyer un courriel. Ce module ne les traite pas — c'est l'objet du module 7. Retenez seulement le principe : le schéma ne suffit pas, il faut une vérification impérative dans la fonction elle-même. Le modèle décrira parfois supprimer_fichier(chemin="/etc/passwd") avec assurance ; c'est la fonction qui doit refuser, pas le prompt.
Les outils du fil rouge, au complet
Pour le reste du cours, l'agent dispose de cinq outils : chercher_web(requete), chercher_base(requete), lire_url(url), noter_fait(source, phrase) qui accumule des faits sourcés, et finish(reponse) qui produit la note finale. Cette palette est stable jusqu'au module 10. Deux outils suffisent au démarrage, cinq à la fin — un compromis entre couverture et confusion validé sur les évaluations du module 10.
En résumé
- La
descriptiond'un outil dit ce qu'il fait, quand l'appeler, quand ne pas l'appeler ; c'est de l'aide à la décision, pas de la documentation. - Les noms d'outils doivent être parallèles et distinctifs ;
chercher_webetchercher_basesont bons,searchetlook_upmauvais. - Le taux d'erreur de sélection monte avec le nombre d'outils ; regrouper et filtrer la palette repousse le plafond.
- Toujours valider les arguments et renvoyer une erreur exploitable au modèle plutôt que de lever une exception.
Module suivant : la mémoire — ce que la boucle garde, ce qu'elle jette et ce qu'elle sait retrouver plus tard.