Warning: Undefined array key "content" in /wp-content/plugins/mon-plugin/inc/api.php on line 47. Cet avertissement, capturé dans les journaux d’erreurs un jeudi soir, ne se produisait que sur environ un appel sur cinquante, ce qui rendait sa reproduction en local particulièrement difficile.
Le code fautif suivait pourtant une structure a priori robuste : un appel à l’API, un décodage JSON, puis un accès direct à la clé attendue dans la réponse. Le problème ne venait ni du réseau ni du décodage, mais de la forme même du contenu renvoyé par l’API dans certains cas précis.
Symptôme
Le code en cause ressemblait à ceci, avec un accès direct sans vérification préalable :
$donnees = json_decode( wp_remote_retrieve_body( $reponse ), true );
$texte = $donnees['choices'][0]['message']['content'];
La très grande majorité des appels fonctionnaient sans problème. L’avertissement n’apparaissait que sur certaines requêtes, sans lien apparent avec le contenu du prompt envoyé.
Diagnostic
Un ajout temporaire de journalisation, enregistrant la réponse brute complète dans un fichier dès qu’un accès échouait, a permis d’isoler la cause : sur les prompts les plus longs, l’API interrompait parfois la génération avant la fin, faute d’espace suffisant dans la limite de jetons de sortie configurée. La réponse renvoyée contenait alors un champ finish_reason à length, et la structure du message ne suivait pas exactement le format habituel dans ce cas de troncature.
{
"choices": [
{
"finish_reason": "length",
"message": { "role": "assistant" }
}
]
}
Le champ content était absent du message dans cette situation précise, alors que le code supposait sa présence systématique. Le code HTTP renvoyé restait 200 : rien, du point de vue du protocole, ne signalait un problème.
Correctif

La correction combine deux mesures : une validation explicite de la structure avant tout accès, et une vérification du champ finish_reason pour distinguer une réponse tronquée d’une réponse complète.
$donnees = json_decode( wp_remote_retrieve_body( $reponse ), true );
$raison = $donnees['choices'][0]['finish_reason'] ?? null;
$texte = $donnees['choices'][0]['message']['content'] ?? null;
if ( 'length' === $raison ) {
error_log( 'Réponse tronquée par manque de jetons disponibles' );
return null;
}
if ( null === $texte ) {
error_log( 'Structure de réponse inattendue : ' . wp_json_encode( $donnees ) );
return null;
}
return $texte;
L’opérateur de coalescence nulle ?? évite l’avertissement en remplaçant l’accès direct par une valeur par défaut explicite, tout en gardant la trace de l’anomalie dans les journaux pour investigation ultérieure.
Prévention
La cause profonde restait la limite de jetons de sortie fixée trop bas par rapport à la longueur habituelle des prompts envoyés. Deux ajustements complémentaires ont suivi le correctif immédiat :
- Augmentation de la limite de jetons de sortie configurée dans l’appel
- Ajout d’un contrôle de longueur du prompt avant envoi, avec un avertissement si la marge devient trop faible
- Écriture d’une fonction utilitaire unique de décodage, réutilisée partout où une réponse d’API est traitée
Généraliser la validation
Cette fonction utilitaire, centralisée, évite qu’un accès direct sans vérification ne se réintroduise ailleurs dans le code au fil des évolutions du plugin :
function extraire_texte_reponse( array $donnees ): ?string {
return $donnees['choices'][0]['message']['content'] ?? null;
}
Un code HTTP 200 ne garantit jamais qu’une réponse d’API contient tout ce que l’on attend d’elle : la structure du corps de la réponse mérite sa propre vérification, indépendante du code de statut.
Pour aller plus loin
Cet incident illustre un principe plus général : une réponse d’API externe, même documentée, doit être traitée comme potentiellement incomplète. La validation explicite de chaque champ attendu, plutôt que la confiance dans la structure documentée, évite ce type d’avertissement intermittent, particulièrement difficile à reproduire en environnement de test.