Skip to main content
Lesson 3 of 12 · AI Moderation and Security with Mistral

Configure guardrails in Python

Lesson 3 of 12 Updated November 3, 2026 Developer
On this page
  1. From theory to code
  2. The guardrails parameter in detail
  3. Complete example: chatbot with guardrails
  4. Handle blocking errors cleanly
  5. Configuration best practices
  6. Key takeaways

Lesson rewritten on November 3, 2026. The previous version configured guardrails using a made-up dictionary (enabled), nonexistent categories (hate, violence, unpredictable), claimed unlisted categories kept “default thresholds,” and tested e.status_code == 3201, even though 3201 is a response body error code, not an HTTP status. Configuration now follows the official format, and the code has been verified against the mistralai SDK 3.7.0 types. Sources: Moderation & Guardrailing · mistralai Python SDK.

From theory to code

You now understand how guardrails work. In this lesson, you will configure them precisely: select your categories, define your thresholds, and handle blocks in your Python code.

Start with the Mistral SDK installed (guardrails require version 2 or later) and an API key loaded from the environment rather than hardcoded.

pip install "mistralai>=2"
import os

from mistralai.client import Mistral

client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])

The guardrails parameter in detail

The guardrails parameter is a list. Each item pairs the block_on_error option with a moderation_llm_v2 configuration, which itself contains thresholds, the ignore_other_categories option, and the action:

from mistralai.client.models import GuardrailConfigTypedDict

GUARDRAILS: list[GuardrailConfigTypedDict] = [
    {
        # Block if the evaluation itself fails
        "block_on_error": True,
        "moderation_llm_v2": {
            # Thresholds per category, from 0 to 1 (1 disables the category)
            "custom_category_thresholds": {
                "sexual": 0.2,
                "hate_and_discrimination": 0.1,
                "violence_and_threats": 0.3,
                "dangerous": 0.2,
                "criminal": 0.1,
                "selfharm": 0.1,
                "health": 0.5,
                "financial": 0.5,
                "law": 0.5,
                "pii": 0.2,
                "jailbreaking": 0.1,
            },
            "ignore_other_categories": False,
            # Action on threshold breach
            "action": "block",
        },
    }
]

The default moderation model is mistral-moderation-2603; the optional model_name key lets you specify a different one.

The list[GuardrailConfigTypedDict] annotation is not required, but it is worth using: the SDK describes every allowed key, so a type checker like pyright or mypy will flag a misspelled category (violence instead of violence_and_threats) that the API itself would not catch.

custom_category_thresholds

This is the heart of the configuration. Each category gets a threshold between 0 and 1, compared against the score the classifier assigns to the prompt. The lower the threshold, the more the filter blocks, at the risk of rejecting legitimate requests. A threshold of 1 explicitly disables the category. Mistral does not publish a recommended value grid: you must calibrate these on your own examples (lesson 7).

Values only make sense in the context of your application. A public-facing chatbot locks down sensitive categories at the lowest settings, while an internal tool for healthcare professionals must allow medical questions while remaining strict on personal data.

# Public chatbot: very low thresholds
public_thresholds = {
    "sexual": 0.1,
    "hate_and_discrimination": 0.1,
    "violence_and_threats": 0.1,
    "criminal": 0.1,
    "selfharm": 0.1,
}

# Internal medical tool: health disabled, PII still monitored
medical_thresholds = {
    "sexual": 0.2,
    "hate_and_discrimination": 0.1,
    "violence_and_threats": 0.3,
    "health": 1,   # disables category: medical questions are the tool's core function
    "pii": 0.1,
}

Categories you do not list

What happens to categories missing from custom_category_thresholds? The documentation does not state it explicitly, but its blocking example is telling: unlisted categories appear there with a threshold of 1.0, noted but ineffective. Do not rely on a default threshold: list every category that should be able to block.

The ignore_other_categories option controls only what is evaluated and returned. When set to False, all evaluated categories appear in the result; when True, only the ones you listed are evaluated.

