Custom Guardrails: Block Before the Model
On this page
Lesson rewritten on November 3, 2026. The previous version described a fictional
guardrailsparameter (a dictionary withenabled), non-existent categories (hate,violence), a missingconversation.send()method in the SDK, and imaginary JSON responses. The official format is a list ofmoderation_llm_v2configurations, and the responses below match the documentation. The code has been verified against themistralaiSDK types version 3.7.0. Sources: Moderation & Guardrailing · mistralai Python SDK.
The Principle of Custom Guardrails
Mistral’s Custom Guardrails act as an input filter: they analyze the user’s prompt before it reaches the generation model. If the content exceeds any threshold you’ve set, the request is blocked with an HTTP 403 error. The model never sees the problematic prompt.
Keep two limitations in mind from the start. First, guardrails moderate input only; they do not check what the model generates. That is the role of the Moderation API (Lesson 5). Second, they replace work you would otherwise have to code yourself: calling the Moderation API, comparing scores against your thresholds, and rejecting the request.
How It Works
When you add guardrails to a request, the flow on Mistral’s side is as follows:
- Your application sends the request with the
guardrailsparameter. - The classifier
mistral-moderation-2603scores the prompt in each category. - If a score exceeds its threshold, the API returns a
403error and the model is not called. - Otherwise, the model processes the request normally.
- The response includes a
guardrailsfield containing the evaluated scores.
Flow: prompt → [mistral-moderation-2603] → score > threshold? → 403 (blocked)
→ else → [LLM] → response + scores
The decision relies on a score compared to a threshold you set, category by category, between 0 and 1. A low threshold blocks more content; a threshold of 1 explicitly disables the category.
The guardrails Parameter Format
The parameter is a list. Each element is an object containing a moderation_llm_v2 configuration and, alongside it, the block_on_error option:
guardrails = [
{
"block_on_error": True, # block if moderation itself fails
"moderation_llm_v2": {
"custom_category_thresholds": {
"jailbreaking": 0.3,
"criminal": 0.2,
},
"ignore_other_categories": False,
"action": "block",
},
}
]
| Key | Location | Role |
|---|---|---|
custom_category_thresholds | inside moderation_llm_v2 | Threshold from 0 to 1 per category; 1 disables the category |
ignore_other_categories | inside moderation_llm_v2 | True: only listed categories are evaluated |
action | inside moderation_llm_v2 | "block" to reject the request if a threshold is exceeded |
model_name | inside moderation_llm_v2, optional | Moderation model other than the default mistral-moderation-2603 |
block_on_error | alongside moderation_llm_v2 | True: the request is blocked if evaluation fails |
You can place multiple objects in the list: the request is blocked as soon as any one of them triggers. Each object contains only one moderation_llm_v2 configuration. An older configuration, moderation_llm_v1, still exists in the SDK with categories from the deprecated mistral-moderation-2411 model (deprecated March 31, 2026): do not use it for new projects.
The Eleven Categories
The keys in custom_category_thresholds are fixed; another key, such as violence or hate, corresponds to nothing:
| Key | What it detects |
|---|---|
sexual | Explicit sexual content |
hate_and_discrimination | Hate or discrimination based on protected characteristics |
violence_and_threats | Described, glorified, or threatened physical violence |
dangerous | Extremely dangerous behaviors |
criminal | Illegal activities |
selfharm | Self-harm, suicide, eating disorders |
health | Requests for detailed or personalized medical advice |
financial | Requests for detailed or personalized financial advice |
law | Requests for detailed or personalized legal advice |
pii | Personal data requested or shared |
jailbreaking | Attempts to bypass model rules |
Lesson 6 revisits each of these and discusses threshold selection.
The Three Levels of Application
The same parameter is accepted in three places. Your choice determines where your security policy lives and who might forget to apply it.
Level 1: A Chat Completions Request
You pass guardrails in the chat.complete() call. Each request carries its own rules: practical for exploration, but risky at scale, since a single call without the parameter slips through unchecked.
import os
from mistralai.client import Mistral
client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
response = client.chat.complete(
model="mistral-large-latest",
messages=[{"role": "user", "content": "What is the distance from the Earth to the Moon?"}],
guardrails=[
{
"block_on_error": True,
"moderation_llm_v2": {
"custom_category_thresholds": {"sexual": 0.1, "selfharm": 0.1},
"ignore_other_categories": False,
"action": "block",
},
}
],
)
print(response.choices[0].message.content)
Use case: Prototyping, one-off calls, tuning thresholds.
Level 2: A Conversation
With the Conversations API, you pass guardrails at startup, in client.beta.conversations.start():
conversation = client.beta.conversations.start(
model="mistral-large-latest",
inputs="Hello, can you help me prepare for an interview?",
guardrails=[
{
"block_on_error": True,
"moderation_llm_v2": {
"custom_category_thresholds": {
"sexual": 0.2,
"hate_and_discrimination": 0.1,
"violence_and_threats": 0.3,
"selfharm": 0.1,
},
"action": "block",
},
}
],
)
for entree in conversation.outputs:
if entree.type == "message.output":
print(entree.content)
Subsequent messages are sent with client.beta.conversations.append(conversation_id=conversation.conversation_id, inputs=...). This method does not accept a guardrails parameter in SDK 3.7.0, and the documentation does not specify whether the startup guardrails apply to subsequent turns. For guaranteed protection on every turn, attach guardrails to an agent.
Use case: One-off conversation without an agent, or overriding an agent’s guardrails for a specific request.
Level 3: An Agent
Guardrails passed to client.beta.agents.create() become part of the agent’s definition. According to the documentation, all conversations using this agent inherit them without needing to repeat them. However, a conversation request can override them by passing its own guardrails.
agent = client.beta.agents.create(
model="mistral-large-latest",
name="Secure Assistant",
instructions="You are a professional customer service assistant.",
guardrails=[
{
"block_on_error": True,
"moderation_llm_v2": {
"custom_category_thresholds": {
"sexual": 0.1,
"hate_and_discrimination": 0.1,
"violence_and_threats": 0.2,
"criminal": 0.1,
"selfharm": 0.1,
"jailbreaking": 0.3,
},
"ignore_other_categories": False,
"action": "block",
},
}
],
)
conversation = client.beta.conversations.start(agent_id=agent.id, inputs="Hello!")
Use case: Production, when multiple teams or services call the same assistant.
What Guardrails Return
When the request passes, the API response includes, alongside standard fields, a guardrails field: a list with one result per guardrail. Here is that field from the documentation example:
{
"guardrails": [
{
"moderation_llm_v2": {
"action": "pass",
"categories": {
"sexual": { "score": 0.03, "violated": false },
"selfharm": { "score": 0.05, "violated": false },
"violence_and_threats": { "score": 0.0, "violated": false },
"hate_and_discrimination": { "score": 0.0, "violated": false }
}
}
}
]
}
With ignore_other_categories set to True, only the categories listed in your thresholds appear in this result. In the Python SDK, a conversation response exposes this field (conversation.guardrails). The chat.complete() response, however, has no designated field for it in SDK 3.7.0: it gets lost there. If you want to log scores—and it is useful to know later whether your thresholds are too loose or too tight—use conversations or read the raw HTTP response.
When a threshold is exceeded, the API returns a 403 error whose body details the decision, category by category:
{
"error": { "message": "Content blocked by guardrail", "status": 403 },
"guardrails": {
"results": {
"moderation_llm_v2": {
"model_name": "mistral-moderation-2603",
"decisions": {
"sexual": { "threshold": 0.1, "score": 0.3, "violated": true },
"selfharm": { "threshold": 0.1, "score": 0.05, "violated": false }
},
"violated": true,
"action": "block"
}
}
}
}
The SDK then raises errors.SDKError, where status_code is 403 and body contains this JSON:
import json
from mistralai.client import errors
try:
response = client.chat.complete(
model="mistral-large-latest",
messages=[{"role": "user", "content": user_message}],
guardrails=guardrails,
)
except errors.SDKError as e:
if e.status_code != 403:
raise
decisions = json.loads(e.body)["guardrails"]["results"]["moderation_llm_v2"]["decisions"]
violated = [name for name, d in decisions.items() if d["violated"]]
print(f"Request blocked: {violated}")
This payload allows you to explain a block during an appeal: which category, what score, what threshold.
Finally, there is the degraded case. If the evaluation itself fails and block_on_error is True, the request is refused with error code 3201 and the message Request blocked due to error in guardrail evaluation and block_on_error is set to True. This is fail-safe mode: when in doubt, block. With block_on_error set to False (its default value in the SDK), the request would pass unchecked.
Choosing the Right Level
| Criterion | Chat Completions | Conversation | Agent |
|---|---|---|---|
| Where guardrails are declared | In every call | At startup (start) | At agent creation |
| Inheritance | None | Undocumented for subsequent turns | All agent conversations |
| Overridable | — | — | Yes, by a conversation request |
| Use case | Tests, tuning thresholds | One-off conversation | Production |
In practice, tune your thresholds on Chat Completions requests using real cases, then move the policy to an agent as soon as the code leaves your machine: there will be no more forgotten calls.
Key Takeaways
- Guardrails block prompts before the model via a
403error; they do not check output - The parameter is a list of objects
{"block_on_error": ..., "moderation_llm_v2": {...}}, not a dictionary withenabled - Eleven categories with fixed names (
hate_and_discrimination,violence_and_threats, …), each with a threshold from 0 to 1;1disables - Three levels:
chat.complete(),conversations.start(),agents.create(); agents pass their guardrails to all their conversations block_on_error=Trueensures fail-safe behavior (error3201)