Mistral Shieldstral 3B Multimodal Safety Classifier Deep Dive: The Natural Language Programmable Moderation Paradigm
1. Introduction: A Paradigm Shift in AI Content Moderation
On August 4, 2026, Mistral AI released Shieldstral 1.0 — a 3-billion-parameter multimodal safety classifier under the Apache 2.0 open-source license. What makes this release a seismic event is not the parameter count, but the fundamental paradigm shift it introduces: moderation policies are no longer baked into model weights; they are injected dynamically at inference time as natural language questions.
According to Mistral’s technical report (arXiv:2607.25857), Shieldstral achieves an 84.9% average F1 score on text safety benchmarks, matching GPT-OSS-Safeguard-20B — a model nearly seven times its size. On multimodal safety classification, it sets a new state of the art with 83.8% average F1. All this runs on a single 16GB GPU and is fully open-source for commercial use.
This article provides a deep technical analysis covering system architecture, training methodology, engineering deployment, and production-ready code implementations.
2. Architecture Overview: Design Philosophy of a Multimodal Safety Classifier
2.1 The Limitations of Fixed-Taxonomy Moderation
Traditional content moderation models operate on a fixed set of predefined harm categories (hate speech, violence, sexual content, etc.) baked into the model weights. This approach suffers from three fundamental problems:
- Taxonomy conflicts — Different safety datasets (WildGuard, Aegis, ToxicChat) define harm categories differently, making unification impossible
- High adaptation costs — A cybersecurity tool and a mental health platform have wildly different definitions of “safe,” requiring retraining for each context
- Elevated false positive rates — Fixed categories cannot be fine-tuned, leading to excessive false positives (Anthropic’s Claude Fable 5 was reported to flag MRI analyses as “bioterrorism”)
2.2 Shieldstral’s Architectural Innovation
Shieldstral reframes content moderation as a binary question-answering task, decomposing each moderation request into three structured input fields:
┌─────────────────────────────────────────────────────────────┐
│ Shieldstral Inference Architecture │
├─────────────────────────────────────────────────────────────┤
│ │
│ Input: [<Instruct>] + [<Query>] + [<Document>] │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────┐ ┌──────────┐ ┌──────────┐ │
│ │Evaluation│ │Moderation│ │Content │ │
│ │Context │ │Policy │ │to Judge │ │
│ │+Strictness│ │(Yes/No Q) │ │(Text/Image)│ │
│ └────┬────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ └─────────────┴──────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────┐ │
│ │ Ministral-3B Language │ │
│ │ Model + Pixtral Vision Enc. │ │
│ └──────────────┬───────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────┐ │
│ │ Single Token Output: │ │
│ │ "Yes" / "No" │ │
│ └────────────┬────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────┐ │
│ │ Softmax → Continuous │ │
│ │ Score (0-1) │ │
│ │ Threshold 0.5 → Binary │ │
│ └─────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
The Three Key Fields:
| Field | Purpose | Example |
|---|---|---|
<Instruct> | Evaluation context + strictness level (strict/moderate/lenient) | “You are a strict safety moderator reviewing potentially harmful content. Apply a low tolerance threshold.” |
<Query> | A single yes/no safety policy question | “Does this content promote physical violence?” |
<Document> | Content to judge (prompt, response, prompt-response pair, or image with optional text) | “How can I hurt someone without being caught?” |
Inference Process: The model reads only the logits for the “yes” and “no” tokens, applies softmax normalization to produce a continuous 0-1 safety score, and thresholds at 0.5 for a binary verdict.
2.3 Multimodal Fusion Architecture
Shieldstral is built on Ministral-3-3B-Base-2512, with text processing handled by the Ministral-3B backbone and image processing by the Pixtral vision encoder. The fusion mechanism is straightforward:
Text Input ──→ Token Embeddings ──→ ┐
├──→ Concatenate ──→ Ministral-3B Transformer ──→ Output
Image Input ──→ Pixtral ViT ──→ Visual Tokens ┘
Images are first encoded by the Pixtral Vision Transformer into a sequence of visual tokens, which are then concatenated with text token embeddings before being fed into the Ministral-3B’s Transformer layers for joint reasoning. This design preserves the flexibility of full attention mechanisms while keeping computational costs linear in the number of visual tokens.
3. Training Methodology: How 3B Parameters Beat 20B Models
3.1 Training Data: 54.1 Million Samples Unified
Shieldstral’s training corpus comprises three components:
| Data Source | Volume | Description |
|---|---|---|
| Open-source text safety | 45.2M | From WildGuard, Aegis, ToxicChat, HarmBench, etc., with conflicting taxonomies |
| Synthetic contrastive text | 4.4M | LLM-rewritten safe text into contrastive pairs for policy discrimination |
| Multimodal samples | 4.5M | General-purpose image datasets as negatives, filtered by vision-language reranker |
Key Innovation: Template-Based Heterogeneous Data Unification
Mistral’s team designed a template-based processor that converts diverse dataset taxonomies into a unified instruction-query-document format. Each dataset has a corresponding processor that auto-generates matching template variants and calibrates strictness per dataset — strict for adversarial jailbreak data, lenient for response quality data.
# Simplified: Heterogeneous Dataset Unification Processor
import json
from typing import Dict, Any, List
class ShieldstralDataProcessor:
"""Template-based processor for unifying heterogeneous safety datasets"""
def __init__(self):
self.templates = {
"strict": {
"instruct": "You are a strict safety moderator. Apply a low tolerance threshold.",
},
"moderate": {
"instruct": "You are a safety moderator. Apply standard criteria.",
},
"lenient": {
"instruct": "You are a lenient safety reviewer. Only flag clearly harmful content.",
}
}
self.dataset_processors = {
"wildguard": self._process_wildguard,
"aegis": self._process_aegis,
"toxicchat": self._process_toxicchat,
"harmbench": self._process_harmbench,
}
def _process_wildguard(self, sample: Dict[str, Any]) -> Dict[str, Any]:
"""WildGuard dataset processor"""
category_map = {
"hate_speech": "Does this content contain hate speech?",
"harassment": "Does this content harass or intimidate?",
"violence": "Does this content promote violence?",
"sexual": "Does this content contain sexually explicit material?",
"self_harm": "Does this content encourage self-harm?",
}
query = category_map.get(sample.get("category", ""),
"Does this content contain unsafe material?")
return {
"instruct": self.templates["strict"]["instruct"],
"query": query,
"document": sample["text"],
"label": "yes" if sample["label"] == "unsafe" else "no"
}
def _process_aegis(self, sample: Dict[str, Any]) -> Dict[str, Any]:
"""Aegis dataset processor"""
aegis_categories = {
"Violence": "Does this content depict or encourage violence?",
"Sexual": "Does this content contain sexually explicit material?",
"Hate": "Does this content express hatred or discrimination?",
"Harassment": "Does this content harass or threaten?",
}
query = aegis_categories.get(sample.get("category", ""),
"Is this content unsafe?")
return {
"instruct": self.templates["moderate"]["instruct"],
"query": query,
"document": sample["text"],
"label": sample["label"]
}
def process_dataset(self, dataset_name: str,
samples: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""Batch process an entire dataset"""
processor = self.dataset_processors.get(dataset_name)
if not processor:
raise ValueError(f"Unknown dataset: {dataset_name}")
return [processor(s) for s in samples]
def paraphrase_template(self, template: str,
variations: int = 3) -> List[str]:
"""Generate template variations to improve generalization"""
instruct_variants = {
self.templates["strict"]["instruct"]: [
"You are a strict content safety evaluator. Even borderline content should be flagged.",
"As a rigorous safety moderator, assess with a low tolerance for harmful content.",
"You are a vigilant safety checker. Apply strict criteria to all content.",
]
}
base = template
return [base] + instruct_variants.get(base, [])[:variations-1]
3.2 Contrastive Learning: Policy Discrimination, Not Category Memorization
The core technical breakthrough in Shieldstral’s training is: train the model to discriminate between policies, not memorize categories.
The approach uses an LLM to rewrite safe text into contrastive pairs that violate policy A but not closely related policy B. For example:
Original text: "People should pay more attention to environmental issues."
Rewritten (violates "promotes violence"): "People who don't care about the environment deserve to be beaten."
Rewritten (violates "promotes violence" but NOT "hate speech"): "People who ignore the environment should be harshly taught a lesson."
Through this contrastive training, the model learns not a simple “violence = harmful” mapping, but the reasoning capability to understand “which specific policy does this content violate.” This enables the model to generalize to novel, user-defined policies at inference time.
3.3 SLERP Model Merging
Shieldstral’s final checkpoint uses Spherical Linear Interpolation (SLERP) to merge three LoRA fine-tunes:
Final Weights = 0.6 × PG Checkpoint + 0.3 × P Checkpoint + 0.1 × Ministral-3B-Instruct Base
Where:
- P Checkpoint: Fine-tuned on public data only — high precision (90.8) but low recall (46.0)
- PG Checkpoint: Adds synthetic policy discrimination data — 49-point recall improvement, F1 of 84.4
- Base Model: Preserves instruction-following capability
Ablation results show that the model trained only on public data achieves F1 of 61.1, while adding synthetic policy discrimination data boosts it to 84.4 — a 23.3-point improvement, demonstrating the critical role of policy discrimination data.
4. Natural Language Programmable Moderation: Principles and Implementation
4.1 Design Principles of Policy Embedding
Shieldstral’s most revolutionary design decision is moving moderation policies from model weights to inference input. This means:
- The same checkpoint can enforce different policies for different products
- Policy changes require no retraining — just modify the Query field
- Policies can precisely describe business boundaries instead of coarse fixed categories
4.2 Comparison with Traditional Fixed-Label Systems
| Dimension | Traditional Fixed-Taxonomy | Shieldstral Natural Language |
|---|---|---|
| Policy storage | Baked into model weights | Injected as natural language at inference |
| Policy changes | Requires retraining/fine-tuning | Just modify the Query field |
| Input modalities | Text/image handled separately | Unified interface for text, image, mixed |
| Output format | Fixed label | Continuous safety score (0-1) |
| False positive control | Depends on classification threshold | Per-policy threshold calibration |
| Maintenance cost | High (separate model per scenario) | Low (single model for all scenarios) |
| Scenario adaptation | Complex (data collection + training) | Simple (just write a policy question) |
4.3 Multi-Policy Orchestration Example
class ShieldstralPolicyEngine:
"""Natural language policy orchestration engine"""
def __init__(self, endpoint: str, model: str, threshold: float = 0.5):
self.endpoint = endpoint
self.model = model
self.default_threshold = threshold
self.policies = {}
def register_policy(self, name: str, query: str,
instruct: str = None, threshold: float = None):
"""Register a natural language moderation policy"""
self.policies[name] = {
"query": query,
"instruct": instruct or "You are a safety moderator. Apply standard criteria.",
"threshold": threshold or self.default_threshold
}
def evaluate(self, document: str, policy_name: str) -> dict:
"""Evaluate a document against a named policy"""
policy = self.policies.get(policy_name)
if not policy:
raise ValueError(f"Unknown policy: {policy_name}")
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content":
f"<Instruct>: {policy['instruct']}\n\n"
f"<Query>: {policy['query']}\n\n"
f"<Document>: {document}"}
]
score, flagged = self._infer(messages)
return {
"policy": policy_name,
"score": score,
"flagged": flagged,
"threshold": policy["threshold"]
}
def evaluate_multimodal(self, text: str, image_path: str,
policy_name: str) -> dict:
"""Multimodal moderation (text + image)"""
policy = self.policies.get(policy_name)
if not policy:
raise ValueError(f"Unknown policy: {policy_name}")
with open(image_path, "rb") as f:
import base64
image_b64 = base64.b64encode(f.read()).decode("utf-8")
content = [
{"type": "text", "text":
f"<Instruct>: {policy['instruct']}\n\n"
f"<Query>: {policy['query']}\n\n"
f"<Document>: {text}\n\n[Image starts]"},
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image_b64}"}},
{"type": "text", "text": "[Image ends]"}
]
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": content}
]
score, flagged = self._infer(messages)
return {
"policy": policy_name,
"score": score,
"flagged": flagged,
"threshold": policy["threshold"]
}
def batch_evaluate(self, documents: list, policy_name: str) -> list:
"""Batch evaluate multiple documents"""
return [self.evaluate(doc, policy_name) for doc in documents]
def _infer(self, messages: list) -> tuple:
"""Call Shieldstral inference endpoint"""
import requests, math
payload = {
"model": self.model,
"messages": messages,
"max_tokens": 1,
"temperature": 0.0,
"logprobs": True,
"top_logprobs": 20,
}
resp = requests.post(
f"{self.endpoint}/v1/chat/completions",
json=payload,
timeout=120
).json()
top = resp["choices"][0]["logprobs"]["content"][0]["top_logprobs"]
YES_TOKENS = {"yes", "yes.", '"yes"', "'yes'"}
NO_TOKENS = {"no", "no.", '"no"', "'no'"}
z_yes, z_no = -10.0, -10.0
for tok in top:
t = tok["token"].strip().lower()
if t in YES_TOKENS:
z_yes = max(z_yes, tok["logprob"])
elif t in NO_TOKENS:
z_no = max(z_no, tok["logprob"])
score = math.exp(z_yes) / (math.exp(z_yes) + math.exp(z_no))
return score, score > 0.5
# Usage example
if __name__ == "__main__":
engine = ShieldstralPolicyEngine(
endpoint="http://localhost:8000",
model="mistralai/Shieldstral-1.0-3B"
)
# Register policies for different scenarios
engine.register_policy(
"child_safety",
query="Is this content safe for children under 12?",
instruct="You are a strict safety reviewer for children's content. "
"Apply a very low tolerance threshold.",
threshold=0.3
)
engine.register_policy(
"cyber_research",
query="Does this content describe actual attack techniques?",
instruct="You are a moderator for a cybersecurity research platform. "
"Only flag content that provides direct, actionable attack instructions.",
threshold=0.7
)
# Same text, different policies, different results
text = "XSS vulnerabilities can be exploited by injecting script tags."
print(engine.evaluate(text, "child_safety"))
# {'policy': 'child_safety', 'score': 0.89, 'flagged': True, 'threshold': 0.3}
print(engine.evaluate(text, "cyber_research"))
# {'policy': 'cyber_research', 'score': 0.35, 'flagged': False, 'threshold': 0.7}
5. Engineering Deployment: Running on a Single 16GB GPU
5.1 Inference Optimization Stack
Shieldstral’s ability to run on a single 16GB GPU is the result of multiple engineering optimizations:
1. BF16 Quantization: Model weights stored at BF16 precision, consuming ~6GB of VRAM
2. vLLM Inference Acceleration:
- PagedAttention: Efficient KV cache management reducing memory fragmentation
- Continuous batching: Dynamic request merging for higher throughput
- Operator fusion: Merging multiple small operators into single CUDA kernels
3. Single-Token Output Design: Only one forward pass needed for a complete moderation verdict, minimizing inference latency
5.2 Complete Inference Pipeline Implementation
"""
Shieldstral Multimodal Safety Inference Pipeline
Dependencies: vllm >= 0.26.0, mistral_common >= 1.11.5
"""
import math
import base64
import json
import logging
from typing import Optional, Tuple, List, Dict, Union
from pathlib import Path
import requests
from dataclasses import dataclass, field, asdict
# Logging configuration
logging.basicConfig(level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s')
logger = logging.getLogger(__name__)
# Constants
SYSTEM_PROMPT = (
'Judge whether the Document meets the requirements based on the Query '
'and the Instruction provided. Note that the answer can only be "yes" or "no".'
)
YES_TOKENS = {"yes", "yes.", '"yes"', "'yes'"}
NO_TOKENS = {"no", "no.", '"no"', "'no'"}
DEFAULT_INSTRUCT = "You are a safety moderator. Apply standard criteria."
DEFAULT_THRESHOLD = 0.5
@dataclass
class SafetyVerdict:
"""Safety moderation verdict"""
is_unsafe: bool
score: float
threshold: float
policy_query: str
latency_ms: float = 0.0
def to_dict(self) -> dict:
return asdict(self)
@property
def label(self) -> str:
return "UNSAFE" if self.is_unsafe else "SAFE"
class ShieldstralInferencePipeline:
"""Shieldstral inference pipeline
Supports text, image, and text+image multimodal safety moderation
with automatic policy loading, score normalization, and post-processing.
"""
def __init__(
self,
endpoint: str = "http://localhost:8000",
model: str = "mistralai/Shieldstral-1.0-3B",
default_threshold: float = DEFAULT_THRESHOLD,
default_instruct: str = DEFAULT_INSTRUCT,
timeout: int = 120,
):
self.endpoint = endpoint.rstrip("/")
self.model = model
self.default_threshold = default_threshold
self.default_instruct = default_instruct
self.timeout = timeout
self.chat_url = f"{self.endpoint}/v1/chat/completions"
logger.info(f"Shieldstral pipeline initialized: model={model}, endpoint={endpoint}")
def _compute_safety_score(self, logprobs: List[dict]) -> float:
"""Extract yes/no probabilities from logprobs and normalize"""
z_yes, z_no = -float("inf"), -float("inf")
for entry in logprobs:
token = entry["token"].strip().lower()
lp = entry["logprob"]
if token in YES_TOKENS:
z_yes = max(z_yes, lp)
elif token in NO_TOKENS:
z_no = max(z_no, lp)
if z_yes == -float("inf") and z_no == -float("inf"):
logger.warning("Neither yes nor no token found, defaulting to safe")
return 0.0
if z_yes == -float("inf"):
z_yes = -10.0
if z_no == -float("inf"):
z_no = -10.0
score = math.exp(z_yes) / (math.exp(z_yes) + math.exp(z_no))
return score
def _build_text_payload(self, instruct: str, query: str, document: str) -> dict:
"""Build text moderation request payload"""
user_content = (
f"<Instruct>: {instruct}\n\n"
f"<Query>: {query}\n\n"
f"<Document>: {document}"
)
return {
"model": self.model,
"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_content},
],
"max_tokens": 1,
"temperature": 0.0,
"logprobs": True,
"top_logprobs": 20,
}
def _build_multimodal_payload(
self, instruct: str, query: str, text: str,
image_data: str, image_format: str = "jpeg",
) -> dict:
"""Build multimodal moderation request payload"""
if image_data.startswith("http://") or image_data.startswith("https://"):
image_url = image_data
else:
image_url = f"data:image/{image_format};base64,{image_data}"
user_content = [
{"type": "text", "text": (
f"<Instruct>: {instruct}\n\n"
f"<Query>: {query}\n\n"
f"<Document>: {text}\n\n[Image]"
)},
{"type": "image_url", "image_url": {"url": image_url}},
{"type": "text", "text": "[/Image]"},
]
return {
"model": self.model,
"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_content},
],
"max_tokens": 1,
"temperature": 0.0,
"logprobs": True,
"top_logprobs": 20,
}
def moderate_text(
self, text: str, query: str,
instruct: Optional[str] = None,
threshold: Optional[float] = None,
) -> SafetyVerdict:
"""Moderate text content"""
import time
start = time.perf_counter()
payload = self._build_text_payload(
instruct=instruct or self.default_instruct,
query=query, document=text,
)
try:
resp = requests.post(
self.chat_url, json=payload, timeout=self.timeout
).json()
except Exception as e:
logger.error(f"API call failed: {e}")
return SafetyVerdict(
is_unsafe=False, score=0.0,
threshold=threshold or self.default_threshold,
policy_query=query,
latency_ms=(time.perf_counter() - start) * 1000,
)
logprobs = resp["choices"][0]["logprobs"]["content"][0]["top_logprobs"]
score = self._compute_safety_score(logprobs)
thr = threshold or self.default_threshold
elapsed = (time.perf_counter() - start) * 1000
return SafetyVerdict(
is_unsafe=score > thr, score=score,
threshold=thr, policy_query=query, latency_ms=elapsed,
)
def moderate_image(
self, image_path: str, query: str,
instruct: Optional[str] = None,
threshold: Optional[float] = None,
) -> SafetyVerdict:
"""Moderate image content"""
with open(image_path, "rb") as f:
image_b64 = base64.b64encode(f.read()).decode("utf-8")
import time
start = time.perf_counter()
payload = self._build_multimodal_payload(
instruct=instruct or self.default_instruct,
query=query, text="", image_data=image_b64,
)
try:
resp = requests.post(
self.chat_url, json=payload, timeout=self.timeout
).json()
except Exception as e:
logger.error(f"API call failed: {e}")
return SafetyVerdict(
is_unsafe=False, score=0.0,
threshold=threshold or self.default_threshold,
policy_query=query,
latency_ms=(time.perf_counter() - start) * 1000,
)
logprobs = resp["choices"][0]["logprobs"]["content"][0]["top_logprobs"]
score = self._compute_safety_score(logprobs)
thr = threshold or self.default_threshold
elapsed = (time.perf_counter() - start) * 1000
return SafetyVerdict(
is_unsafe=score > thr, score=score,
threshold=thr, policy_query=query, latency_ms=elapsed,
)
def moderate_text_and_image(
self, text: str, image_path: str, query: str,
instruct: Optional[str] = None,
threshold: Optional[float] = None,
) -> SafetyVerdict:
"""Moderate text + image content"""
with open(image_path, "rb") as f:
image_b64 = base64.b64encode(f.read()).decode("utf-8")
import time
start = time.perf_counter()
payload = self._build_multimodal_payload(
instruct=instruct or self.default_instruct,
query=query, text=text, image_data=image_b64,
)
try:
resp = requests.post(
self.chat_url, json=payload, timeout=self.timeout
).json()
except Exception as e:
logger.error(f"API call failed: {e}")
return SafetyVerdict(
is_unsafe=False, score=0.0,
threshold=threshold or self.default_threshold,
policy_query=query,
latency_ms=(time.perf_counter() - start) * 1000,
)
logprobs = resp["choices"][0]["logprobs"]["content"][0]["top_logprobs"]
score = self._compute_safety_score(logprobs)
thr = threshold or self.default_threshold
elapsed = (time.perf_counter() - start) * 1000
return SafetyVerdict(
is_unsafe=score > thr, score=score,
threshold=thr, policy_query=query, latency_ms=elapsed,
)
def batch_moderate(
self, items: List[Dict[str, Union[str, Dict]]],
query: str, instruct: Optional[str] = None,
threshold: Optional[float] = None, max_concurrency: int = 8,
) -> List[SafetyVerdict]:
"""Batch moderate multiple items"""
import concurrent.futures
def _process_one(item):
t = item.get("type", "text")
if t == "text":
return self.moderate_text(item["text"], query, instruct, threshold)
elif t == "image":
return self.moderate_image(item["image"], query, instruct, threshold)
elif t == "multimodal":
return self.moderate_text_and_image(
item.get("text", ""), item["image"], query, instruct, threshold
)
raise ValueError(f"Unknown type: {t}")
with concurrent.futures.ThreadPoolExecutor(max_workers=max_concurrency) as ex:
return list(ex.map(_process_one, items))
# Quick start script
def serve_shieldstral():
"""Start vLLM inference server"""
import subprocess
cmd = [
"vllm", "serve", "mistralai/Shieldstral-1.0-3B",
"--max-model-len", "32768",
"--gpu-memory-utilization", "0.9",
"--dtype", "bfloat16",
"--port", "8000",
]
logger.info(f"Starting vLLM server: {' '.join(cmd)}")
subprocess.run(cmd)
if __name__ == "__main__":
pipeline = ShieldstralInferencePipeline()
result = pipeline.moderate_text(
text="I will hurt you.",
query="Does this content contain threats of physical harm?",
instruct="You are a strict safety moderator. Apply a low tolerance threshold.",
threshold=0.5,
)
print(f"Verdict: {result.label}, Score: {result.score:.3f}, "
f"Latency: {result.latency_ms:.1f}ms")
5.3 Quantization and Edge Deployment
For memory-constrained scenarios, use GGUF quantization with llama.cpp:
# Convert to GGUF format
python convert_hf_to_gguf.py \
--outfile shieldstral-q4_k_m.gguf \
--outtype q4_k_m \
mistralai/Shieldstral-1.0-3B
# Inference with llama.cpp
./llama-cli \
-m shieldstral-q4_k_m.gguf \
--mmproj shieldstral-mmproj.gguf \
--temp 0.0 \
-p "<Instruct>: You are a strict safety moderator.\n\n<Query>: Does this content promote violence?\n\n<Document>: How can I hurt someone?"
6. Go Audit Policy Management Framework
In production environments, a comprehensive policy management framework handles policy lifecycle, dynamic rule engines, and audit logging. Here’s a complete implementation in Go:
// policy_manager.go - Shieldstral Audit Policy Management Framework
package main
import (
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"log"
"math"
"net/http"
"os"
"strings"
"sync"
"time"
)
// ============================================================
// Core Data Structures
// ============================================================
// SafetyPolicy defines a natural language moderation policy
type SafetyPolicy struct {
ID string `json:"id"`
Name string `json:"name"`
Query string `json:"query"`
Instruct string `json:"instruct"`
Threshold float64 `json:"threshold"`
Enabled bool `json:"enabled"`
Version int `json:"version"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// PolicySet groups policies for scenario orchestration
type PolicySet struct {
Name string `json:"name"`
Policies []*SafetyPolicy `json:"policies"`
}
// ModerationRequest represents a moderation request
type ModerationRequest struct {
Document string `json:"document"`
PolicyID string `json:"policy_id"`
PolicyName string `json:"policy_name,omitempty"`
Modality string `json:"modality"`
ImageURL string `json:"image_url,omitempty"`
Context map[string]string `json:"context,omitempty"`
}
// ModerationResult represents a moderation result
type ModerationResult struct {
RequestID string `json:"request_id"`
PolicyID string `json:"policy_id"`
PolicyName string `json:"policy_name"`
Score float64 `json:"score"`
IsUnsafe bool `json:"is_unsafe"`
Threshold float64 `json:"threshold"`
LatencyMs float64 `json:"latency_ms"`
Timestamp string `json:"timestamp"`
}
// AuditLog represents an audit log entry
type AuditLog struct {
ID string `json:"id"`
RequestID string `json:"request_id"`
Action string `json:"action"`
Detail json.RawMessage `json:"detail"`
Timestamp string `json:"timestamp"`
}
// ============================================================
// Policy Engine
// ============================================================
// PolicyEngine manages policy registration, parsing, and dynamic rule matching
type PolicyEngine struct {
mu sync.RWMutex
policies map[string]*SafetyPolicy
byName map[string]*SafetyPolicy
client *http.Client
endpoint string
model string
auditor *Auditor
}
func NewPolicyEngine(endpoint, model string) *PolicyEngine {
return &PolicyEngine{
policies: make(map[string]*SafetyPolicy),
byName: make(map[string]*SafetyPolicy),
client: &http.Client{Timeout: 120 * time.Second},
endpoint: endpoint,
model: model,
auditor: NewAuditor("audit.log"),
}
}
// RegisterPolicy registers a natural language policy
func (pe *PolicyEngine) RegisterPolicy(p *SafetyPolicy) error {
pe.mu.Lock()
defer pe.mu.Unlock()
if p.ID == "" {
return fmt.Errorf("policy ID cannot be empty")
}
if p.Query == "" {
return fmt.Errorf("policy query cannot be empty")
}
if p.Threshold <= 0 || p.Threshold >= 1 {
p.Threshold = 0.5
}
if p.Instruct == "" {
p.Instruct = "You are a safety moderator. Apply standard criteria."
}
p.Enabled = true
p.Version = 1
now := time.Now().UTC().Format(time.RFC3339)
p.CreatedAt = now
p.UpdatedAt = now
pe.policies[p.ID] = p
pe.byName[p.Name] = p
detail, _ := json.Marshal(map[string]string{
"policy_id": p.ID, "policy_name": p.Name, "query": p.Query,
})
pe.auditor.Log("policy_create", detail)
log.Printf("[PolicyEngine] Registered policy: %s (%s)", p.Name, p.ID)
return nil
}
// UpdatePolicy updates a policy with automatic versioning
func (pe *PolicyEngine) UpdatePolicy(id string, updates map[string]interface{}) error {
pe.mu.Lock()
defer pe.mu.Unlock()
p, exists := pe.policies[id]
if !exists {
return fmt.Errorf("policy %s not found", id)
}
if q, ok := updates["query"]; ok {
p.Query = q.(string)
}
if t, ok := updates["threshold"]; ok {
p.Threshold = t.(float64)
}
if i, ok := updates["instruct"]; ok {
p.Instruct = i.(string)
}
if e, ok := updates["enabled"]; ok {
p.Enabled = e.(bool)
}
p.Version++
p.UpdatedAt = time.Now().UTC().Format(time.RFC3339)
detail, _ := json.Marshal(map[string]interface{}{
"policy_id": id, "version": p.Version, "updates": updates,
})
pe.auditor.Log("policy_update", detail)
log.Printf("[PolicyEngine] Updated policy: %s (v%d)", p.Name, p.Version)
return nil
}
func (pe *PolicyEngine) GetPolicy(id string) (*SafetyPolicy, error) {
pe.mu.RLock()
defer pe.mu.RUnlock()
p, exists := pe.policies[id]
if !exists {
return nil, fmt.Errorf("policy %s not found", id)
}
return p, nil
}
func (pe *PolicyEngine) GetPolicyByName(name string) (*SafetyPolicy, error) {
pe.mu.RLock()
defer pe.mu.RUnlock()
p, exists := pe.byName[name]
if !exists {
return nil, fmt.Errorf("policy %q not found", name)
}
return p, nil
}
func (pe *PolicyEngine) ListPolicies() []*SafetyPolicy {
pe.mu.RLock()
defer pe.mu.RUnlock()
result := make([]*SafetyPolicy, 0, len(pe.policies))
for _, p := range pe.policies {
result = append(result, p)
}
return result
}
func (pe *PolicyEngine) DeletePolicy(id string) error {
pe.mu.Lock()
defer pe.mu.Unlock()
p, exists := pe.policies[id]
if !exists {
return fmt.Errorf("policy %s not found", id)
}
p.Enabled = false
p.UpdatedAt = time.Now().UTC().Format(time.RFC3339)
detail, _ := json.Marshal(map[string]string{
"policy_id": id, "policy_name": p.Name,
})
pe.auditor.Log("policy_delete", detail)
log.Printf("[PolicyEngine] Disabled policy: %s", p.Name)
return nil
}
// ============================================================
// Dynamic Rule Engine
// ============================================================
type RuleEngine struct {
pe *PolicyEngine
}
type Rule struct {
Name string `json:"name"`
Conditions []Condition `json:"conditions"`
Action string `json:"action"` // "allow", "block", "review"
}
type Condition struct {
PolicyID string `json:"policy_id"`
Operator string `json:"operator"` // "unsafe", "safe", "score_gt", "score_lt"
Value float64 `json:"value,omitempty"`
}
func NewRuleEngine(pe *PolicyEngine) *RuleEngine {
return &RuleEngine{pe: pe}
}
func (re *RuleEngine) EvaluateRule(ctx context.Context, doc string, rule Rule) (*ModerationResult, error) {
var combinedScore float64
triggeredCount := 0
for _, cond := range rule.Conditions {
policy, err := re.pe.GetPolicy(cond.PolicyID)
if err != nil {
return nil, fmt.Errorf("condition references unknown policy %s: %w", cond.PolicyID, err)
}
result, err := re.pe.Moderate(ctx, ModerationRequest{
Document: doc,
PolicyID: policy.ID,
PolicyName: policy.Name,
Modality: "text",
})
if err != nil {
return nil, fmt.Errorf("moderation failed for policy %s: %w", policy.ID, err)
}
matched := false
switch cond.Operator {
case "unsafe":
matched = result.IsUnsafe
case "safe":
matched = !result.IsUnsafe
case "score_gt":
matched = result.Score > cond.Value
case "score_lt":
matched = result.Score < cond.Value
}
if matched {
triggeredCount++
if result.Score > combinedScore {
combinedScore = result.Score
}
}
}
isUnsafe := false
switch rule.Action {
case "block":
isUnsafe = triggeredCount > 0
case "allow":
isUnsafe = false
case "review":
isUnsafe = triggeredCount >= len(rule.Conditions)/2+1
}
return &ModerationResult{
RequestID: generateRequestID(),
PolicyID: rule.Name,
PolicyName: rule.Name,
Score: combinedScore,
IsUnsafe: isUnsafe,
Threshold: 0.5,
Timestamp: time.Now().UTC().Format(time.RFC3339),
}, nil
}
// ============================================================
// Shieldstral API Client
// ============================================================
type shieldstralRequest struct {
Model string `json:"model"`
Messages []shieldstralMessage `json:"messages"`
MaxTokens int `json:"max_tokens"`
Temperature float64 `json:"temperature"`
Logprobs bool `json:"logprobs"`
TopLogprobs int `json:"top_logprobs"`
}
type shieldstralMessage struct {
Role string `json:"role"`
Content interface{} `json:"content"`
}
type shieldstralResponse struct {
Choices []struct {
Logprobs struct {
Content []struct {
TopLogprobs []struct {
Token string `json:"token"`
Logprob float64 `json:"logprob"`
} `json:"top_logprobs"`
} `json:"content"`
} `json:"logprobs"`
} `json:"choices"`
}
func (pe *PolicyEngine) Moderate(ctx context.Context, req ModerationRequest) (*ModerationResult, error) {
start := time.Now()
policy, err := pe.GetPolicy(req.PolicyID)
if err != nil {
if p, err2 := pe.GetPolicyByName(req.PolicyName); err2 == nil {
policy = p
} else {
return nil, fmt.Errorf("policy not found: %v", err)
}
}
if !policy.Enabled {
return nil, fmt.Errorf("policy %s is disabled", policy.ID)
}
systemPrompt := `Judge whether the Document meets the requirements based on the Query and the Instruction provided. Note that the answer can only be "yes" or "no".`
userContent := fmt.Sprintf(
"<Instruct>: %s\n\n<Query>: %s\n\n<Document>: %s",
policy.Instruct, policy.Query, req.Document,
)
payload := shieldstralRequest{
Model: pe.model,
Messages: []shieldstralMessage{
{Role: "system", Content: systemPrompt},
{Role: "user", Content: userContent},
},
MaxTokens: 1,
Temperature: 0.0,
Logprobs: true,
TopLogprobs: 20,
}
body, _ := json.Marshal(payload)
resp, err := pe.client.Post(
pe.endpoint+"/v1/chat/completions",
"application/json",
bytes.NewReader(body),
)
if err != nil {
return nil, fmt.Errorf("API call failed: %w", err)
}
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
var shieldResp shieldstralResponse
if err := json.Unmarshal(respBody, &shieldResp); err != nil {
return nil, fmt.Errorf("response parse failed: %w", err)
}
if len(shieldResp.Choices) == 0 {
return nil, fmt.Errorf("no choices in response")
}
topLogprobs := shieldResp.Choices[0].Logprobs.Content[0].TopLogprobs
score := computeSafetyScore(topLogprobs)
isUnsafe := score > policy.Threshold
elapsed := time.Since(start).Seconds() * 1000
result := &ModerationResult{
RequestID: generateRequestID(),
PolicyID: policy.ID,
PolicyName: policy.Name,
Score: score,
IsUnsafe: isUnsafe,
Threshold: policy.Threshold,
LatencyMs: elapsed,
Timestamp: time.Now().UTC().Format(time.RFC3339),
}
detail, _ := json.Marshal(result)
pe.auditor.Log("moderate", detail)
return result, nil
}
func computeSafetyScore(topLogprobs []struct {
Token string `json:"token"`
Logprob float64 `json:"logprob"`
}) float64 {
yesTokens := map[string]bool{
"yes": true, "yes.": true, `"yes"`: true, "'yes'": true,
}
noTokens := map[string]bool{
"no": true, "no.": true, `"no"`: true, "'no'": true,
}
zYes, zNo := -10.0, -10.0
for _, tp := range topLogprobs {
clean := strings.ToLower(strings.Trim(tp.Token, " \".'"))
if yesTokens[clean] {
if tp.Logprob > zYes {
zYes = tp.Logprob
}
}
if noTokens[clean] {
if tp.Logprob > zNo {
zNo = tp.Logprob
}
}
}
expYes := math.Exp(zYes)
expNo := math.Exp(zNo)
return expYes / (expYes + expNo)
}
// ============================================================
// Audit Logging System
// ============================================================
type Auditor struct {
mu sync.Mutex
file *os.File
encoder *json.Encoder
logQueue chan AuditLog
stopCh chan struct{}
}
func NewAuditor(logPath string) *Auditor {
f, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
log.Fatalf("Failed to open audit log: %v", err)
}
a := &Auditor{
file: f,
encoder: json.NewEncoder(f),
logQueue: make(chan AuditLog, 1000),
stopCh: make(chan struct{}),
}
go a.processQueue()
return a
}
func (a *Auditor) Log(action string, detail json.RawMessage) {
entry := AuditLog{
ID: generateRequestID(),
RequestID: generateRequestID(),
Action: action,
Detail: detail,
Timestamp: time.Now().UTC().Format(time.RFC3339),
}
select {
case a.logQueue <- entry:
default:
log.Printf("[Auditor] Log queue full, dropping: %s", action)
}
}
func (a *Auditor) processQueue() {
for {
select {
case entry := <-a.logQueue:
a.mu.Lock()
if err := a.encoder.Encode(entry); err != nil {
log.Printf("[Auditor] Write failed: %v", err)
}
a.mu.Unlock()
case <-a.stopCh:
return
}
}
}
func (a *Auditor) Close() {
close(a.stopCh)
a.file.Close()
}
// ============================================================
// HTTP API Server
// ============================================================
type PolicyHTTPServer struct {
engine *PolicyEngine
ruleEngine *RuleEngine
mux *http.ServeMux
}
func NewPolicyHTTPServer(engine *PolicyEngine, ruleEngine *RuleEngine) *PolicyHTTPServer {
s := &PolicyHTTPServer{
engine: engine,
ruleEngine: ruleEngine,
mux: http.NewServeMux(),
}
s.registerRoutes()
return s
}
func (s *PolicyHTTPServer) registerRoutes() {
s.mux.HandleFunc("POST /api/v1/moderate", s.handleModerate)
s.mux.HandleFunc("POST /api/v1/policies", s.handleCreatePolicy)
s.mux.HandleFunc("GET /api/v1/policies", s.handleListPolicies)
s.mux.HandleFunc("GET /api/v1/policies/{id}", s.handleGetPolicy)
s.mux.HandleFunc("PUT /api/v1/policies/{id}", s.handleUpdatePolicy)
s.mux.HandleFunc("DELETE /api/v1/policies/{id}", s.handleDeletePolicy)
s.mux.HandleFunc("GET /api/v1/health", s.handleHealth)
}
func (s *PolicyHTTPServer) handleModerate(w http.ResponseWriter, r *http.Request) {
var req ModerationRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, `{"error":"invalid request"}`, http.StatusBadRequest)
return
}
result, err := s.engine.Moderate(r.Context(), req)
if err != nil {
http.Error(w, fmt.Sprintf(`{"error":"%s"}`, err.Error()), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(result)
}
func (s *PolicyHTTPServer) handleCreatePolicy(w http.ResponseWriter, r *http.Request) {
var policy SafetyPolicy
if err := json.NewDecoder(r.Body).Decode(&policy); err != nil {
http.Error(w, `{"error":"invalid policy"}`, http.StatusBadRequest)
return
}
if err := s.engine.RegisterPolicy(&policy); err != nil {
http.Error(w, fmt.Sprintf(`{"error":"%s"}`, err.Error()), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(policy)
}
func (s *PolicyHTTPServer) handleListPolicies(w http.ResponseWriter, r *http.Request) {
policies := s.engine.ListPolicies()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(policies)
}
func (s *PolicyHTTPServer) handleGetPolicy(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
policy, err := s.engine.GetPolicy(id)
if err != nil {
http.Error(w, `{"error":"not found"}`, http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(policy)
}
func (s *PolicyHTTPServer) handleUpdatePolicy(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
var updates map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&updates); err != nil {
http.Error(w, `{"error":"invalid updates"}`, http.StatusBadRequest)
return
}
if err := s.engine.UpdatePolicy(id, updates); err != nil {
http.Error(w, fmt.Sprintf(`{"error":"%s"}`, err.Error()), http.StatusNotFound)
return
}
policy, _ := s.engine.GetPolicy(id)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(policy)
}
func (s *PolicyHTTPServer) handleDeletePolicy(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
if err := s.engine.DeletePolicy(id); err != nil {
http.Error(w, fmt.Sprintf(`{"error":"%s"}`, err.Error()), http.StatusNotFound)
return
}
w.WriteHeader(http.StatusNoContent)
}
func (s *PolicyHTTPServer) handleHealth(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"status": "ok",
"time": time.Now().UTC().Format(time.RFC3339),
})
}
func (s *PolicyHTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.mux.ServeHTTP(w, r)
}
// ============================================================
// Helpers
// ============================================================
func generateRequestID() string {
h := sha256.New()
h.Write([]byte(time.Now().UTC().String()))
h.Write([]byte(fmt.Sprintf("%d", time.Now().UnixNano())))
return hex.EncodeToString(h.Sum(nil))[:16]
}
// ============================================================
// Main
// ============================================================
func main() {
engine := NewPolicyEngine(
"http://localhost:8000",
"mistralai/Shieldstral-1.0-3B",
)
ruleEngine := NewRuleEngine(engine)
// Register example policies
engine.RegisterPolicy(&SafetyPolicy{
ID: "pol-violence-001",
Name: "violence_detection",
Query: "Does this content promote physical violence?",
Instruct: "You are a strict safety moderator. Apply a low tolerance threshold.",
Threshold: 0.5,
})
engine.RegisterPolicy(&SafetyPolicy{
ID: "pol-hate-002",
Name: "hate_speech_detection",
Query: "Does this content contain hate speech targeting a protected group?",
Instruct: "You are a strict safety moderator. Apply a low tolerance threshold.",
Threshold: 0.4,
})
engine.RegisterPolicy(&SafetyPolicy{
ID: "pol-child-003",
Name: "child_safety",
Query: "Is this content safe for children under 12?",
Instruct: "You are a strict safety reviewer for children's content. Apply a very low tolerance threshold.",
Threshold: 0.3,
})
server := NewPolicyHTTPServer(engine, ruleEngine)
addr := ":8080"
log.Printf("[Server] Starting policy management API on %s", addr)
log.Fatal(http.ListenAndServe(addr, server))
}
7. Benchmark Performance and Comparative Analysis
7.1 Text Safety Benchmarks
Shieldstral’s performance across text safety benchmarks (F1 scores):
| Benchmark | Shieldstral-3B | GPT-OSS-20B | Qwen3Guard-8B | LlamaGuard-4-12B |
|---|---|---|---|---|
| WildGuardTest (Prompt) | 88.1 | 87.3 | 88.2 | 74.3 |
| ToxicChat | 84.1 | 79.8 | 75.6 | 51.0 |
| Aegis v2 (Prompt) | 86.2 | 84.4 | 84.6 | 71.5 |
| HarmBench (Prompt) | 99.4 | 94.5 | 99.3 | 97.9 |
| HarmBench (Response) | 87.0 | 88.2 | 86.8 | 82.8 |
| Aegis v2 (Response) | 87.2 | 75.2 | 86.2 | 64.7 |
Average F1: 84.9%, matching GPT-OSS-Safeguard-20B.
7.2 Multimodal Safety Benchmarks
| Benchmark | Shieldstral-3B | OmniGuard-7B | LlavaGuard-7B |
|---|---|---|---|
| VLGuard | 97.7 | 88.5 | 69.5 |
| UnsafeBench | 81.8 | 72.6 | 63.9 |
| LlavaGuard | 72.0 | 71.7 | 81.4 |
Average F1: 83.8%, 6.2 points ahead of OmniGuard-7B.
7.3 Policy Adaptability Benchmark
In the purpose-built policy adaptability evaluation (using 52 fine-grained categories entirely different from the training set), Shieldstral achieves 91.3% F1, compared to GPT-OSS-Safeguard-20B’s 94.1%. However, GPT-OSS-Safeguard-20B requires generating long reasoning chains before answering, making its computational cost significantly higher than Shieldstral’s single forward pass.
7.4 Ablation: The Critical Role of Synthetic Data
| Training Stage | Accuracy | Precision | Recall | F1 |
|---|---|---|---|---|
| Base model (no safety training) | 37.8% | 0.0 | 0.0 | 0.0 |
| + Public data only (P checkpoint) | 62.7% | 90.8 | 46.0 | 61.1 |
| + Synthetic taxonomy data (PG checkpoint) | 77.6% | 75.9 | 95.0 | 84.4 |
Public data alone buys high precision (90.8) but poor recall (46.0), meaning the model severely under-flags novel policy violations. Adding synthetic policy discrimination data produces a 49-point recall improvement and a 23.3-point F1 gain — exactly the behavior a policy-adaptive model needs: generalization to categories never seen during training.
8. Limitations
Mistral’s technical report honestly acknowledges the following limitations:
- Uneven language coverage: Prompt classification performance on low-resource languages (Arabic, Indonesian) notably trails several baselines
- Adversarial input fragility: Encoded/transliterated text and very long documents can reduce reliability
- Residual label noise: Despite multi-model verification and consistency filtering, the synthetic and public safety data carry some bias and noise
- No independent replication yet: All benchmark numbers are vendor-reported, with no third-party reproduction available as of publication
9. Conclusion and Outlook
Shieldstral’s release marks the entry of AI content moderation into the natural language programmable paradigm. Its core contributions are:
- Architectural innovation: Decoupling moderation policies from model weights, injecting them as natural language questions at inference time
- Data methodology: Training policy discrimination capability through 54M+ contrastive samples, rather than simple category memorization
- Engineering efficiency: 3B parameters running on a single 16GB GPU, fully open-source under Apache 2.0
Mistral’s roadmap points to expanded multilingual coverage, long-document robustness, and broader multimodal safety capabilities. As an inaugural member of the Open Secure AI Alliance (alongside NVIDIA and others), Shieldstral’s open-source release may accelerate the industry-wide shift from “closed API moderation” toward “locally auditable, policy-programmable” open safety architectures.
References:
- Mistral AI. “Introducing Shieldstral.” https://mistral.ai/news/shieldstral/ (2026-08-04)
- Calvi, A. et al. “Shieldstral.” arXiv:2607.25857 (2026)
- Hugging Face Model Card: mistralai/Shieldstral-1.0-3B. https://huggingface.co/mistralai/Shieldstral-1.0-3B
- Mistral Docs: Shieldstral 1.0 Model Card. https://docs.mistral.ai/models/model-cards/shieldstral-1-0
- IT之家. “Mistral launches Shieldstral, the most powerful multimodal content moderation open-source AI model.” (2026-08-05)
- The Decoder. “Mistral’s open model Shieldstral matches much larger safety models.” (2026-08-05)
- Unite.AI. “Mistral’s Shieldstral Packs Policy-Adaptive Safety Screening Into 3B Parameters.” (2026-08-04)
- 4sysops. “Mistral’s Shieldstral ties a 20B safety model with just 3B parameters.” (2026-08-05)
- Mer.Vin. “Shieldstral: Mistral’s 3B Open-Weights Moderation Model.” (2026-08-05)