Modération et sécurité du contenu visuel
Sur cette page
- Modération et sécurité du contenu visuel
- Le système de modération intégré
- Pré-filtrer les prompts
- Contrôler aussi les images produites
- Quand ce sont vos utilisateurs qui écrivent les prompts
- Tracer pour pouvoir rendre des comptes
- Droit d’auteur et marques
- Conformité RGPD
- Avant d’ouvrir les vannes
- Testez vos connaissances
Corrigé le 3 novembre 2026 d’après le guide Image generation d’OpenAI : un refus de modération se reconnaît à
error.code == "moderation_blocked", avec un objetmoderation_detailsfacultatif, et non à la présence des motssafetyoucontent_policydans le message d’erreur ; ajout du paramètremoderation(auto,low). Source : Image generation.
Mise à jour du 2 novembre 2026 — modèles d’image.
gpt-image-1, sur lequel ce cours avait été écrit, est arrêté depuis le 23 octobre 2026, etgpt-image-1.5,gpt-image-1-minietchatgpt-image-latestseront retirés le 1er décembre 2026. Les exemples utilisent désormaisgpt-image-2.5-sunburst, le plus précis en édition ;gpt-image-2.5-flareest plus rapide pour la génération courante. Les deux acceptent en plus les qualitésxhighetmax. Les modèles GPT Image renvoient toujours l’image en base64 : le paramètreresponse_formatdes anciens modèles DALL·E n’est pas accepté et a été retiré du code. Source : Image generation (OpenAI).
Modération et sécurité du contenu visuel
Tant que vous générez des images pour vous-même, la question de la modération se règle par votre propre jugement. Dès que vous ouvrez la génération à des utilisateurs, elle devient une responsabilité légale et éthique qui vous engage. Cette leçon couvre les outils de modération d’OpenAI, la manière de les enchaîner en filtres successifs, et les dispositions qui protègent à la fois votre application et les personnes qui s’en servent.
Le système de modération intégré
GPT Image refuse de lui-même certains contenus — violence, contenu sexuel explicite, entre autres — et signale ce refus par une erreur spécifique. Votre code doit donc distinguer un refus de politique de contenu d’une panne technique, car les deux appellent des réactions opposées : le premier se traduit par un message à l’utilisateur, le second par une alerte à votre équipe. La documentation d’OpenAI désigne le discriminant à utiliser : un refus de modération arrive en BadRequestError avec error.code égal à moderation_blocked (type image_generation_user_error), et le corps de l’erreur peut contenir un objet moderation_details qui précise l’étape (input, output ou unknown) et des catégories générales (harassment, sexual, violence…). Ne cherchez pas des mots dans le message d’erreur : son texte n’est pas un contrat, le code l’est. La fonction ci-dessous ne relaie l’exception que s’il ne s’agit pas d’un refus de modération.
from openai import OpenAI, BadRequestError
client = OpenAI()
def generer_avec_moderation(prompt: str, output: str) -> dict:
"""Génère une image en gérant les refus de modération."""
try:
response = client.images.generate(
model="gpt-image-2.5-sunburst",
prompt=prompt,
size="1024x1024",
quality="medium",
)
import base64
data = base64.b64decode(response.data[0].b64_json)
with open(output, "wb") as f:
f.write(data)
return {"status": "ok", "path": output}
except BadRequestError as e:
if e.code != "moderation_blocked":
raise # panne technique, quota, requête invalide : à traiter ailleurs
corps = e.body if isinstance(e.body, dict) else {}
details = corps.get("moderation_details") or {}
return {
"status": "refuse",
"raison": "Contenu refusé par la politique de sécurité",
"etape": details.get("moderation_stage"), # input, output ou unknown
"categories": details.get("categories") or [], # pour vos journaux
}
resultat = generer_avec_moderation(
"Un chaton jouant avec une pelote de laine",
"chaton.png"
)
print(resultat)
OpenAI conseille de garder un message générique pour l’utilisateur final et de réserver moderation_details à vos journaux et à votre support. Ces erreurs ne se rejouent pas telles quelles : sans modifier le prompt ou les images fournies, la même requête sera refusée de nouveau. Notez enfin que la sévérité du filtrage se règle par le paramètre moderation des modèles GPT Image : auto (filtrage standard, par défaut) ou low (moins restrictif).
Pré-filtrer les prompts
Attendre le refus de GPT Image pour découvrir qu’un prompt posait problème n’est pas satisfaisant : vous avez déjà consommé l’appel, et vous n’apprenez rien de précis sur la raison. L’API de modération, elle, est gratuite, rapide, et vous rend des catégories nommées. L’appeler avant la génération vous permet de refuser proprement, d’expliquer à l’utilisateur ce qui a été signalé, et de garder cette information pour votre audit. La fonction verifier_prompt extrait la liste des catégories déclenchées, et generer_image_securisee enchaîne simplement les deux étapes : contrôle d’abord, génération ensuite.
from openai import OpenAI
client = OpenAI()
def verifier_prompt(prompt: str) -> dict:
"""Vérifie un prompt avec l'API de modération avant génération."""
moderation = client.moderations.create(
model="omni-moderation-latest",
input=prompt
)
result = moderation.results[0]
if result.flagged:
categories_flagged = [
cat for cat, flagged in result.categories.__dict__.items()
if flagged
]
return {
"autorise": False,
"categories": categories_flagged,
"message": "Ce prompt a été signalé par le système de modération."
}
return {"autorise": True}
def generer_image_securisee(prompt: str, output: str):
"""Pipeline complet : modération + génération."""
# Étape 1 : pré-filtrage
check = verifier_prompt(prompt)
if not check["autorise"]:
print(f"Prompt refusé : {check['message']}")
print(f"Catégories : {check['categories']}")
return None
# Étape 2 : génération
print("Prompt validé, génération en cours...")
return generer_avec_moderation(prompt, output)
# Test avec un prompt inoffensif
generer_image_securisee(
"Un paysage de montagne avec un lac cristallin",
"montagne.png"
)
Contrôler aussi les images produites
Un prompt anodin peut donner une image qui ne l’est pas. La formulation détournée, l’ambiguïté d’un terme, ou simplement une interprétation malheureuse du modèle suffisent à produire un visuel que vous ne voudriez pas voir affiché sous votre logo. Le modèle omni-moderation-latest accepte des images en entrée, ce qui vous permet de refermer la boucle : la même mécanique de catégories s’applique au fichier généré. Le pipeline complet ci-dessous enchaîne les trois temps — contrôle du prompt, génération, contrôle de l’image — et surtout, en cas de signalement, supprime le fichier du disque avant de retourner son verdict. Une image problématique qui reste sur le serveur « le temps qu’on regarde » est une image qui finira par fuiter.
from openai import OpenAI
import base64
client = OpenAI()
def moderer_image_generee(image_path: str) -> dict:
"""Analyse une image générée pour détecter du contenu problématique."""
with open(image_path, "rb") as f:
image_b64 = base64.b64encode(f.read()).decode()
moderation = client.moderations.create(
model="omni-moderation-latest",
input=[
{
"type": "image_url",
"image_url": {
"url": f"data:image/png;base64,{image_b64}"
}
}
]
)
result = moderation.results[0]
if result.flagged:
categories_flagged = [
cat for cat, flagged in result.categories.__dict__.items()
if flagged
]
return {
"safe": False,
"categories": categories_flagged
}
return {"safe": True}
def pipeline_securise(prompt: str, output: str):
"""Pipeline complet : modération prompt + génération + modération image."""
# 1. Vérifier le prompt
check_prompt = verifier_prompt(prompt)
if not check_prompt["autorise"]:
return {"status": "prompt_refuse", "details": check_prompt}
# 2. Générer l'image
response = client.images.generate(
model="gpt-image-2.5-sunburst",
prompt=prompt,
size="1024x1024",
quality="medium",
)
data = base64.b64decode(response.data[0].b64_json)
with open(output, "wb") as f:
f.write(data)
# 3. Vérifier l'image générée
check_image = moderer_image_generee(output)
if not check_image["safe"]:
import os
os.remove(output)
return {
"status": "image_supprimee",
"raison": "L'image générée a été signalée",
"categories": check_image["categories"]
}
return {"status": "ok", "path": output}
Quand ce sont vos utilisateurs qui écrivent les prompts
Le contenu généré par les utilisateurs change la nature du problème : vous ne faites plus face à des maladresses mais, parfois, à des tentatives délibérées de contournement. La classe ModerateurPrompt empile quatre défenses de nature différente, ce qui est le principe même d’une protection sérieuse. Le nettoyage retire les caractères de contrôle par lesquels transitent les injections et plafonne la longueur à cinq cents caractères, ce qui coupe court aux prompts-fleuves conçus pour noyer les consignes. La vérification locale par liste de mots interdits — laissée vide, à remplir selon votre politique et votre secteur — intercepte à coût nul ce que vous connaissez déjà. La modération API prend ensuite le relais sur tout ce que votre liste n’anticipait pas. Enfin, le préfixe de sécurité concaténé devant le prompt final rappelle explicitement au modèle le cadre attendu, ce qui déplace la frontière en votre faveur sur les cas limites.
from openai import OpenAI
import re
client = OpenAI()
class ModerateurPrompt:
"""Filtre multicouche pour les prompts utilisateur."""
def __init__(self):
self.mots_interdits = [] # À remplir selon votre politique
self.prefixe_securite = (
"Image sûre et appropriée pour tout public. "
"Pas de contenu violent, sexuel ou discriminatoire. "
)
def nettoyer(self, prompt: str) -> str:
"""Nettoie et normalise un prompt utilisateur."""
# Supprimer les injections potentielles
prompt = prompt.strip()
# Limiter la longueur
prompt = prompt[:500]
# Supprimer les caractères de contrôle
prompt = re.sub(r'[\x00-\x1f\x7f-\x9f]', '', prompt)
return prompt
def verifier_mots(self, prompt: str) -> bool:
"""Vérifie l'absence de mots interdits."""
prompt_lower = prompt.lower()
for mot in self.mots_interdits:
if mot in prompt_lower:
return False
return True
def securiser(self, prompt: str) -> str:
"""Ajoute un préfixe de sécurité au prompt."""
return f"{self.prefixe_securite}{prompt}"
def pipeline(self, prompt_brut: str) -> dict:
"""Pipeline complet de modération d'un prompt utilisateur."""
# Étape 1 : nettoyage
prompt = self.nettoyer(prompt_brut)
# Étape 2 : vérification locale
if not self.verifier_mots(prompt):
return {"autorise": False, "raison": "Mot interdit détecté"}
# Étape 3 : modération API
check = verifier_prompt(prompt)
if not check["autorise"]:
return {"autorise": False, "raison": check["message"]}
# Étape 4 : ajout du préfixe de sécurité
prompt_final = self.securiser(prompt)
return {"autorise": True, "prompt": prompt_final}
moderateur = ModerateurPrompt()
# Simuler un prompt utilisateur
resultat = moderateur.pipeline("Un chat tigré dormant sur un coussin rouge")
if resultat["autorise"]:
print(f"Prompt sécurisé : {resultat['prompt']}")
else:
print(f"Refusé : {resultat['raison']}")
Tracer pour pouvoir rendre des comptes
Le jour où une image problématique refait surface, la question qu’on vous posera sera : qui l’a demandée, quand, et qu’a fait votre système. Sans journal, vous n’avez pas de réponse. AuditLogger écrit une ligne JSON par génération dans un fichier .jsonl — un format append-only qui supporte l’écriture concurrente et se relit ligne par ligne sans charger l’ensemble en mémoire. Chaque entrée relie un identifiant utilisateur à un prompt tronqué, à l’issue de la génération et, le cas échéant, à la raison du refus. La méthode rapport en tire ensuite le taux d’acceptation sur les cent dernières générations et détaille les refus : c’est le tableau de bord qui vous alertera si un compte particulier accumule les signalements.
import json
import os
from datetime import datetime
class AuditLogger:
"""Journal d'audit pour les générations d'images."""
def __init__(self, log_file: str = "audit_images.jsonl"):
self.log_file = log_file
def enregistrer(
self,
prompt: str,
user_id: str,
resultat: str,
image_path: str = None,
raison_refus: str = None
):
"""Enregistre une entrée d'audit."""
entry = {
"timestamp": datetime.now().isoformat(),
"user_id": user_id,
"prompt": prompt[:200],
"résultat": resultat, # "ok", "prompt_refuse", "image_supprimee"
"image_path": image_path,
"raison_refus": raison_refus,
}
with open(self.log_file, "a") as f:
f.write(json.dumps(entry, ensure_ascii=False) + "\n")
def rapport(self, derniers_n: int = 100):
"""Génère un rapport des dernières générations."""
if not os.path.exists(self.log_file):
print("Aucun log trouvé.")
return
entries = []
with open(self.log_file, "r") as f:
for line in f:
entries.append(json.loads(line))
recent = entries[-derniers_n:]
total = len(recent)
ok = sum(1 for e in recent if e["résultat"] == "ok")
refuses = sum(1 for e in recent if e["résultat"] != "ok")
print(f"Rapport des {total} dernières générations :")
print(f" Acceptées : {ok} ({100*ok/total:.1f}%)")
print(f" Refusées : {refuses} ({100*refuses/total:.1f}%)")
if refuses > 0:
print(f"\nDétail des refus :")
for e in recent:
if e["résultat"] != "ok":
print(f" [{e['timestamp'][:10]}] {e['user_id']} : {e['raison_refus']}")
logger = AuditLogger()
logger.rapport()
Droit d’auteur et marques
GPT Image sait produire des images qui évoquent le style d’un artiste ou l’identité visuelle d’une marque, et cette capacité est exactement ce qui vous expose. La modération technique ne vous protège pas sur ce terrain-là : elle ne détecte pas qu’une affiche ressemble beaucoup à celle d’un concurrent. La protection est donc contractuelle et organisationnelle, et elle prend la forme d’une politique d’usage écrite, opposable, que vos utilisateurs acceptent et que votre équipe applique. Le bloc REGLES_PRODUCTION en donne la formulation minimale, à afficher dans votre documentation autant qu’à rappeler dans vos conditions.
# Bonnes pratiques à intégrer dans votre politique d'usage
REGLES_PRODUCTION = """
1. Ne pas demander d'imiter le style d'un artiste vivant nommément
2. Ne pas reproduire de logos ou marques déposées
3. Ne pas générer de visages de personnes réelles
4. Ajouter une mention "Image générée par IA" quand c'est requis
5. Conserver les logs de génération pendant 12 mois minimum
6. Permettre aux utilisateurs de signaler du contenu problématique
"""
def avertissement_usage():
"""Affiche les règles d'usage en production."""
print("=" * 60)
print("RÈGLES DE PRODUCTION — Génération d'images IA")
print("=" * 60)
print(REGLES_PRODUCTION)
Conformité RGPD
Pour une entreprise européenne, les durées de conservation et le consentement ne sont pas des options de configuration mais des obligations dont il faut pouvoir démontrer le respect. Les inscrire dans un objet de configuration explicite, plutôt que de les disperser dans le code, vous donne un endroit unique à montrer lors d’un audit. ConfigConformite porte ainsi la rétention des journaux à un an, celle des images à quatre-vingt-dix jours, et rend obligatoires la mention IA et le consentement. La méthode ajouter_metadata_ia mérite une attention particulière : elle inscrit dans le PNG lui-même, via PngInfo, le fait que l’image est générée, l’outil qui l’a produite et la date. Ces métadonnées voyagent avec le fichier, y compris hors de votre plateforme, ce qu’aucune mention affichée sur une page web ne fait.
class ConfigConformite:
"""Configuration de conformité pour la génération d'images."""
def __init__(self):
self.retention_logs_jours = 365
self.retention_images_jours = 90
self.mention_ia_obligatoire = True
self.consentement_requis = True
def verifier_consentement(self, user_id: str) -> bool:
"""Vérifie que l'utilisateur a consenti à la génération IA."""
# À implémenter avec votre système de gestion des consentements
return True
def ajouter_metadata_ia(self, image_path: str):
"""Ajoute des métadonnées indiquant la génération par IA."""
from PIL import Image
from PIL.PngImagePlugin import PngInfo
img = Image.open(image_path)
metadata = PngInfo()
metadata.add_text("AI-Generated", "true")
metadata.add_text("Generator", "GPT Image via OpenAI API")
metadata.add_text("Date", datetime.now().isoformat())
img.save(image_path, pnginfo=metadata)
print(f"Métadonnées IA ajoutées : {image_path}")
Avant d’ouvrir les vannes
Huit dispositions doivent être en place le jour où votre système devient accessible, et il est plus honnête de se les poser avant qu’après. La modération d’abord, dans ses deux temps : pré-filtrage des prompts et post-vérification des images, comme le fait pipeline_securise. Le journal ensuite, complet, associant chaque génération à son prompt, son auteur et son issue — c’est votre seule preuve en cas de litige, et il doit être sauvegardé indépendamment des images elles-mêmes, sans quoi une purge de stockage emporterait votre traçabilité.
Vient le volet utilisateur. Une limitation de débit par compte empêche qu’un seul abonné consomme votre budget mensuel en une nuit, volontairement ou par script mal écrit. L’information sur le caractère généré des images relève à la fois de la loyauté et, de plus en plus, de la contrainte réglementaire ; elle se double d’un marquage technique dans les métadonnées PNG ou EXIF du fichier. Le droit à l’effacement suppose que vous puissiez retrouver et supprimer l’ensemble des données d’un utilisateur sur simple demande, ce qui n’est possible que si vos entrées d’audit portent bien un user_id. Enfin, ouvrez un canal de signalement : vos utilisateurs verront toujours avant vous ce qui pose problème, et leur retirer le moyen de vous le dire ne fait pas disparaître le problème.
Rassemblez maintenant l’ensemble en un service unique de génération d’images : pré-filtrage des prompts par modération API et liste de mots, préfixe de sécurité, post-vérification de l’image produite, journal d’audit et métadonnées IA sur chaque fichier. Éprouvez-le avec cinq prompts variés, dont au moins un qui doit être refusé — c’est le seul moyen de vérifier que vos garde-fous fonctionnent vraiment plutôt que d’être simplement présents dans le code.
Testez vos connaissances
Génération visuelle par l’API : validez les réflexes avant la production.
1. Que permettent l'inpainting et l'outpainting ?
Réponse : L’inpainting modifie une zone précise d’une image existante (remplacer, corriger) ; l’outpainting étend l’image au-delà de son cadre d’origine — deux éditions ciblées sans tout regénérer.
2. Qu'est-ce qui fait un bon prompt visuel avancé ?
Réponse : Décrire sujet, composition, éclairage et perspective — pas seulement le thème : plus le prompt cadre la scène comme un directeur photo, plus l’image obéit.
3. Comment travailler efficacement par itérations ?
Réponse : Partir d’une génération de base, puis demander des variations ciblées (style, cadrage, détail) plutôt que réécrire le prompt de zéro — on garde ce qui marche, on ne retouche que l’écart.
4. Quels leviers pour optimiser les coûts de génération ?
Réponse : Adapter qualité et résolution au besoin réel (brouillons en basse, finaux en haute), limiter les itérations par de meilleurs prompts, et mettre en cache/réutiliser les visuels validés.
5. Pourquoi la modération du contenu visuel est-elle indispensable ?
Réponse : Parce que l’API et vos conditions imposent des limites (contenus interdits, personnes réelles, marques) : filtrer les prompts et contrôler les sorties protège vos utilisateurs et votre application.
Cadrer, itérer, maîtriser coûts et conformité : la production visuelle est un pipeline — pas un coup de dés.