Aller au contenu principal

Modération et sécurité du contenu visuel

Mis à jour le 29 juillet 2026

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 fonction ci-dessous intercepte BadRequestError, inspecte le message à la recherche des marqueurs safety ou content_policy, et ne relaie l’exception que si elle ne reconnaît pas 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-1",
            prompt=prompt,
            size="1024x1024",
            quality="medium",
            response_format="b64_json"
        )

        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 "safety" in str(e).lower() or "content_policy" in str(e).lower():
            return {
                "status": "refuse",
                "raison": "Contenu refusé par la politique de sécurité",
                "details": str(e)
            }
        raise


resultat = generer_avec_moderation(
    "Un chaton jouant avec une pelote de laine",
    "chaton.png"
)
print(resultat)

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-1",
        prompt=prompt,
        size="1024x1024",
        quality="medium",
        response_format="b64_json"
    )

    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.