Aller au contenu principal

Modération et sécurité du contenu visuel

Modération et sécurité du contenu visuel

Déployer la génération d’images en production implique des responsabilités légales et éthiques. Cette leçon couvre les outils de modération d’OpenAI, les bonnes pratiques de filtrage, et les stratégies pour protéger votre application et vos utilisateurs.

Le système de modération intégré

GPT Image refuse automatiquement de générer certains contenus (violence, contenu sexuel explicite, etc.). Quand un prompt est refusé, l’API renvoie une erreur spécifique :

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é-filtrage des prompts utilisateur

Avant d’envoyer un prompt à GPT Image, filtrez-le avec l’API de modération d’OpenAI :

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"
)

Modération des images générées

Vérifiez aussi les images après génération, car le modèle peut parfois produire du contenu inattendu :

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}

Gestion des prompts utilisateurs (UGC)

Quand vos utilisateurs fournissent les prompts, ajoutez des couches de protection supplémentaires :

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']}")

Logging et audit

En production, conservez un journal de toutes les générations pour audit :

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],
            "resultat": 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["resultat"] == "ok")
        refuses = sum(1 for e in recent if e["resultat"] != "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["resultat"] != "ok":
                    print(f"  [{e['timestamp'][:10]}] {e['user_id']} : {e['raison_refus']}")


logger = AuditLogger()
logger.rapport()

Respect du droit d’auteur et des marques

GPT Image peut générer des images ressemblant à des styles artistiques ou des marques existantes. En production, protégez-vous :

# 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)

Configuration RGPD et conformité

Pour les entreprises européennes, respectez le RGPD :

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}")

Checklist de mise en production

Avant de déployer votre système de génération d’images :

  • Modération : pré-filtrage des prompts + post-vérification des images
  • Logging : journal complet de toutes les générations avec prompts et résultats
  • Rate limiting : limiter le nombre de générations par utilisateur
  • Consentement : informer les utilisateurs que les images sont générées par IA
  • Métadonnées : marquer les images générées (EXIF ou métadonnées PNG)
  • Droit à l’effacement : pouvoir supprimer les données d’un utilisateur sur demande
  • Signalement : permettre aux utilisateurs de signaler du contenu problématique
  • Backup : sauvegarder les logs d’audit indépendamment des images

Exercice pratique

Créez un service complet de génération d’images qui intègre : le pré-filtrage des prompts (modération API + liste de mots), le préfixe de sécurité, la post-vérification des images, le logging d’audit, et l’ajout de métadonnées IA sur chaque image. Testez-le avec 5 prompts variés (dont au moins un qui devrait être refusé).