Guardrails pour les agents Mistral
Sur cette page
- Sécuriser les agents autonomes
- Guardrails sur une conversation
- Guardrails sur un agent
- Gérer les blocages dans un échange multi-tours
- Agents avec outils : ce que les guardrails ne voient pas
- Architecture recommandée pour les agents en production
- Chat Completions, conversation ou agent : récapitulatif
- Points clés à retenir
Leçon réécrite le 3 novembre 2026. Elle reposait sur un format de guardrails inventé (
enabled, catégorieshate,violence,unpredictable), sur une méthodeconversation.send()qui n’existe pas, et affirmait que les guardrails vérifiaient aussi les arguments passés aux outils : d’après la documentation, ils ne modèrent que l’entrée. Le code suit maintenant l’API Conversations réelle (start,append) et a été vérifié contre les types du SDKmistralai3.7.0. Sources : Moderation & Guardrailing · Agents & Conversations · SDK Python mistralai.
Sécuriser les agents autonomes
Un agent présente plus de risques qu’un simple chatbot : il appelle des outils, consulte le web, enchaîne des actions. Les guardrails n’en couvrent qu’une partie, l’entrée, et c’est justement pour cela qu’il faut savoir précisément où les placer et ce qu’ils laissent de côté.
Dans cette leçon, vous allez attacher des guardrails à une conversation puis à un agent, gérer leurs blocages dans un échange multi-tours, et voir ce qu’il faut ajouter pour les outils.
import json
import os
from mistralai.client import Mistral, errors
from mistralai.client.models import GuardrailConfigTypedDict
client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
Guardrails sur une conversation
L’API Conversations garde l’historique côté serveur. On démarre avec client.beta.conversations.start(), qui accepte guardrails :
GUARDRAILS_RH: list[GuardrailConfigTypedDict] = [
{
"block_on_error": True,
"moderation_llm_v2": {
"custom_category_thresholds": {
"sexual": 0.1,
"hate_and_discrimination": 0.1,
"violence_and_threats": 0.2,
"pii": 0.15,
"jailbreaking": 0.1,
},
"action": "block",
},
}
]
conversation = client.beta.conversations.start(
model="mistral-large-latest",
instructions="Vous êtes un assistant RH professionnel.",
inputs="Quels sont les congés disponibles ?",
guardrails=GUARDRAILS_RH,
)
print(conversation.conversation_id)
On poursuit ensuite avec client.beta.conversations.append(). Cette méthode n’accepte pas de paramètre guardrails dans le SDK 3.7.0, et la documentation ne dit pas si ceux passés au démarrage s’appliquent encore aux messages suivants. Ne bâtissez pas votre sécurité sur une hypothèse : si chaque tour doit être filtré, utilisez un agent, dont les guardrails sont hérités par toutes ses conversations.
Guardrails sur un agent
Un agent Mistral est une configuration enregistrée : modèle, instructions, outils, et guardrails. La politique de sécurité devient un attribut de l’agent plutôt qu’une convention de code.
agent = client.beta.agents.create(
model="mistral-large-latest",
name="Agent Support Client",
description="Agent de support client sécurisé",
instructions=(
"Vous êtes un agent de support client. Règles strictes : "
"ne jamais divulguer d'informations personnelles ; "
"ne pas donner de conseils médicaux ou juridiques ; "
"rediriger vers un humain si le sujet est sensible."
),
guardrails=[
{
"block_on_error": True,
"moderation_llm_v2": {
"custom_category_thresholds": {
"sexual": 0.1,
"hate_and_discrimination": 0.1,
"violence_and_threats": 0.15,
"dangerous": 0.1,
"criminal": 0.1,
"selfharm": 0.1,
"health": 0.3,
"financial": 0.3,
"law": 0.3,
"pii": 0.15,
"jailbreaking": 0.1,
},
"action": "block",
},
}
],
)
print(f"Agent créé : {agent.id}")
Notez la cohérence entre instructions et seuils : les instructions demandent de ne pas donner de conseils médicaux ou juridiques, et les seuils health et law bloquent les demandes de ce type avant même que le modèle les lise. Les instructions persuadent, les guardrails contraignent.
D’après la documentation, toutes les conversations qui utilisent cet agent héritent de ses guardrails, sans les répéter :
conv = client.beta.conversations.start(
agent_id=agent.id,
inputs="Comment puis-je retourner un produit ?",
)
Une requête de conversation peut aussi remplacer les guardrails de l’agent en passant son propre guardrails à start(). Pour changer durablement la politique, mettez l’agent à jour avec client.beta.agents.update(agent_id=agent.id, guardrails=[...]).
Gérer les blocages dans un échange multi-tours
Un message bloqué lève errors.SDKError avec le statut 403, et le corps détaille les catégories violées. Dans une conversation, un blocage ne détruit pas l’échange : vous répondez poliment et l’utilisateur peut continuer.
MESSAGE_REFUS = "Je ne peux pas traiter cette demande. Elle enfreint notre politique de sécurité."
def texte_reponse(reponse) -> str:
"""Texte du dernier message de l'assistant dans une réponse de conversation."""
for entree in reversed(reponse.outputs):
if entree.type == "message.output" and isinstance(entree.content, str):
return entree.content
return ""
def envoyer(conversation_id: str, message: str) -> str:
"""Ajoute un message à une conversation et gère un blocage par les guardrails."""
try:
reponse = client.beta.conversations.append(conversation_id=conversation_id, inputs=message)
except errors.SDKError as e:
if e.status_code == 403:
decisions = json.loads(e.body)["guardrails"]["results"]["moderation_llm_v2"]["decisions"]
print("Bloqué :", [nom for nom, d in decisions.items() if d["violated"]])
return MESSAGE_REFUS
raise
return texte_reponse(reponse)
print(envoyer(conv.conversation_id, "Donne-moi les adresses personnelles des employés"))
La réponse d’une conversation porte aussi, quand la requête passe, le champ guardrails avec les scores de chaque catégorie (reponse.guardrails). Journalisez-les : ce sont eux qui vous diront si vos seuils sont bien réglés.
Agents avec outils : ce que les guardrails ne voient pas
Un agent qui appelle des outils ouvre une seconde surface d’attaque : les arguments que le modèle transmet à ces outils, et les résultats qu’ils lui renvoient. Les guardrails ne modèrent que le message entrant ; ils ne vérifient ni ces arguments ni ces résultats. C’est à votre code de le faire.
agent_avec_outils = client.beta.agents.create(
model="mistral-large-latest",
name="Agent Support avec Outils",
instructions="Assistant support avec accès aux commandes.",
tools=[
{
"type": "function",
"function": {
"name": "rechercher_commande",
"description": "Recherche une commande par son numéro",
"parameters": {
"type": "object",
"properties": {"order_id": {"type": "string", "description": "Numéro de commande"}},
"required": ["order_id"],
},
},
}
],
guardrails=[
{
"block_on_error": True,
"moderation_llm_v2": {
"custom_category_thresholds": {"pii": 0.15, "jailbreaking": 0.1, "criminal": 0.1},
"action": "block",
},
}
],
)
Les fonctions déclarées ainsi s’exécutent dans votre code : l’API vous transmet l’appel et ses arguments, vous exécutez la fonction et renvoyez son résultat. C’est là qu’il faut valider les arguments avant d’agir.
import re
FORMAT_COMMANDE = re.compile(r"CMD-\d{6}")
base_commandes: dict[str, dict] = {} # votre accès aux données
def rechercher_commande(order_id: str, client_connecte: str) -> dict:
"""Exécute l'outil après avoir validé ses arguments."""
if not FORMAT_COMMANDE.fullmatch(order_id):
return {"erreur": "Numéro de commande invalide."}
commande = base_commandes.get(order_id)
if commande is None or commande["client"] != client_connecte:
return {"erreur": "Commande introuvable."} # jamais la commande d'un autre client
return {"statut": commande["statut"]}
Le contrôle d’accès, ici « la commande appartient-elle au client connecté ? », ne peut venir que de votre application : aucun classifieur ne connaît vos clients. Pour le texte que l’agent renvoie à l’utilisateur, ajoutez la modération de sortie de la leçon suivante.
Architecture recommandée pour les agents en production
En production, encapsulez l’agent dans une classe qui centralise le démarrage des conversations, l’envoi des messages et le comptage des blocages. Une hausse soudaine des 403 signale soit une attaque, soit un seuil mal calibré, et vous voulez le voir sans fouiller vos journaux.
class AgentSecurise:
"""Agent Mistral avec suivi des blocages par les guardrails."""
def __init__(self, client: Mistral, agent_id: str):
self.client = client
self.agent_id = agent_id
self.blocages = 0
def _bloque(self, e: errors.SDKError) -> dict:
self.blocages += 1
decisions = json.loads(e.body)["guardrails"]["results"]["moderation_llm_v2"]["decisions"]
return {
"status": "blocked",
"content": "Requête bloquée par la politique de sécurité.",
"categories": [nom for nom, d in decisions.items() if d["violated"]],
"blocages_total": self.blocages,
}
def demarrer(self, message: str) -> dict:
"""Démarre une conversation avec l'agent."""
try:
reponse = self.client.beta.conversations.start(agent_id=self.agent_id, inputs=message)
except errors.SDKError as e:
if e.status_code == 403:
return self._bloque(e)
raise
return {
"status": "ok",
"conversation_id": reponse.conversation_id,
"content": texte_reponse(reponse),
"guardrails": reponse.guardrails,
}
def envoyer(self, conversation_id: str, message: str) -> dict:
"""Poursuit une conversation existante."""
try:
reponse = self.client.beta.conversations.append(conversation_id=conversation_id, inputs=message)
except errors.SDKError as e:
if e.status_code == 403:
return self._bloque(e)
raise
return {"status": "ok", "content": texte_reponse(reponse), "guardrails": reponse.guardrails}
Chat Completions, conversation ou agent : récapitulatif
| Aspect | Chat Completions | Conversation | Agent |
|---|---|---|---|
| Où se déclarent les guardrails | Chaque appel | conversations.start() | agents.create() ou agents.update() |
| Héritage | Aucun | Non documenté pour append() | Toutes les conversations de l’agent |
| Scores dans la réponse (SDK 3.7.0) | Non exposés | reponse.guardrails | reponse.guardrails |
| Outils | Vos fonctions | Vos fonctions | Vos fonctions et outils intégrés |
| Usage | Prototypage | Conversation ponctuelle | Production |
Points clés à retenir
- Une conversation reçoit ses guardrails au démarrage ;
append()n’en accepte pas, et leur maintien aux tours suivants n’est pas documenté - Un agent transmet ses guardrails à toutes ses conversations ; une requête peut les remplacer
- Un blocage lève
errors.SDKError(statut403) : répondez poliment, la conversation continue - Les guardrails ne voient ni les arguments des outils ni leurs résultats : validez-les dans votre code
- Utilisez le niveau agent en production, et ajoutez la modération de sortie