Image de couverture : Laravel + OpenAI : Pourquoi passer au Strict JSON Schema pour fiabiliser vos pipelines IA
tech

Laravel + OpenAI : Pourquoi passer au Strict JSON Schema pour fiabiliser vos pipelines IA

05 May 2026
5 min de lecture
28 vues
Sébastien Muler

Vos pipelines IA tombent en production à 3h du matin parce qu'un champ attendu par votre worker n'est pas dans la réponse OpenAI. Le mode JSON standard n'a rien signalé. C'est le scénario que le mode Structured Outputs avec strict: true permet d'éviter définitivement.

Cet article couvre l'implémentation concrète dans Laravel, les vraies causes d'échec en production, et pourquoi cette approche est indispensable dès que des étapes aval dépendent de payloads typés et prévisibles.

Source originale : OpenAI Structured Outputs in Laravel par Dewald Hugo.


JSON Mode vs Strict JSON Schema : une différence qui coûte cher

Le mode json_object garantit du JSON syntaxiquement valide. Rien de plus. Il ne garantit pas :

  • que les clés requises sont présentes
  • que les types sont respectés (status peut être un entier au lieu d'une chaîne)
  • que les valeurs d'enum sont respectées
  • que des champs n'ont pas silencieusement disparu

Le mode json_schema avec strict: true ferme complètement cette brèche. Le modèle utilise un décodage contraint : il est littéralement incapable d'émettre un token qui violerait votre schéma. La validation ne se fait plus dans votre code — elle se fait au niveau de la génération.

Garantie json_object json_schema strict
JSON valide
Clés requises présentes
Types corrects
Valeurs enum respectées

En production avec des workers en file d'attente, cette différence est la frontière entre un pipeline stable et un pipeline qui plante de façon intermittente et difficile à déboguer.


Implémentation dans Laravel

Définir le schéma JSON

La première étape est de définir un schéma strict qui décrit exactement la structure attendue en réponse.

$schema = [
    'type' => 'object',
    'properties' => [
        'status' => [
            'type' => 'string',
            'enum' => ['approved', 'rejected', 'pending'],
        ],
        'confidence' => [
            'type' => 'number',
        ],
        'reason' => [
            'type' => 'string',
        ],
    ],
    'required' => ['status', 'confidence', 'reason'],
    'additionalProperties' => false,
];

Le champ additionalProperties: false est important : il empêche le modèle d'ajouter des clés non déclarées, ce qui rendrait vos désérialisations plus fragiles.

Appel API avec response_format

use OpenAI\Laravel\Facades\OpenAI;

$response = OpenAI::chat()->create([
    'model' => 'gpt-4o',
    'messages' => [
        ['role' => 'user', 'content' => $prompt],
    ],
    'response_format' => [
        'type' => 'json_schema',
        'json_schema' => [
            'name' => 'moderation_result',
            'strict' => true,
            'schema' => $schema,
        ],
    ],
]);

$data = json_decode($response->choices[0]->message->content, true);

Avec strict: true, si le modèle ne peut pas satisfaire le schéma (ce qui est rare mais possible avec des schémas trop complexes ou contradictoires), il retourne une erreur explicite plutôt qu'une réponse silencieusement invalide. C'est exactement le comportement que vous voulez : fail fast, fail loudly.

Encapsuler dans un service Laravel

Pour un usage en pipeline, il est recommandé d'encapsuler la logique dans un service injectable :

class ModerationService
{
    public function analyze(string $content): ModerationResult
    {
        $response = OpenAI::chat()->create([
            'model' => 'gpt-4o',
            'messages' => [
                ['role' => 'system', 'content' => 'Tu es un modérateur de contenu.'],
                ['role' => 'user', 'content' => $content],
            ],
            'response_format' => [
                'type' => 'json_schema',
                'json_schema' => [
                    'name' => 'moderation_result',
                    'strict' => true,
                    'schema' => $this->getSchema(),
                ],
            ],
        ]);

        $data = json_decode($response->choices[0]->message->content, true);

        return ModerationResult::fromArray($data);
    }

    private function getSchema(): array
    {
        return [
            'type' => 'object',
            'properties' => [
                'status'     => ['type' => 'string', 'enum' => ['approved', 'rejected', 'pending']],
                'confidence' => ['type' => 'number'],
                'reason'     => ['type' => 'string'],
            ],
            'required' => ['status', 'confidence', 'reason'],
            'additionalProperties' => false,
        ];
    }
}

L'objet ModerationResult est un simple DTO (Data Transfer Object) qui mappe les champs. Vous pouvez utiliser des classes PHP readonly ou un package comme spatie/data-transfer-object selon vos préférences.


Les vrais points de friction en production

L'implémentation de base est simple. Les difficultés réelles apparaissent sur des cas spécifiques :

Schémas récursifs ou trop complexes : OpenAI impose des limites sur la profondeur et la complexité des schémas en mode strict. Un schéma avec des $ref profondément imbriqués peut être refusé. Simplifiez : aplatissez les structures quand c'est possible.

Les tableaux d'objets : Déclarez toujours le type des éléments dans items. Un tableau sans items défini n'est pas un schéma strict valide.

'items_list' => [
    'type' => 'array',
    'items' => [
        'type' => 'object',
        'properties' => [
            'id'    => ['type' => 'string'],
            'label' => ['type' => 'string'],
        ],
        'required' => ['id', 'label'],
        'additionalProperties' => false,
    ],
],

Les valeurs nullables : En JSON Schema strict OpenAI, utilisez "type": ["string", "null"] plutôt que nullable: true (syntaxe OpenAPI non supportée ici).

Retry et idempotence : Même avec un schéma valide, une réponse peut échouer pour des raisons réseau. Intégrez vos appels dans des jobs Laravel avec tries et backoff configurés, et assurez-vous que vos opérations en aval sont idempotentes.


Conclusion

Le passage de json_object à json_schema strict n'est pas un détail d'implémentation : c'est une décision d'architecture. En déplaçant la validation de votre code applicatif vers le niveau de génération du modèle, vous supprimez une catégorie entière de bugs intermittents difficiles à reproduire.

Pour MulerTech, cette approche s'intègre naturellement dans les pipelines Symfony/Laravel où des workers consomment des réponses IA : la garantie de conformité au schéma permet de désérialiser directement vers des DTOs fortement typés, sans couche de validation défensive intermédiaire.

Si vous construisez des fonctionnalités IA en production sur Laravel, adoptez strict: true dès le départ. Rétrofiter un pipeline existant après un incident de 3h du matin est nettement moins agréable.

Partager cet article