Création d’agents IA sécurisés à l’aide de LangChain Guardrails et de middleware
Découvrez comment les mécanismes déterministes et basés sur des modèles fonctionnent dans LangChain pour détecter les fuites de PII, appliquer des règles commerciales et ajouter des étapes d’approbation humaine aux agents IA.
Qu’est-ce que les garde-fous ?
Un garde-fou est un mécanisme qui surveille ce qu’un système d’IA fait et l’empêche de prendre des actions que vous ne souhaitez pas qu’il entreprenne.
Imaginons un agent équipé des outils suivants :
search()
sendEmail()
deleteUser()
makePayment()
Le modèle pourrait décider qu’appeler deleteUser() est la bonne chose à faire.
Mais est-ce vraiment quelque chose que vous souhaitez autoriser ?
Un garde-fou se situe entre la décision de l’agent et l’exécution réelle de cette action :
User
↓
Agent
↓
Guardrail
↓
Is this allowed?
├── Yes → Execute
└── No → Block / Ask for approval
Les garde-fous sont couramment utilisés pour :
- Empêcher la fuite d’informations personnelles identifiables
- Détecter et empêcher les tentatives d’injection de prompts
- Filtrer le contenu nocif ou inapproprié
- Appliquer la logique métier et les contraintes réglementaires
- Vérifier que les sorties respectent les normes de qualité et de correction
Dans LangChain, les garde-fous sont principalement mis en place grâce au middleware, ce qui permet d’intégrer des interventions aux points spécifiques du flux d’exécution de l’agent.
Pourquoi les agents IA ont-ils besoin de garde-fous ?
Le logiciel traditionnel suit une logique écrite explicitement par un développeur.
Par exemple :
if (!user.isAdmin) {
throw new Error("Unauthorized");
}
Les LLM ne fonctionnent pas de cette manière.
Vous fournissez des instructions et des outils, mais c’est le modèle lui-même qui décide de l’action à entreprendre.
Prenons un agent de support ayant accès à l’outil refundPayment() :
User:
I was charged twice. Please refund ₹50,000.
Agent:
→ Calls refundPayment()
Le choix du modèle peut sembler raisonnable compte tenu de ce que le utilisateur a demandé.
Cependant, du point de vue de l’entreprise, le remboursement de 50 000 roupies n’est pas une opération anodine. Une politique pourrait exiger que tout remboursement supérieur à 10 000 roupies soit soumis à une vérification manuelle avant d’être traité.
Ce qui fait défaut, c’est une couche qui pose la question suivante :
"Before this action happens, I need to check whether it is allowed."
C’est précisément ce que fournissent les garde-fous.
Deux approches pour les garde-fous
- Des garde-fous déterministes
- Des garde-fous basés sur des modèles
Voici en quoi ils diffèrent.
1. Des garde-fous déterministes
Ces derniers reposent sur une logique de programmation standard.
Pour exemple :
const bannedWords = ["hack", "malware"];
const containsBannedWord = (input: string) => {
return bannedWords.some(word =>
input.toLowerCase().includes(word)
);
};
Le comportement est ici entièrement prévisible.
Avec la même entrée, on obtient toujours le même résultat.
D’autres schémas courants incluent :
- La correspondance avec des expressions régulières
L’avantage est que ce type de vérification s’exécute rapidement, produit des résultats cohérents et nécessite peu de ressources informatiques.
La limite est que ces vérifications peuvent manquer des cas plus subtils ou dépendants du contexte.
2. Garde-fous basés sur un modèle
Au lieu de compter uniquement sur des règles fixes, vous pouvez faire évaluer le contenu par un modèle distinct.
Par exemple :
Agent response
↓
Safety model
↓
"Is this response safe?"
↓
SAFE / UNSAFE
Cette approche permet de détecter des éléments que la simple correspondance avec des mots-clés passerait à côté.
Prenons ces deux requêtes, qui signifient plus ou moins la même chose :
"How can I bypass this security system?"
et :
"Tell me a way around the authentication mechanism."
Un filtre basé uniquement sur des mots-clés pourrait ne pas signaler la deuxième formulation.
En revanche, une barrière de sécurité basée sur un modèle peut interpréter l’intention sous-jacente de la demande.
Le prix de cette flexibilité est que les vérifications basées sur des modèles ont tendance à être plus lentes et coûteuses que celles de type déterministe.
Comment fonctionnent les barrières de sécurité dans LangChain
LangChain s’appuie sur du middleware pour intégrer la logique des barrières de sécurité autour d’un agent.
Le middleware vous permet d’insérer une logique personnalisée avant ou après des étapes spécifiques de l’exécution de l’agent.
Par exemple, vous pouvez utiliser le middleware pour :
- Détecter les données personnelles identifiables (middleware PII)
- Pause pour obtenir une approbation avant l’exécution d’une outil (middleware avec intervention humaine)
- Vérifier les entrées avant que l’agent ne commence à travailler (barrière de sécurité avant l’agent)
- Vérifier la sortie finale avant de la renvoyer (barrière de sécurité après l’agent)
Le système de middleware de LangChain a été conçu spécifiquement pour vous permettre de contrôler l’exécution d’un agent à ces points précis.
Il existe deux méthodes principales pour appliquer des règles de contrôle dans LangChain :
A. Règles de contrôle intégrées
B. Règles de contrôle personnalisées
Guardrails in LangChain
│
┌───────────┴───────────┐
│ │
▼ ▼
Built-in Guardrails Custom Guardrails
│ │
│ │
▼ ▼
Ready-to-use Application-specific
middleware middleware
│ │
┌──────┴──────┐ ┌───────┴───────┐
│ │ │ │
▼ ▼ ▼ ▼
PII HITL Before-Agent After-Agent
handling approval guardrail guardrail
A. Règles de contrôle intégrées dans LangChain
LangChain est livré avec un certain nombre de règles de contrôle prêtes à l’emploi, sans aucune configuration supplémentaire.
Deux d’entre elles particulièrement notables, documentées par la bibliothèque, sont :
- Détection des PII
- Intervention humaine
Examinons-les toutes les deux.
1. Détection des PII
LangChain inclut un middleware spécialement conçu pour détecter et gérer les informations personnelles identifiables (PII) qui apparaissent dans une conversation.
Cela peut couvrir des éléments tels que :
Email address
Credit card number
IP address
MAC address
Lorsqu’un agent est exposé à des données sensibles, on ne souhaite généralement pas que ces données soient transmises au modèle ou restituées dans la réponse.
LangChain résout ce problème grâce à piiRedactionMiddleware().
Voici un exemple :
import {
createAgent,
piiRedactionMiddleware,
} from "langchain";
const agent = createAgent({
model: "gpt-5.5",
tools: [customerServiceTool],
middleware: [
piiRedactionMiddleware({
piiType: "email",
strategy: "redact",
applyToInput: true,
applyToOutput: true,
}),
],
});
const result = await agent.invoke({
messages: [{
role: "user",
content: "My email is john.doe@example.com"
}]
});
Supposons qu’un utilisateur soumette :
My email is john.doe@example.com
Le middleware intercepte et réécrit ce contenu avant que le modèle ne puisse y accéder :
My email is [REDACTED_EMAIL]
En d’autres termes, le modèle travaille avec des **placeholders nettoyés plutôt que avec les données sensibles brutes**.
Remarque : La mise à jour de
applyToOutput: truegarantit que, même si le modèle génère des données PII dans sa réponse, le middleware les supprime avant que la réponse n’atteigne l’utilisateur. Si vos outils risquent de divulguer des données PII dans leurs résultats,applyToToolResults: trueétend cette même protection aux sorties des outils.
Stratégies de gestion des données PII
LangChain prend en charge quatre méthodes distinctes pour gérer les données PII détectées :
Pour voir la différence, appliquons chacune de ces stratégies au même exemple d’entrée.
Supposons que l’utilisateur saisisse :
My email is john.doe@example.com
1. redact
Avec redact, toutes les données PII détectées sont entièrement remplacées par un placeholder générique.
piiRedactionMiddleware({
piiType: "email",
strategy: "redact",
applyToInput: true,
});
C’est ce que le modèle reçoit réellement :
My email is [REDACTED_EMAIL]
Cette stratégie convient aux situations où le modèle n’a pas vraiment besoin de connaître la valeur sous-jacente.
2. mask
La stratégie mask masque une partie de la valeur tout en laissant suffisamment d’informations visibles pour le contexte.
piiRedactionMiddleware({
piiType: "email",
strategy: "mask",
applyToInput: true,
});
L’e-mail pourrait ressembler à ceci :
My email is j***@example.com
C’est pratique lorsque le modèle ou l’utilisateur final a besoin d’une référence partielle aux données sans les exposer complètement.
3. hash
hash remplace les PII par une valeur de hash cohérente et déterministe.
piiRedactionMiddleware({
piiType: "email",
strategy: "hash",
applyToInput: true,
});
L’e-mail transformé pourrait ressembler à ceci :
My email is 8f14e45fceea167a5a36dedd4bea2543...
Puisque le hachage est déterministe, des entrées identiques produisent toujours des hashes identiques. Cela permet de suivre ou de comparer des occurrences répétées de la même valeur sans jamais révéler les données originales.
4. block
Contrairement aux trois autres, block ne transforme absolument pas les données personnelles sensibles — il rejette simplement la demande immédiatement dès que ce type de données est détecté.
Par exemple, vous pouvez configurer un détecteur personnalisé pour repérer les clés API :
piiRedactionMiddleware({
piiType: "api_key",
detector: /sk-[a-zA-Z0-9]{32}/,
strategy: "block",
applyToInput: true,
});
Si un utilisateur envoie ensuite :
My API key is sk-abcdefghijklmnopqrstuvwxyz123456
le middleware reconnaît le schéma de la clé API et bloque la demande avant que sa valeur ne puisse être transmise plus loin.
Cette stratégie convient aux cas où certaines catégories de données sensibles — clés API, identifiants et secrets similaires — doivent ne jamais être autorisées à entrer dans le pipeline de l’agent dès le départ.
2. Intervention humaine
Certaines opérations présentent un risque trop élevé pour être entièrement confiées à un agent autonome.
Pensez par exemple aux actions telles que :
delete production database
send an external email
make a financial transaction
modify production data
Au lieu d’interdire complètement ces actions, vous pouvez les diriger vers une étape d’approbation par un humain.
Le flux résultant est le suivant :
Agent
↓
Tool call
↓
Guardrail
↓
Human approval
↓
┌───────────────┐
│ │
Approved Rejected
│ │
↓ ↓
Execute Stop
LangChain propose humanInTheLoopMiddleware() pour mettre en œuvre ce schéma.
Par exemple :
import { createAgent, humanInTheLoopMiddleware } from "langchain";
import { MemorySaver, Command } from "@langchain/langgraph";
const agent = createAgent({
model: "gpt-5.5",
tools: [
searchTool,
sendEmailTool,
deleteDatabaseTool,
],
middleware: [
humanInTheLoopMiddleware({
interruptOn: {
send_email: {
allowAccept: true,
allowEdit: true,
allowRespond: true,
},
delete_database: {
allowAccept: true,
allowEdit: true,
allowRespond: true,
},
search: false,
},
}),
],
// A checkpointer is required so the paused run can be resumed later
checkpointer: new MemorySaver(),
});
Avec cela en place, l’agent s’arrête avant d’exécuter send_email ou delete_database et attend une décision humaine. Pour reprendre l’exécution par la suite, l’appel nécessite à la fois un ID de thread et une Commande:
const config = { configurable: { thread_id: "some_id" } };
// First call pauses and waits for approval
await agent.invoke(
{ messages: [{ role: "user", content: "Send an email to the team" }] },
config
);
// Resume after a human approves the tool call
await agent.invoke(
new Command({ resume: { decisions: [{ type: "approve" }] } }),
config
);
Important : sans un
checkpointeret unthread_id, il n’y a rien à partir de quoi le middleware peut reprendre, et le mécanisme d’arrêt suivi d’approbation ne fonctionnera tout simplement pas. C’est de loin la plus fréquente erreur de configuration.
Ici, la configuration stipule effectivement :
search → automatically allowed
send_email → require human approval
delete_database → require human approval
Cette approche s’avère particulièrement utile pour les agents exécutés dans des environnements de production.
Si vous souhaitez en savoir plus sur le modèle « human-in-the-loop », un guide pratique distinct explique en détail comment le graphe est mis en pause via interrupt(), attend une décision humaine, puis reprend son exécution grâce à Command.
B. Contraintes personnalisées
Le middleware LangChain fourni par défaut ne conviendra pas à tous les scénarios auxquels votre application est confrontée.
Lorsque vos besoins dépassent ce qui est intégré, LangChain vous permet d’écrire votre propre middleware et d’implémenter des comportements de contraintes personnalisés.
Deux points d’ancrage du cycle de vie sont particulièrement utiles à cette fin :
beforeAgent
afterAgent
Ces points d’ancrage vous permettent d’insérer de la logique de contrainte à des moments précis pendant l’exécution d’un agent.
1. Contrôles avant l’exécution de l’agent
beforeAgent s’exécute au tout début de l’appel à un agent. Vous pouvez l’utiliser pour créer un contrôle avant l’exécution qui inspecte ou filtre une requête entrante avant même que l’agent ne commence à la traiter.
Les cas d’usage typiques incluent :
- L’authentification
- La limitation du débit
- Le filtrage des entrées
- Le rejet des requêtes inappropriées
- Les vérifications liées à une session
Voici un exemple :
import { createMiddleware, AIMessage } from "langchain";
const sensitiveDataFilterMiddleware = (sensitiveKeywords: string[]) => {
const keywords = sensitiveKeywords.map((kw) => kw.toLowerCase());
return createMiddleware({
name: "SensitiveDataFilterMiddleware",
beforeAgent: {
hook: (state) => {
// Check if messages exist
if (!state.messages || state.messages.length === 0) {
return;
}
// Get the first user message
const firstMessage = state.messages[0];
// Make sure the message is from the user
if (firstMessage._getType() !== "human") {
return;
}
const content = firstMessage.content.toString().toLowerCase();
// Check for sensitive keywords
for (const keyword of keywords) {
if (content.includes(keyword)) {
// Stop the agent before it starts processing
return {
messages: [
new AIMessage(
"I cannot process requests containing sensitive information. " +
"Please remove passwords, API keys, or secrets and try again."
),
],
jumpTo: "end",
};
}
}
// No sensitive content found
return;
},
canJumpTo: ["end"],
},
});
};
// Create the agent
import { createAgent } from "langchain";
const agent = createAgent({
model: "gpt-5.5",
tools: [searchTool, calculatorTool],
middleware: [
sensitiveDataFilterMiddleware([
"password",
"api_key",
"secret",
"private_key",
]),
],
});
// This request will be blocked
const result = await agent.invoke({
messages: [
{
role: "user",
content: "Show me how to store my production password securely.",
},
],
});
console.log(result);
Flux :
User Request
│
▼
┌──────────────────────┐
│ beforeAgent Hook │
│ │
│ Check user message │
│ for sensitive words │
└──────────┬───────────┘
│
▼
┌─────────────────┐
│ Sensitive │
│ keyword found? │
└───────┬─────────┘
│
┌────────┴────────┐
│ │
YES NO
│ │
▼ ▼
┌──────────────────┐ ┌──────────────┐
│ Return blocked │ │ Continue to │
│ AIMessage │ │ the agent │
└────────┬─────────┘ └───────┬──────┘
│ │
▼ ▼
jumpTo: "end" Agent executes
│ │
▼ ▼
Final Response Final Response
Avec ce crochet activé, une requête problématique est bloquée avant que l’agent n’ait la possibilité d’exécuter ou d’appeler des outils.
2. Contrôles après l’exécution de l’agent
afterAgent s’exécute une fois que l’agent a terminé son travail. Il vous permet de mettre en place un contrôle après l’exécution qui vérifie ou filtre la réponse finale de l’agent avant qu’elle n’atteigne l’utilisateur.
Les applications courantes incluent :
- Vérifications de sécurité
- Vérification de la qualité du résultat
- Vérifications de conformité
- Filtrage du résultat
- Évaluation effectuée par un autre modèle
Par exemple, vous pouvez faire passer la réponse par un second modèle dont la seule fonction est de l’évaluer :
import {
createMiddleware,
AIMessage,
initChatModel,
} from "langchain";
const toxicityGuardrailMiddleware = () => {
// Model used only for toxicity evaluation
const evaluatorModel = initChatModel("gpt-5.4-mini");
return createMiddleware({
name: "ToxicityGuardrailMiddleware",
afterAgent: {
hook: async (state) => {
// Get the final AI response
if (!state.messages || state.messages.length === 0) {
return;
}
const lastMessage =
state.messages[state.messages.length - 1];
if (lastMessage._getType() !== "ai") {
return;
}
const response = lastMessage.content.toString();
// Ask the evaluator model to check for toxicity
const evaluationPrompt = `
You are a toxicity detection system.
Analyze the following AI response and determine
whether it contains toxic, abusive, hateful, or
harassing language.
Respond with ONLY:
SAFE
or
TOXIC
AI response:
${response}
`;
const evaluation = await evaluatorModel.invoke([ { role: "user", content: evaluationPrompt, }, ]);
const result = evaluation.content
.toString()
.trim()
.toUpperCase();
// Replace the response if it is toxic
if (result === "TOXIC") {
return {
messages: [
new AIMessage(
"I'm unable to provide that response because it contains inappropriate language."
),
],
jumpTo: "end",
};
}
return;
},
canJumpTo: ["end"],
},
});
};
// Create the agent
import { createAgent } from "langchain";
const agent = createAgent({
model: "gpt-5.5",
tools: [
searchTool,
calculatorTool,
],
middleware: [
toxicityGuardrailMiddleware(),
],
});
// Invoke the agent
const result = await agent.invoke({
messages: [
{
role: "user",
content: "Give me a response to this angry customer.",
},
],
});
console.log(result);
User
↓
Main Agent (GPT-5.5)
↓
Generates response
↓
afterAgent hook
↓
Evaluator Model (GPT-5.4-mini)
↓
┌───────────────┐
│ Is it toxic? │
└───────┬───────┘
│
┌───┴───┐
↓ ↓
SAFE TOXIC
↓ ↓
Return Replace
response response
Le point essentiel à retenir est que vous ne devez jamais faire confiance automatiquement au résultat fourni par l’agent. Au lieu de cela, vous envoyez la réponse générée à un modèle d’évaluation dont le rôle est de la vérifier selon vos critères de sécurité avant de la restituer à l’utilisateur.
Combinaison de plusieurs mécanismes de protection
Dans la pratique, un seul mécanisme de protection est rarement suffisant pour une application réelle.
Les différentes phases d’exécution de l’agent exigent des types de mesures de sécurité variés. Pensez à cette séquence :
1. Input filtering
2. PII protection
3. Tool approval
4. Output safety check
LangChain vous permet d’associer plusieurs composants de middleware à un seul agent en même temps.
Voici un exemple :
const agent = createAgent({
model: "gpt-5.5",
tools: [
searchTool,
sendEmailTool,
],
middleware: [
// 1. Before-agent guardrail for input filtering
sensitiveDataFilterMiddleware([
"password",
"api_key",
"secret",
"private_key",
]),
// 2. Built-in PII protection middleware
piiRedactionMiddleware({
piiType: "email",
strategy: "redact",
applyToInput: true,
applyToOutput: true,
}),
// 3. Built-in Human approval middleware for sensitive tools
humanInTheLoopMiddleware({
interruptOn: {
send_email: {
allowAccept: true,
allowEdit: true,
allowRespond: true,
},
},
}),
// 4. After-agent guardrail
toxicityGuardrailMiddleware(),
],
});
Chaque couche de middleware est chargée de protéger une partie spécifique du parcours d’exécution de l’agent.
Ensemble, le flux global se présente comme ceci :
User Request
│
▼
┌──────────────────────┐
│ Before-agent │
│ guardrail │
│ │
│ Input filtering │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ PII protection │
│ │
│ Redact sensitive │
│ information │
└──────────┬───────────┘
│
▼
AI Agent
│
▼
Tool call?
/ \
No Yes
│ │
│ ▼
│ ┌───────────────┐
│ │ Human approval│
│ └───────┬───────┘
│ │
│ ┌────┴────┐
│ │ │
│ Approved Rejected
│ │ │
│ ▼ ▼
│ Execute Stop
│ │
└───────┤
▼
Agent response
│
▼
┌──────────────────────┐
│ After-agent │
│ guardrail │
│ │
│ Toxicity check │
│ using evaluator model│
└──────────┬───────────┘
│
┌────┴────┐
│ │
SAFE TOXIC
│ │
▼ ▼
Return Replace
response response
Cela crée une stratégie de défense en couches pour l’agent.
L’idée principale est que chaque mécanisme de protection est responsable d’un point de contrôle différent :
- Le mécanisme de protection avant l’agent valide la requête reçue.
- Le middleware PII protège les données sensibles.
- La revue par un humain bloque les appels à des outils risqués jusqu’à ce qu’une personne les approuve.
- Le mécanisme de protection après l’agent vérifie la réponse finale avant qu’elle n’atteigne l’utilisateur.
Au lieu de compter sur un seul mécanisme de sécurité, on empile plusieurs couches afin que l’agent soit protégé plus efficacement à chaque étape.
Les garde-fous ne concernent pas seulement la sécurité
Lorsque les gens entendent le terme « garde-fous », ils imaginent généralement un système pour bloquer du contenu nocif ou offensant.
Cependant, dans les systèmes en production, les garde-fous servent également à appliquer la logique métier et les politiques spécifiques à l’application.
Par exemple :
Customer support agent
Can:
✓ Search orders
✓ Check delivery status
Cannot:
✗ Refund more than ₹10,000
✗ Delete customer account
✗ Change payment details
Ces règles n’ont rien à voir avec la détection de contenu nocif.
Elles représentent des politiques d’application qui définissent les limites de ce que l’agent est autorisé à faire.
Puisque un LLM prend des décisions de manière dynamique en temps de exécution, il est nécessaire d’avoir un point d’application fiable pour les règles qui doivent être respectées quel que soit le choix du modèle.
Remarque : c’est le LLM qui décide de ce qu’il veut faire ; les règles de contrôle déterminent ce que l’application lui permet réellement de faire.
Pensées finales
Créer un agent IA implique bien plus que de simplement relier un LLM à quelques outils.
Lorsque cet agent commence à gérer des demandes réelles des utilisateurs et à interagir avec des systèmes réels, il est nécessaire d’établir des limites claires pour son comportement. C’est précisément là que jouent un rôle les règles de contrôle.
LangChain propose plusieurs éléments de base à cette fin :
- Règles de contrôle déterministes pour les règles qui doivent être prévisibles
- Règles de contrôle basées sur des modèles pour les vérifications dépendant du sens et du contexte
- Moyen de transmission pour les données personnelles sensibles afin de gérer ces informations
- Moyen de transmission avec intervention humaine pour les actions ayant des conséquences réelles
- Moyen de transmission avant l’agent pour les vérifications au niveau des entrées et des sessions
- Moyen de transmission après l’agent pour valider la sortie finale
La leçon la plus importante à retenir de tout cela est : ne comptez pas uniquement sur les LLM pour faire respecter les règles de votre application.
Laissez le modèle gérer le raisonnement et la prise de décision, mais conservez les limites vraiment importantes codées dans le code et les middleware, afin de pouvoir les examiner et les vérifier directement.
C’est ce qui transforme un agent IA en quelque chose de suffisamment fiable pour un environnement de production réel.
Références
- Le guide officiel de LangChain traitant des concepts de garde-fous, disponible à https://docs.langchain.com/oss/javascript/langchain/guardrails
- La référence officielle de LangChain décrivant le fonctionnement général des middleware, disponible à https://docs.langchain.com/oss/javascript/langchain/middleware/overview
Lectures complémentaires
- Construire un clone local de Angry Birds avec Qwen3.8-27B et Pi — Apprenez à mettre en place un flux de travail de codage IA entièrement local en utilisant LM Studio et l’agent Pi pour créer un niveau jouable d’Angry Birds avec Qwen3.8-27B.
- Garde-fous structuraux pour les agents IA : À l’intérieur du pipeline ResolveFlow — Explique comment un agent basé sur LangGraph assure la séparation entre le raisonnement et l’exécution grâce à des vérifications au niveau du code plutôt qu’à des instructions dans les prompts, y compris une erreur de récupération qui est apparue au cours du processus.