Aller au contenu principal

Patterns complexes : listes imbriquées, enums, unions

Patterns complexes : listes imbriquées, enums, unions

Les cas d’extraction simples (un objet plat avec des champs texte) sont faciles à gérer. Les vrais défis arrivent avec les structures complexes : listes d’objets imbriqués, types conditionnels, champs polymorphiques. Cette leçon vous montre comment concevoir des schémas robustes pour ces cas avancés.

Listes imbriquées

Le pattern le plus courant en production : extraire une liste d’objets dont chacun contient lui-même des sous-listes.

import json
from openai import OpenAI

client = OpenAI()

# Schéma : extraire un programme de formation avec modules et leçons
formation_schema = {
    "type": "object",
    "properties": {
        "titre": {"type": "string"},
        "duree_totale_heures": {"type": "number"},
        "modules": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "titre_module": {"type": "string"},
                    "objectifs": {
                        "type": "array",
                        "items": {"type": "string"}
                    },
                    "lecons": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "titre_lecon": {"type": "string"},
                                "duree_minutes": {"type": "integer"},
                                "type": {
                                    "type": "string",
                                    "enum": ["theorie", "pratique",
                                             "evaluation"]
                                }
                            },
                            "required": ["titre_lecon",
                                         "duree_minutes", "type"],
                            "additionalProperties": False
                        }
                    }
                },
                "required": ["titre_module", "objectifs", "lecons"],
                "additionalProperties": False
            }
        }
    },
    "required": ["titre", "duree_totale_heures", "modules"],
    "additionalProperties": False
}

Enums pour contraindre les valeurs

Les enums sont le moyen le plus fiable de limiter les valeurs possibles d’un champ. Le Structured Output les applique au niveau de la génération :

# Système de classification multi-niveaux
classification_schema = {
    "type": "object",
    "properties": {
        "categorie_principale": {
            "type": "string",
            "enum": ["technique", "commercial", "juridique",
                     "rh", "finance"]
        },
        "sous_categorie": {
            "type": "string",
            "enum": [
                "bug", "feature", "performance",
                "devis", "facturation", "contrat",
                "recrutement", "conge", "formation",
                "budget", "audit", "conformite"
            ]
        },
        "priorite": {
            "type": "string",
            "enum": ["critique", "haute", "moyenne", "basse"]
        },
        "action_requise": {
            "type": "string",
            "enum": ["repondre", "escalader", "archiver",
                     "planifier", "deleguer"]
        }
    },
    "required": ["categorie_principale", "sous_categorie",
                  "priorite", "action_requise"],
    "additionalProperties": False
}

Simuler les unions de types

JSON Schema supporte anyOf pour les types conditionnels, mais le Structured Output strict d’OpenAI a des limitations. Le pattern recommandé est d’utiliser un champ discriminant :

# Pattern : un événement peut être une réunion, une tâche ou un rappel
event_schema = {
    "type": "object",
    "properties": {
        "type": {
            "type": "string",
            "enum": ["reunion", "tache", "rappel"]
        },
        "titre": {"type": "string"},
        "date": {"type": "string"},
        "heure_debut": {"type": ["string", "null"]},
        "heure_fin": {"type": ["string", "null"]},
        "participants": {
            "type": "array",
            "items": {"type": "string"}
        },
        "lieu": {"type": ["string", "null"]},
        "priorite": {
            "type": ["string", "null"],
            "enum": ["haute", "moyenne", "basse", None]
        },
        "description": {"type": "string"}
    },
    "required": ["type", "titre", "date", "heure_debut",
                  "heure_fin", "participants", "lieu",
                  "priorite", "description"],
    "additionalProperties": False
}

L’astuce : rendez les champs spécifiques à un type nullable ("type": ["string", "null"]). Pour une réunion, participants et lieu sont remplis ; pour un rappel, ils sont null.

Objets récursifs

Pour les structures arborescentes (menus, organigrammes, fils de discussion), utilisez $defs pour les références récursives :

# Structure d'un commentaire avec réponses imbriquées
comment_schema = {
    "type": "object",
    "properties": {
        "commentaires": {
            "type": "array",
            "items": {"$ref": "#/$defs/comment"}
        }
    },
    "required": ["commentaires"],
    "additionalProperties": False,
    "$defs": {
        "comment": {
            "type": "object",
            "properties": {
                "auteur": {"type": "string"},
                "contenu": {"type": "string"},
                "sentiment": {
                    "type": "string",
                    "enum": ["positif", "negatif", "neutre"]
                },
                "reponses": {
                    "type": "array",
                    "items": {"$ref": "#/$defs/comment"}
                }
            },
            "required": ["auteur", "contenu", "sentiment",
                          "reponses"],
            "additionalProperties": False
        }
    }
}

Pattern : extraction multi-entités

Quand un texte contient plusieurs types d’entités :

multi_entity_schema = {
    "type": "object",
    "properties": {
        "personnes": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "nom": {"type": "string"},
                    "role": {"type": "string"},
                    "organisation": {"type": ["string", "null"]}
                },
                "required": ["nom", "role", "organisation"],
                "additionalProperties": False
            }
        },
        "organisations": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "nom": {"type": "string"},
                    "type": {
                        "type": "string",
                        "enum": ["entreprise", "association",
                                 "administration", "autre"]
                    },
                    "secteur": {"type": ["string", "null"]}
                },
                "required": ["nom", "type", "secteur"],
                "additionalProperties": False
            }
        },
        "dates": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "date_brute": {"type": "string"},
                    "date_iso": {"type": "string"},
                    "contexte": {"type": "string"}
                },
                "required": ["date_brute", "date_iso", "contexte"],
                "additionalProperties": False
            }
        },
        "montants": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "valeur": {"type": "number"},
                    "devise": {"type": "string"},
                    "contexte": {"type": "string"}
                },
                "required": ["valeur", "devise", "contexte"],
                "additionalProperties": False
            }
        }
    },
    "required": ["personnes", "organisations", "dates", "montants"],
    "additionalProperties": False
}

Bonnes pratiques pour les schémas complexes

  1. Commencez simple, complexifiez progressivement : testez d’abord un objet plat, puis ajoutez les imbrications
  2. Utilisez les nullable plutôt que les optionnels : avec strict: true, tous les champs sont requis — utilisez "type": ["string", "null"] pour les champs facultatifs
  3. Limitez la profondeur : au-delà de 3-4 niveaux d’imbrication, la qualité baisse
  4. Guidez avec le system prompt : les enums et le schéma contraignent la structure, le system prompt guide le contenu

Mise en pratique

  1. Définissez un schéma pour extraire un organigramme d’entreprise (structure récursive)
  2. Testez avec un texte décrivant une organisation de 15-20 personnes
  3. Vérifiez que les relations hiérarchiques sont correctement imbriquées
  4. Ajoutez des enums pour les rôles (direction, management, opérationnel)

Points clés à retenir

  • Les listes imbriquées sont le pattern le plus courant en production
  • Les enums sont appliqués au niveau de la génération — exploitez-les
  • Simulez les unions avec un champ discriminant et des champs nullable
  • $defs permet les structures récursives (commentaires, arbres)
  • Limitez la profondeur d’imbrication à 3-4 niveaux