Configure guardrails in Python
On this page
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 testede.status_code == 3201, even though3201is a response body error code, not an HTTP status. Configuration now follows the official format, and the code has been verified against themistralaiSDK 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
guardrailsis a list of objects{"block_on_error": ..., "moderation_llm_v2": {...}}- Inside
moderation_llm_v2:custom_category_thresholds,ignore_other_categories,action, and optionallymodel_name - Thresholds from
0to1; lower is stricter;1disables a category. List every category that should block - A block raises
errors.SDKErrorwithstatus_code == 403; a failure withblock_on_errorcarries code3201in the body - Keep
block_on_error=Truein production, as the default isFalse