Aller au contenu principal
Leçon 4 sur 12 · Modération et Sécurité IA avec Mistral

Guardrails pour les agents Mistral

Leçon 4 sur 12 Mis à jour le 3 novembre 2026 Développeur
Sur cette page
  1. Sécuriser les agents autonomes
  2. Guardrails sur une conversation
  3. Guardrails sur un agent
  4. Gérer les blocages dans un échange multi-tours
  5. Agents avec outils : ce que les guardrails ne voient pas
  6. Architecture recommandée pour les agents en production
  7. Chat Completions, conversation ou agent : récapitulatif
  8. Points clés à retenir

Leçon réécrite le 3 novembre 2026. Elle reposait sur un format de guardrails inventé (enabled, catégories hate, violence, unpredictable), sur une méthode conversation.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 SDK mistralai 3.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

AspectChat CompletionsConversationAgent
Où se déclarent les guardrailsChaque appelconversations.start()agents.create() ou agents.update()
HéritageAucunNon documenté pour append()Toutes les conversations de l’agent
Scores dans la réponse (SDK 3.7.0)Non exposésreponse.guardrailsreponse.guardrails
OutilsVos fonctionsVos fonctionsVos fonctions et outils intégrés
UsagePrototypageConversation ponctuelleProduction

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 (statut 403) : 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