Retour aux ressources
// technique · agents

Donner des outils à un agent sans qu’il hallucine

Un modèle seul invente une fiction crédible. Un modèle outillé va vérifier. La différence entre les deux tient dans quelques fonctions bien décrites — et quelques garde-fous pour qu'il s'en serve vraiment.

Technique10 juil. 2026~8 min · intermédiaire
LangGraphLangChainfunction callingZodguardrails
01

Pourquoi il hallucine

Un LLM ne ment pas : il complète. Quand tu lui demandes de résumer l'activité d'un dépôt qu'il ne peut pas lire, il produit la suite la plus plausible — une roadmap crédible, mais inventée. Le problème n'est pas le modèle, c'est qu'on lui a coupé l'accès au réel.

i

C'est le point de bascule que je raconte dans d'un prompt à l'agentique : donner des outils, c'est passer de « deviner » à « aller chercher ».

02

Un outil, c'est une fonction décrite

Un outil, c'est trois choses : un nom, une description (en langage naturel, c'est elle que le modèle lit), et un schéma d'entrée. Le modèle décide quand l'appeler à partir de la description — donc elle compte autant que le code.

tools.tscopier
import { tool } from "@langchain/core/tools";
import { z } from "zod";
// un outil = une fonction + un schema. Le modele l'appelle quand il en a besoin.
const getGitlabActivity = tool(
async ({ projectId }) => {
const commits = await gitlab.commits(projectId); // donnees REELLES
return JSON.stringify(commits.slice(0, 20));
},
{
name: "get_gitlab_activity",
description: "Lit les commits reels d'un projet GitLab. A utiliser avant de resumer une roadmap.",
schema: z.object({ projectId: z.string() }),
},
);
03

Le modèle décide, le graphe exécute

On attache les outils au modèle. À chaque tour, il peut soit répondre, soit demander un appel d'outil. Le nœud exécute l'appel, réinjecte le résultat, et redonne la main au modèle — c'est la boucle agentique.

node.tscopier
import { ChatOpenAI } from "@langchain/openai";
// on "donne" les outils au modele — il decide seul quand les appeler
const model = new ChatOpenAI({ model: "gpt-4o" }).bindTools([
getGitlabActivity,
listProjects,
readMemory,
]);
// dans le noeud : si le modele demande un outil, on l'execute et on reboucle
const res = await model.invoke(messages);
if (res.tool_calls?.length) {
// ... execute les outils, ajoute les resultats, rappelle le modele
}
// le graphe (langgraph)
gather
contexte
clarify
question ?
draft
décider
review
auto-relire
apply
appliquer
review ↺ draft — boucle si insuffisantclarify → human-in-the-loop
Postgres — état durableLangfuse — tracé
04

Les garde-fous

Donner des outils ne suffit pas : il faut cadrer leur usage, sinon l'agent boucle, appelle trop, ou ignore les outils et invente quand même.

01
Descriptions précises

Dis quand utiliser l'outil, pas seulement ce qu'il fait. « À appeler avant de résumer une roadmap » guide mieux que « lit GitLab ».

02
Limiter les tours

Un compteur max d'itérations évite qu'un agent tourne en rond sur des appels d'outils sans jamais conclure.

03
Valider les sorties

Un schéma Zod en sortie : si le modèle renvoie un champ mal typé, on le renvoie corriger au lieu de planter.

04
Tracer chaque appel

Sans traçage, un agent outillé est indéboguable. On voit quel outil a été appelé, avec quoi, et ce qu'il a renvoyé.

!

L'outil le plus dangereux est celui qui écrit. Les outils en lecture sont sûrs ; pour ceux qui modifient (créer, supprimer), ajoute une confirmation humaine — c'est tout l'objet de human-in-the-loop en vrai.

05

Concevoir le schéma comme un contrat

Le schéma d'entrée n'est pas de la paperasse : c'est le contrat qui empêche le modèle d'improviser. En mode strict, le fournisseur garantit que les arguments produits collent exactement au schéma — plus de champ inventé, plus de type approximatif. Côté Anthropic, on ajoute `strict: true` à la définition ; côté OpenAI, la même option exige `additionalProperties: false` et tous les champs en `required` (un champ optionnel se modélise en union avec `null`).

Avec Zod, tu écris le schéma une seule fois et LangChain le convertit en JSON Schema pour l'appel d'outil, puis valide et parse la réponse à la volée — pas de parsing manuel côté toi (doc structured output). Le même objet Zod sert donc à décrire l'entrée au modèle et à vérifier la sortie avant de la laisser continuer.

Nommer sans ambiguïté