TARGETED_GUARDRAILS: list[GuardrailConfigTypedDict] = [
    {
        "block_on_error": True,
        "moderation_llm_v2": {
            "custom_category_thresholds": {
                "hate_and_discrimination": 0.1,
                "violence_and_threats": 0.2,
            },
            "ignore_other_categories": True,  # evaluates only these two categories
            "action": "block",
        },
    }
]

Warning: with this configuration, a jailbreak attempt or a request for personal data would pass unchecked. Restrict the list only for a specific reason.

Complete example: chatbot with guardrails

Here is a minimal function: it sends the message with the full configuration and turns a block into a readable response for the user.

from mistralai.client import errors


def chat_securise(message_utilisateur: str) -> str:
    """Sends a message protected by guardrails."""
    try:
        response = client.chat.complete(
            model="mistral-large-latest",
            messages=[
                {"role": "system", "content": "You are a professional assistant."},
                {"role": "user", "content": message_utilisateur},
            ],
            guardrails=GUARDRAILS,
        )
    except errors.SDKError as e:
        if e.status_code == 403:
            return "Sorry, this request cannot be processed."
        raise

    contenu = response.choices[0].message.content
    return contenu if isinstance(contenu, str) else ""


print(chat_securise("Explain machine learning to me"))

Two notes. message.content can be a string or a list of parts depending on the model, hence the isinstance check. And the response from chat.complete() does not expose guardrail scores in SDK 3.7.0: to log them, use the Conversations API, whose response carries them in guardrails.

Handle blocking errors cleanly

You must distinguish two failures that call for neither the same message nor the same alert: a content policy block, which returns HTTP status 403, and a moderation evaluation failure with block_on_error enabled, which the response body signals with code 3201. Both arrive as errors.SDKError, whose body contains the JSON returned by the API:

import json
from typing import Any

from mistralai.client import errors


def chat_avec_gestion_erreurs(message_utilisateur: str) -> dict[str, Any]:
    """Chat that distinguishes policy blocks from moderation failures."""
    try:
        response = client.chat.complete(
            model="mistral-large-latest",
            messages=[{"role": "user", "content": message_utilisateur}],
            guardrails=GUARDRAILS,
        )
    except errors.SDKError as e:
        try:
            corps = json.loads(e.body)
        except json.JSONDecodeError:
            raise e

        if corps.get("code") == 3201:
            return {
                "status": "error",
                "content": "Moderation service unavailable. Request blocked as a precaution.",
            }
        if e.status_code == 403:
            decisions = corps["guardrails"]["results"]["moderation_llm_v2"]["decisions"]
            return {
                "status": "blocked",
                "content": "Your message was blocked by our security policy.",
                "categories": [nom for nom, d in decisions.items() if d["violated"]],
            }
        raise

    return {"status": "ok", "content": response.choices[0].message.content}

Check code 3201 first, because the documentation does not specify the HTTP status for this error. Violated categories come from decisions, where each entry gives threshold, score, and violated: this is what you will log to explain a block.

Configuration best practices

Start restrictive, then loosen up. Low thresholds at launch help you discover false positives while volume is still manageable, and each increase is decided based on observed cases rather than intuition:

# Phase 1: launch (very restrictive)
phase1 = {"hate_and_discrimination": 0.1, "violence_and_threats": 0.1, "criminal": 0.1}

# Phase 2: after analyzing false positives
phase2 = {"hate_and_discrimination": 0.15, "violence_and_threats": 0.25, "criminal": 0.1}

# Phase 3: stable production
phase3 = {"hate_and_discrimination": 0.2, "violence_and_threats": 0.3, "criminal": 0.15}

These values illustrate the approach; yours will come from your own measurements. Keep block_on_error set to True in production: its default value in the SDK is False, and a moderation failure would then let unverified content through. Finally, the documentation warns that the moderation model evolves: policies based on scores may require recalibration.

Key takeaways

  • guardrails is a list of objects {"block_on_error": ..., "moderation_llm_v2": {...}}
  • Inside moderation_llm_v2: custom_category_thresholds, ignore_other_categories, action, and optionally model_name
  • Thresholds from 0 to 1; lower is stricter; 1 disables a category. List every category that should block
  • A block raises errors.SDKError with status_code == 403; a failure with block_on_error carries code 3201 in the body
  • Keep block_on_error=True in production, as the default is False