Un paramètre `user` invite le modèle à deviner : un id ? un nom ? un email ? Un paramètre `user_id` ne laisse pas le choix. Anthropic résume ça par le « test du stagiaire » : si un nouvel arrivant ne peut pas utiliser l'outil avec la seule description et les seuls noms de champs, le modèle ne le pourra pas non plus (writing tools for agents).

enumUn `enum` (statut, catégorie) interdit au modèle un état invalide — mieux qu'une string libre qu'il faut re-valider.
strictLe mode strict transforme le schéma d'un vœu pieux en garantie : les arguments matchent, ou l'appel n'a pas lieu.
descriptionLa description dit *quand* appeler, pas juste ce que fait l'outil. C'est ce texte que le modèle lit pour décider.
i

Le mode strict garantit la *forme*, pas le *sens*. Un JSON Schema ne dit pas quelles combinaisons de champs font sens ni quelles conventions ton API attend — ça reste à la description et à la validation métier de le porter.

06

Rattraper les erreurs, mesurer les appels

Même bien décrit, un outil renvoie parfois une erreur, ou le modèle passe un argument que la validation métier refuse. La bonne réaction n'est pas de planter : c'est de renvoyer l'erreur *au modèle* comme un résultat d'outil, avec un message actionnable, pour qu'il corrige au tour suivant. Anthropic recommande des messages « spécifiques et actionnables » plutôt que des codes opaques (writing tools for agents).

retry.tscopier
const parsed = ToolInput.safeParse(rawArgs);
if (!parsed.success) {
// On ne plante pas : on renvoie l’erreur au modèle comme tool_result
return {
role: "tool",
tool_call_id: call.id,
content: `Validation failed. Fix these fields and retry:\n\${parsed.error.issues
.map((i) => `- \${i.path.join(".")}: \${i.message}`)
.join("\n")}`,
};
}
// parsed.data est typé et validé — on peut exécuter l’outil en confiance
const result = await runTool(parsed.data);
!

Une boucle de correction sans plafond, c'est un agent qui peut boucler à l'infini (et brûler des tokens) sur un argument qu'il ne saura jamais produire. Garde le compteur d'itérations de la section 04 aussi sur la boucle de retry.

Limiter la surface d'outils

Plus tu donnes d'outils, plus le modèle se trompe de cible et plus le contexte se remplit. Anthropic conseille de rester sous ~20 outils actifs et de consolider : un `schedule_event` vaut mieux que `list_users` + `list_events` + `create_event`. Au-delà, on passe à la découverte d'outils à la demande plutôt que de tout charger dans le contexte.

Évaluer, pas juste espérer

« Ça marche sur mon exemple » ne suffit pas. On évalue l'usage des outils à trois niveaux : la réponse finale, l'étape isolée (a-t-il choisi le bon outil ?), et la *trajectoire* entière (a-t-il suivi le bon chemin d'appels ?). Le package `agentevals` fournit des évaluateurs prêts à l'emploi : match strict, non ordonné, ou sous-ensemble sur les appels d'outils attendus.

Réponse finale : la sortie répond-elle vraiment à la demande, ancrée sur les données récupérées ?
Étape isolée : sur cette entrée, a-t-il appelé le bon outil avec les bons arguments ?
Trajectoire : la séquence d'appels correspond-elle au chemin attendu (ordonné ou non) ?

Ces évaluations tournent sur le traçage de la section 04 : sans trace de quel outil a été appelé avec quoi, on ne peut ni déboguer ni scorer. Le traçage est le socle, l'éval est ce qu'on construit dessus.

01

Sources & ressources

01Anthropic — Tool use with ClaudeLa référence : définir un outil, `input_schema`, mode strict, et comment le modèle décide d'appeler.02Anthropic — Writing effective tools for agentsLe « test du stagiaire », nommage sans ambiguïté, consolidation des outils et messages d'erreur actionnables.03OpenAI — Function calling guideMode strict, contraintes de schéma (`additionalProperties: false`), et rester sous ~20 fonctions.04OpenAI — Structured outputsGarantir qu'une sortie colle à un JSON Schema, avec le champ `refusal` pour les refus de sécurité.05LangChain — Structured outputBrancher un schéma Zod sur le modèle, validation et parsing automatiques, repli via tool calling.06LangChain — agentevalsÉvaluateurs prêts à l'emploi pour trajectoires d'agent : match strict, non ordonné, sous-ensemble.07LangSmith — Trajectory evaluationsÉvaluer le chemin complet d'un agent — outils choisis, raisonnement intermédiaire — avec ou sans trajectoire de référence.

Un agent qui hallucine est presque toujours un agent privé d'accès au réel. Des outils bien décrits, une boucle qui exécute, des garde-fous pour cadrer : c'est ce qui transforme une fiction crédible en réponse ancrée sur les faits.