Si vous avez déjà collé un extrait d’article dans une meta description “vite fait” à minuit, vous savez le problème : c’est répétitif, ça finit souvent trop long, et ça part parfois hors-sujet. Le résumé automatique, lui, peut être fiable… à condition de le brancher proprement à WordPress 6.9.4, avec du cache, des garde-fous, et zéro clé API exposée.
Le besoin / Le cas d’usage
Un site WordPress vit de contenus. Mais le lecteur, lui, scanne. Un résumé court et stable (2–3 phrases) améliore :
- Les pages de catégorie (aperçu homogène des articles).
- Les newsletters (teasers rapides sans réécriture manuelle).
- Les pages “À la une” (mise en avant plus lisible).
- Les sites multi-auteurs : même style de résumé, même longueur.
À la fin, vous aurez un système qui :
- génère un résumé IA lors de l’enregistrement d’un article,
- le stocke en meta (dans la base WordPress),
- le met en cache (Transients) pour éviter de payer deux fois,
- l’affiche via un shortcode (compatible Divi 5, Elementor, Avada),
- gère les erreurs (timeout, quota, JSON invalide) sans casser votre site.
Résumé rapide
- Vous stockez la clé API dans wp-config.php (jamais en dur dans le code).
- Un mu-plugin génère un résumé via wp_remote_post() vers l’API OpenAI.
- Le résumé est enregistré dans post meta (
_ai_summary) et réutilisé. - Un transient évite les appels répétés et limite les coûts.
- Un shortcode
[ai_summary]permet l’affichage dans Divi 5 / Elementor / Avada. - Vous avez un mode debug, des logs propres, et un tableau de diagnostic.
Quand utiliser l’IA pour ça
J’active ce type de résumé automatique quand le site a au moins une de ces contraintes :
- Beaucoup d’articles (archives profondes, catégories très actives).
- Plusieurs rédacteurs et besoin d’un format uniforme.
- Contenu long (guides, dossiers, études) où un chapeau humain est souvent oublié.
- Recyclage : newsletter, push notifications, pages “top articles”.
Ça marche aussi très bien pour des sites qui utilisent Divi 5, Elementor ou Avada : on affiche le résumé via shortcode, sans toucher au builder.
Quand ne PAS utiliser l’IA
Ne payez pas une API si une solution native suffit. Cas typiques :
- Vous avez déjà un extrait WordPress (champ “Extrait”) et votre équipe le remplit correctement.
- Vos articles sont courts (300–500 mots) : un extrait tronqué peut suffire.
- Vous avez des contraintes légales fortes (contenu médical/juridique) : un résumé IA peut “interpréter” et introduire des erreurs.
- Vous voulez un résumé 100% déterministe : l’IA varie, même avec des paramètres stricts.
Alternative simple (sans IA) : afficher l’extrait, ou générer un “résumé” en tronquant le contenu (ex. wp_trim_words()). C’est gratuit, instantané, et souvent suffisant.
Prérequis
Versions et environnement
- WordPress : 6.9.4 (avril 2026) ou plus récent.
- PHP : 8.1+ (recommandé). Si vous êtes en 7.x, vous allez croiser des erreurs fatales sur du code moderne.
- HTTPS côté site (fortement recommandé) pour éviter de faire transiter des sessions admin en clair.
Une API : définition simple (utile avant le code)
Une API (Application Programming Interface) est un service accessible via HTTP. Concrètement, WordPress envoie une requête HTTP (souvent en JSON) à une URL, le service répond avec du JSON, et votre code transforme cette réponse en texte exploitable.
Dans WordPress, on fait ça proprement via l’API HTTP : wp_remote_post() et wp_remote_get(). Source officielle : HTTP API (developer.wordpress.org).
Clé API et stockage (wp-config.php)
Vous avez besoin d’une clé API d’un fournisseur IA. Ici je montre OpenAI parce que l’API est stable et bien documentée, mais la structure sera similaire pour Anthropic/Mistral/Google.
- Créez une clé : OpenAI API docs
Ajoutez ensuite la clé dans wp-config.php (au-dessus de “That’s all, stop editing!”). Ne la mettez jamais dans un plugin, jamais dans un snippet partagé, jamais dans un dépôt Git.
define( 'BPCAB_OPENAI_API_KEY', 'sk-votre-cle-ici' );
Attention coûts : chaque résumé est un appel payant. Vous allez limiter ça avec le cache et en ne résumant que lors d’événements précis (publication / mise à jour).
Où coller le code
Pour ce type de fonctionnalité, j’évite functions.php (trop facile à casser lors d’une mise à jour de thème). Deux options robustes :
- mu-plugin (recommandé) : fichier dans
wp-content/mu-plugins/, chargé automatiquement. - plugin custom : dossier dans
wp-content/plugins/, activable/désactivable.
Je pars sur un mu-plugin, parce que j’ai souvent vu des sites “perdre” leurs snippets après un changement de thème ou un plugin de snippets désactivé.
Architecture de la solution
Flux (schéma textuel)
Éditeur WordPress → enregistrement de l’article → hook save_post → récupération du contenu → vérifications (type, statut, autosave) → cache transient (éviter double appel) → wp_remote_post() → API IA → parsing JSON → sanitation → stockage en post meta _ai_summary → affichage via shortcode.
Pourquoi ce design
- Stocker en meta : le résumé est persistant, indexable, et réutilisable sans appeler l’API à chaque affichage.
- Transient : évite les appels multiples si vous cliquez “Mettre à jour” plusieurs fois, ou si un plugin déclenche plusieurs sauvegardes.
- Shortcode : compatible avec Divi 5 / Elementor / Avada sans développement spécifique.
Le code complet — étape par étape
Étape 1 — Créer le mu-plugin
Créez le dossier wp-content/mu-plugins/ s’il n’existe pas. Ajoutez un fichier :
wp-content/mu-plugins/ai-auto-summary.php
Collez la base suivante :
<?php
/**
* Plugin Name: AI Auto Summary (mu-plugin)
* Description: Génère et stocke un résumé IA des articles WordPress.
* Version: 1.0.0
* Requires at least: 6.9
* Requires PHP: 8.1
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
Étape 2 — Définir les constantes et réglages
On centralise les réglages : longueur, modèle, timeout. Vous pourrez ajuster sans toucher à toute la logique.
const BPCAB_AI_SUMMARY_META_KEY = '_ai_summary';
const BPCAB_AI_SUMMARY_MODEL = 'gpt-4.1-mini'; // Modèle “mini” : bon compromis coût/qualité (à ajuster)
const BPCAB_AI_TIMEOUT_SECONDS = 20;
const BPCAB_AI_MAX_INPUT_CHARS = 12000; // Limite simple pour éviter d'envoyer des romans entiers
Étape 3 — Ajouter le hook de génération au bon moment
Un hook est un point d’extension. Une action exécute du code à un moment donné (ex. sauvegarde d’un post). Un filtre modifie une valeur (ex. le contenu avant affichage). Ici, on utilise une action : save_post.
Piège classique : utiliser the_content pour générer le résumé. Ça déclenche l’IA à chaque affichage. Résultat : facture API + site lent. On génère au moment de la sauvegarde, pas au rendu.
add_action( 'save_post', 'bpcab_ai_maybe_generate_summary', 20, 3 );
/**
* Déclenche la génération du résumé lors de l'enregistrement.
*
* @param int $post_id ID du post.
* @param WP_Post $post Objet post.
* @param bool $update True si mise à jour, false si création.
*/
function bpcab_ai_maybe_generate_summary( int $post_id, $post, bool $update ): void {
// 1) Éviter les autosaves/révisions (sinon vous payez pour rien)
if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
return;
}
// 2) Vérifier le type de contenu : ici, uniquement les articles
if ( $post->post_type !== 'post' ) {
return;
}
// 3) Générer seulement quand l'article est publié (ou en passe de l'être)
$status = get_post_status( $post_id );
if ( $status !== 'publish' ) {
return;
}
// 4) Si un résumé existe déjà, ne pas écraser (vous pourrez ajouter un bouton “Regénérer” plus tard)
$existing = get_post_meta( $post_id, BPCAB_AI_SUMMARY_META_KEY, true );
if ( is_string( $existing ) && $existing !== '' ) {
return;
}
// 5) Vérifier la présence de la clé API
if ( ! defined( 'BPCAB_OPENAI_API_KEY' ) || ! is_string( BPCAB_OPENAI_API_KEY ) || BPCAB_OPENAI_API_KEY === '' ) {
error_log( '[AI Summary] Clé API absente. Ajoutez BPCAB_OPENAI_API_KEY dans wp-config.php.' );
return;
}
$content = bpcab_ai_get_post_text_for_summary( $post_id );
if ( $content === '' ) {
return;
}
// 6) Anti double-facturation : transient de verrouillage pendant 10 minutes
$lock_key = 'bpcab_ai_summary_lock_' . $post_id;
if ( get_transient( $lock_key ) ) {
return;
}
set_transient( $lock_key, 1, 10 * MINUTE_IN_SECONDS );
$summary = bpcab_ai_generate_summary_via_openai( $content, get_the_title( $post_id ) );
// En cas d'échec, on libère le lock plus tôt (sinon il expire tout seul)
if ( $summary === '' ) {
delete_transient( $lock_key );
return;
}
// 7) Sanitation : on stocke du texte simple
$summary = sanitize_text_field( $summary );
update_post_meta( $post_id, BPCAB_AI_SUMMARY_META_KEY, $summary );
}
Étape 4 — Extraire un texte “propre” depuis l’article
Le contenu WordPress peut contenir des blocs, du HTML, des shortcodes. Envoyer ça brut à l’IA donne souvent des résumés bizarres (elle “résume” des bouts de mise en page). On nettoie :
- suppression des shortcodes,
- suppression des balises HTML,
- limite de taille.
/**
* Récupère un texte propre à résumer (sans HTML/shortcodes).
*/
function bpcab_ai_get_post_text_for_summary( int $post_id ): string {
$post = get_post( $post_id );
if ( ! $post instanceof WP_Post ) {
return '';
}
$text = $post->post_content;
// Retirer les shortcodes (souvent présents avec Divi/Avada et certains builders)
$text = strip_shortcodes( $text );
// Retirer le HTML
$text = wp_strip_all_tags( $text );
// Normaliser les espaces
$text = preg_replace( '/s+/', ' ', $text );
$text = trim( (string) $text );
if ( $text === '' ) {
return '';
}
// Limiter la taille envoyée à l'API (simple et efficace)
if ( mb_strlen( $text ) > BPCAB_AI_MAX_INPUT_CHARS ) {
$text = mb_substr( $text, 0, BPCAB_AI_MAX_INPUT_CHARS );
}
return $text;
}
Étape 5 — Appeler l’API OpenAI avec wp_remote_post()
On fait un appel HTTP POST vers l’endpoint “Responses” (API moderne). On gère :
- timeout (sinon votre sauvegarde peut sembler “bloquée”),
- codes HTTP (401, 429, 500…),
- JSON invalide,
- cache (transient basé sur le hash du contenu).
/**
* Génère un résumé via l'API OpenAI.
*
* @param string $content Texte à résumer.
* @param string $title Titre de l'article (aide au contexte).
* @return string Résumé ou chaîne vide si échec.
*/
function bpcab_ai_generate_summary_via_openai( string $content, string $title = '' ): string {
// Cache “fonctionnel” : si le contenu est identique, on réutilise le résumé
$hash = hash( 'sha256', $title . '|' . $content );
$cache_key = 'bpcab_ai_summary_' . substr( $hash, 0, 32 );
$cached = get_transient( $cache_key );
if ( is_string( $cached ) && $cached !== '' ) {
return $cached;
}
$endpoint = 'https://api.openai.com/v1/responses';
// Prompt : court, contraint, stable
$instruction = "Vous êtes un assistant de rédaction. Résumez l'article en français en 2 à 3 phrases maximum. " .
"Ton neutre. Pas de listes. Pas de titre. Pas de conclusion. " .
"Ne mentionnez pas que vous êtes une IA.";
$input = "Titre : " . $title . "nnTexte :n" . $content;
$body = array(
'model' => BPCAB_AI_SUMMARY_MODEL,
'input' => array(
array(
'role' => 'system',
'content' => $instruction,
),
array(
'role' => 'user',
'content' => $input,
),
),
// Paramètres de génération : limiter la longueur et la variabilité
'max_output_tokens' => 160,
'temperature' => 0.2,
);
$args = array(
'timeout' => BPCAB_AI_TIMEOUT_SECONDS,
'headers' => array(
'Authorization' => 'Bearer ' . BPCAB_OPENAI_API_KEY,
'Content-Type' => 'application/json',
),
'body' => wp_json_encode( $body ),
);
$response = wp_remote_post( $endpoint, $args );
if ( is_wp_error( $response ) ) {
error_log( '[AI Summary] wp_remote_post error: ' . $response->get_error_message() );
return '';
}
$code = (int) wp_remote_retrieve_response_code( $response );
$raw = (string) wp_remote_retrieve_body( $response );
if ( $code < 200 || $code >= 300 ) {
error_log( '[AI Summary] HTTP ' . $code . ' - Réponse: ' . $raw );
return '';
}
$data = json_decode( $raw, true );
if ( ! is_array( $data ) ) {
error_log( '[AI Summary] JSON invalide. Body: ' . $raw );
return '';
}
/*
* Parsing : l'API Responses peut renvoyer un tableau "output" avec des items.
* On récupère le texte de sortie de manière prudente, sans supposer un format unique.
*/
$summary = bpcab_ai_extract_text_from_openai_responses( $data );
$summary = trim( (string) $summary );
if ( $summary === '' ) {
error_log( '[AI Summary] Résumé vide. Body: ' . $raw );
return '';
}
// Sanitation côté affichage : on stocke du texte simple, mais on reste prudent
$summary = wp_strip_all_tags( $summary );
// Cache 30 jours : un résumé ne change pas souvent
set_transient( $cache_key, $summary, 30 * DAY_IN_SECONDS );
return $summary;
}
/**
* Extrait le texte depuis la réponse OpenAI "Responses API".
* Cette fonction est volontairement défensive.
*/
function bpcab_ai_extract_text_from_openai_responses( array $data ): string {
// Cas fréquent : output[0].content[0].text
if ( isset( $data['output'] ) && is_array( $data['output'] ) ) {
foreach ( $data['output'] as $output_item ) {
if ( ! is_array( $output_item ) ) {
continue;
}
if ( ! isset( $output_item['content'] ) || ! is_array( $output_item['content'] ) ) {
continue;
}
foreach ( $output_item['content'] as $content_item ) {
if ( is_array( $content_item ) && isset( $content_item['text'] ) && is_string( $content_item['text'] ) ) {
return $content_item['text'];
}
}
}
}
// Fallback : certains formats renvoient "output_text"
if ( isset( $data['output_text'] ) && is_string( $data['output_text'] ) ) {
return $data['output_text'];
}
return '';
}
Étape 6 — Afficher le résumé (shortcode)
Un shortcode est un petit code entre crochets que WordPress remplace par du contenu. C’est la méthode la plus simple pour l’intégration dans :
- Divi 5 : module “Code” ou “Texte” (en mode texte) avec
[ai_summary]. - Elementor : widget “Shortcode”.
- Avada : élément “Shortcode” (Fusion Builder).
add_shortcode( 'ai_summary', 'bpcab_ai_summary_shortcode' );
/**
* Shortcode: [ai_summary id="123"]
* Sans id, affiche le résumé du post courant.
*/
function bpcab_ai_summary_shortcode( array $atts ): string {
$atts = shortcode_atts(
array(
'id' => 0,
),
$atts,
'ai_summary'
);
$post_id = (int) $atts['id'];
if ( $post_id <= 0 ) {
$post_id = get_the_ID();
}
if ( ! $post_id ) {
return '';
}
$summary = get_post_meta( $post_id, BPCAB_AI_SUMMARY_META_KEY, true );
if ( ! is_string( $summary ) || $summary === '' ) {
return '';
}
// Affichage : on autorise uniquement du texte (pas de HTML)
$summary = esc_html( $summary );
return '<div class="ai-summary">' . $summary . '</div>';
}
Si vous voulez du HTML minimal (ex. italique), ne stockez pas du HTML brut sans contrôle. Utilisez wp_kses_post() au moment de l’affichage et une whitelist stricte. Pour un débutant, je conseille : texte simple.
Le code assemblé complet
Copiez-collez tout ce fichier dans wp-content/mu-plugins/ai-auto-summary.php. Ensuite, ajoutez la constante de clé API dans wp-config.php.
<?php
/**
* Plugin Name: AI Auto Summary (mu-plugin)
* Description: Génère et stocke un résumé IA des articles WordPress.
* Version: 1.0.0
* Requires at least: 6.9
* Requires PHP: 8.1
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
const BPCAB_AI_SUMMARY_META_KEY = '_ai_summary';
const BPCAB_AI_SUMMARY_MODEL = 'gpt-4.1-mini';
const BPCAB_AI_TIMEOUT_SECONDS = 20;
const BPCAB_AI_MAX_INPUT_CHARS = 12000;
add_action( 'save_post', 'bpcab_ai_maybe_generate_summary', 20, 3 );
add_shortcode( 'ai_summary', 'bpcab_ai_summary_shortcode' );
/**
* Déclenche la génération du résumé lors de l'enregistrement.
*
* @param int $post_id ID du post.
* @param WP_Post $post Objet post.
* @param bool $update True si mise à jour, false si création.
*/
function bpcab_ai_maybe_generate_summary( int $post_id, $post, bool $update ): void {
if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
return;
}
if ( ! ( $post instanceof WP_Post ) ) {
return;
}
if ( $post->post_type !== 'post' ) {
return;
}
$status = get_post_status( $post_id );
if ( $status !== 'publish' ) {
return;
}
$existing = get_post_meta( $post_id, BPCAB_AI_SUMMARY_META_KEY, true );
if ( is_string( $existing ) && $existing !== '' ) {
return;
}
if ( ! defined( 'BPCAB_OPENAI_API_KEY' ) || ! is_string( BPCAB_OPENAI_API_KEY ) || BPCAB_OPENAI_API_KEY === '' ) {
error_log( '[AI Summary] Clé API absente. Ajoutez BPCAB_OPENAI_API_KEY dans wp-config.php.' );
return;
}
$content = bpcab_ai_get_post_text_for_summary( $post_id );
if ( $content === '' ) {
return;
}
$lock_key = 'bpcab_ai_summary_lock_' . $post_id;
if ( get_transient( $lock_key ) ) {
return;
}
set_transient( $lock_key, 1, 10 * MINUTE_IN_SECONDS );
$summary = bpcab_ai_generate_summary_via_openai( $content, get_the_title( $post_id ) );
if ( $summary === '' ) {
delete_transient( $lock_key );
return;
}
$summary = sanitize_text_field( $summary );
update_post_meta( $post_id, BPCAB_AI_SUMMARY_META_KEY, $summary );
}
/**
* Récupère un texte propre à résumer (sans HTML/shortcodes).
*/
function bpcab_ai_get_post_text_for_summary( int $post_id ): string {
$post = get_post( $post_id );
if ( ! $post instanceof WP_Post ) {
return '';
}
$text = (string) $post->post_content;
$text = strip_shortcodes( $text );
$text = wp_strip_all_tags( $text );
$text = preg_replace( '/s+/', ' ', $text );
$text = trim( (string) $text );
if ( $text === '' ) {
return '';
}
if ( mb_strlen( $text ) > BPCAB_AI_MAX_INPUT_CHARS ) {
$text = mb_substr( $text, 0, BPCAB_AI_MAX_INPUT_CHARS );
}
return $text;
}
/**
* Génère un résumé via l'API OpenAI.
*/
function bpcab_ai_generate_summary_via_openai( string $content, string $title = '' ): string {
$hash = hash( 'sha256', $title . '|' . $content );
$cache_key = 'bpcab_ai_summary_' . substr( $hash, 0, 32 );
$cached = get_transient( $cache_key );
if ( is_string( $cached ) && $cached !== '' ) {
return $cached;
}
$endpoint = 'https://api.openai.com/v1/responses';
$instruction = "Vous êtes un assistant de rédaction. Résumez l'article en français en 2 à 3 phrases maximum. " .
"Ton neutre. Pas de listes. Pas de titre. Pas de conclusion. " .
"Ne mentionnez pas que vous êtes une IA.";
$input = "Titre : " . $title . "nnTexte :n" . $content;
$body = array(
'model' => BPCAB_AI_SUMMARY_MODEL,
'input' => array(
array(
'role' => 'system',
'content' => $instruction,
),
array(
'role' => 'user',
'content' => $input,
),
),
'max_output_tokens' => 160,
'temperature' => 0.2,
);
$args = array(
'timeout' => BPCAB_AI_TIMEOUT_SECONDS,
'headers' => array(
'Authorization' => 'Bearer ' . BPCAB_OPENAI_API_KEY,
'Content-Type' => 'application/json',
),
'body' => wp_json_encode( $body ),
);
$response = wp_remote_post( $endpoint, $args );
if ( is_wp_error( $response ) ) {
error_log( '[AI Summary] wp_remote_post error: ' . $response->get_error_message() );
return '';
}
$code = (int) wp_remote_retrieve_response_code( $response );
$raw = (string) wp_remote_retrieve_body( $response );
if ( $code < 200 || $code >= 300 ) {
error_log( '[AI Summary] HTTP ' . $code . ' - Réponse: ' . $raw );
return '';
}
$data = json_decode( $raw, true );
if ( ! is_array( $data ) ) {
error_log( '[AI Summary] JSON invalide. Body: ' . $raw );
return '';
}
$summary = bpcab_ai_extract_text_from_openai_responses( $data );
$summary = trim( (string) $summary );
if ( $summary === '' ) {
error_log( '[AI Summary] Résumé vide. Body: ' . $raw );
return '';
}
$summary = wp_strip_all_tags( $summary );
set_transient( $cache_key, $summary, 30 * DAY_IN_SECONDS );
return $summary;
}
/**
* Extrait le texte depuis la réponse OpenAI "Responses API".
*/
function bpcab_ai_extract_text_from_openai_responses( array $data ): string {
if ( isset( $data['output'] ) && is_array( $data['output'] ) ) {
foreach ( $data['output'] as $output_item ) {
if ( ! is_array( $output_item ) ) {
continue;
}
if ( ! isset( $output_item['content'] ) || ! is_array( $output_item['content'] ) ) {
continue;
}
foreach ( $output_item['content'] as $content_item ) {
if ( is_array( $content_item ) && isset( $content_item['text'] ) && is_string( $content_item['text'] ) ) {
return $content_item['text'];
}
}
}
}
if ( isset( $data['output_text'] ) && is_string( $data['output_text'] ) ) {
return $data['output_text'];
}
return '';
}
/**
* Shortcode: [ai_summary id="123"]
*/
function bpcab_ai_summary_shortcode( array $atts ): string {
$atts = shortcode_atts(
array(
'id' => 0,
),
$atts,
'ai_summary'
);
$post_id = (int) $atts['id'];
if ( $post_id <= 0 ) {
$post_id = get_the_ID();
}
if ( ! $post_id ) {
return '';
}
$summary = get_post_meta( $post_id, BPCAB_AI_SUMMARY_META_KEY, true );
if ( ! is_string( $summary ) || $summary === '' ) {
return '';
}
return '<div class="ai-summary">' . esc_html( $summary ) . '</div>';
}
Explication du code
Pourquoi save_post (et pas the_content)
save_post se déclenche quand WordPress enregistre un contenu. Ça vous donne un moment “naturel” pour appeler l’IA une fois, stocker le résultat, et ne plus y toucher.
J’ai souvent vu des tutos qui résument “à l’affichage” via the_content. Sur un site avec cache désactivé ou un bot qui crawl, vous payez l’API en boucle. Et le lecteur peut voir un résumé différent à chaque chargement.
Le verrou transient (anti double appel)
WordPress peut sauvegarder plusieurs fois (autosave, mises à jour, plugins SEO). Le transient bpcab_ai_summary_lock_{ID} évite de déclencher l’IA 3 fois en 30 secondes.
Le cache par hash de contenu
On calcule un hash SHA-256 du titre + contenu. Si le même texte revient (copier-coller, restauration), on réutilise le résumé depuis le transient. C’est un gain direct sur la facture.
Sanitization : ce qu’on protège
sanitize_text_field(): retire des caractères et normalise un texte simple.wp_strip_all_tags(): supprime toute balise HTML qui pourrait avoir été générée.esc_html()à l’affichage : empêche toute injection HTML/JS.
Risque réel : si vous affichez une réponse IA sans échappement, vous ouvrez une porte à l’injection (rare, mais je l’ai déjà vu via des contenus qui “forcent” l’IA à produire du HTML).
Coûts API et optimisation
Les prix changent régulièrement. Prenez l’habitude de vérifier la page officielle avant de déployer : OpenAI Pricing.
Estimation simple (ordre de grandeur)
Un résumé de 2–3 phrases, c’est souvent :
- entrée : 1 000 à 3 000 tokens (selon la longueur de l’article),
- sortie : 80 à 160 tokens.
Exemple de calcul mensuel :
- 200 articles résumés / mois
- 2 000 tokens d’entrée + 120 tokens de sortie en moyenne
Vous multipliez par le prix du modèle choisi. Le point clé : le coût est surtout dans l’entrée. D’où la limite BPCAB_AI_MAX_INPUT_CHARS.
Optimisations qui marchent vraiment
- Limiter le texte envoyé : 8 000–12 000 caractères suffisent souvent.
- Modèle “mini” pour des résumés : inutile de payer un modèle lourd pour 3 phrases.
- Générer une seule fois et stocker en meta.
- Regénération manuelle (option avancée) plutôt que regénérer à chaque update.
Variantes et cas d’usage avancés
1) Regénérer le résumé quand l’article est mis à jour
Par défaut, le code ne remplace pas un résumé existant. Si vous voulez le refaire à chaque mise à jour, supprimez ce bloc :
// 4) Si un résumé existe déjà, ne pas écraser
$existing = get_post_meta( $post_id, BPCAB_AI_SUMMARY_META_KEY, true );
if ( is_string( $existing ) && $existing !== '' ) {
return;
}
Mais je ne le fais pas sur la plupart des sites : un auteur peut faire 10 petites corrections, et vous payez 10 fois.
2) Résumer aussi des pages (post_type = page)
Vous pouvez autoriser page :
if ( ! in_array( $post->post_type, array( 'post', 'page' ), true ) ) {
return;
}
3) Intégration Divi 5 / Elementor / Avada (pratique)
- Divi 5 : ajoutez un module “Code” et mettez
[ai_summary]. Si vous utilisez un template Theme Builder, mettez le shortcode dans le layout d’article. - Elementor : widget “Shortcode” dans le template Single Post, puis
[ai_summary]. - Avada : élément “Shortcode” dans le builder, ou dans un template global.
Si votre builder “nettoie” le shortcode (ça arrive sur certains modules texte), utilisez le widget/élément dédié “Shortcode”.
Sécurité et bonnes pratiques
Ne jamais exposer la clé API côté client
Ne faites pas d’appel à OpenAI depuis JavaScript dans le navigateur. Même si vous “cachez” la clé, elle finira visible. L’appel doit rester côté serveur (PHP) via wp_remote_post().
Limiter qui peut déclencher la génération
Le hook save_post est déclenché par des actions d’édition. En admin, c’est déjà protégé par les capacités WordPress. Si vous ajoutez plus tard un bouton “Regénérer”, utilisez :
- un nonce (jeton anti-CSRF),
current_user_can( 'edit_post', $post_id ).
Référence : Nonces (developer.wordpress.org).
Rate limiting (anti abus / anti facture surprise)
Si plusieurs auteurs publient en masse, vous pouvez limiter à X résumés/heure via un transient global. Exemple : stocker un compteur et refuser au-delà d’un seuil. C’est basique, mais ça évite le “j’ai importé 2 000 posts et j’ai tout résumé d’un coup”.
RGPD : données envoyées à un tiers
Vous envoyez du contenu à un prestataire externe. Selon votre contexte :
- mettez à jour votre politique de confidentialité,
- évitez d’envoyer des données personnelles inutiles,
- ne résumez pas automatiquement des contenus contenant des informations sensibles.
Comment tester et déboguer
1) Activer les logs WordPress
Dans wp-config.php (sur un environnement de test), activez :
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );
Les erreurs seront dans wp-content/debug.log.
2) Test minimal
- Créez un nouvel article.
- Ajoutez 2–3 paragraphes de texte (pas seulement un titre).
- Publiez.
- Vérifiez la meta
_ai_summary(avec un plugin comme “Query Monitor” ou via l’éditeur si vous avez un outil de meta). - Ajoutez
[ai_summary]dans votre template ou dans le contenu, et vérifiez l’affichage.
3) Vérifier les appels HTTP
Si vous avez des timeouts, commencez par augmenter temporairement BPCAB_AI_TIMEOUT_SECONDS à 30. Puis regardez si votre hébergeur bloque les requêtes sortantes.
Doc officielle sur l’API HTTP WordPress : HTTP API.
Si ça ne marche pas
Tableau de diagnostic
| Symptôme | Cause probable | Vérification | Solution |
|---|---|---|---|
| Le résumé n’apparaît jamais | Code collé au mauvais endroit / mu-plugin non chargé | Vérifiez que le fichier est dans wp-content/mu-plugins/ |
Déplacez le fichier, vérifiez les droits, rechargez l’admin |
| Erreur 500 en admin lors de la publication | Erreur PHP (point-virgule manquant, parenthèse) | Consultez wp-content/debug.log |
Corrigez la syntaxe, ou restaurez une sauvegarde |
| HTTP 401 dans les logs | Clé API invalide | Voir debug.log (réponse API) |
Regénérez la clé, mettez-la dans wp-config.php |
| HTTP 429 dans les logs | Quota dépassé / rate limit fournisseur | Dashboard fournisseur | Attendre, réduire les appels, ajouter du rate limiting + cache |
| Timeout / publication “bloquée” | Timeout trop bas, hébergeur lent, firewall sortant | Augmenter le timeout, tester depuis un autre serveur | Timeout 30s, contacter l’hébergeur, autoriser l’endpoint |
| Résumé vide | Contenu trop court ou trop “builder” (shortcodes) | Vérifiez le texte après nettoyage | Ajoutez du texte réel, ajustez le nettoyage, résumez un extrait |
| Vous modifiez l’article mais le résumé ne change pas | Le code ne régénère pas si meta existante | Vérifiez la présence de _ai_summary |
Supprimez la meta ou implémentez une regénération manuelle |
Erreurs réalistes que je vois souvent (et comment les éviter)
- Tester en production sans sauvegarde : faites un test sur staging. Une faute de syntaxe PHP peut bloquer tout l’admin.
- Confusion action/filtre :
save_postest une action. Ne cherchez pas à “return” quelque chose pour modifier une valeur. - Hook inadapté : éviter
the_contentpour une tâche coûteuse. - Plugin de cache : si vous affichez le résumé via shortcode et que vous avez un cache agressif, videz-le après la première génération.
- PHP trop ancien : si votre hébergeur est en PHP 7.4, passez en 8.1+. Référence : PHP supported versions (php.net).
Ressources
- WordPress HTTP API (developer.wordpress.org)
- Nonces et sécurité (developer.wordpress.org)
- Transients API (developer.wordpress.org)
- OpenAI API Overview
- OpenAI Responses API Reference
- Miroir GitHub du développement WordPress (wordpress-develop)
- WordPress Core Trac (suivi des tickets)
- Extension cURL (php.net)
FAQ
1) Est-ce que ça marche avec Elementor / Divi 5 / Avada ?
Oui. Le shortcode [ai_summary] est la méthode la plus simple. Utilisez le widget/élément “Shortcode” de votre builder.
2) Pourquoi un mu-plugin plutôt que functions.php ?
Parce que functions.php dépend du thème. Un changement de thème, et vous perdez la fonctionnalité. Le mu-plugin est chargé automatiquement, thème ou pas.
3) Est-ce que ça va ralentir la publication d’un article ?
Oui, un peu : vous attendez la réponse API pendant la sauvegarde (jusqu’au timeout). Sur des sites très actifs, je bascule souvent ce traitement en tâche asynchrone (WP-Cron ou Action Scheduler), mais c’est une variante plus avancée.
4) Puis-je résumer uniquement un extrait (les 2 premiers paragraphes) ?
Oui. Modifiez bpcab_ai_get_post_text_for_summary() pour ne prendre que les X premiers caractères, ou pour découper par paragraphes avant nettoyage.
5) Pourquoi stocker en post meta plutôt que recalculer à l’affichage ?
Pour éviter de payer l’API à chaque page vue et pour garantir un résumé stable. À l’affichage, vous ne faites qu’une lecture en base (rapide).
6) Puis-je afficher le résumé dans les archives (catégories) automatiquement ?
Oui. Vous pouvez ajouter le shortcode dans le template d’archive (ou via le builder si votre thème le permet). Autre option : accrocher un filtre sur the_excerpt pour remplacer l’extrait par la meta, mais je le fais seulement si le site a une stratégie claire (sinon ça surprend les auteurs).
7) Est-ce que WordPress 6.9.4 impose une manière particulière de faire les requêtes HTTP ?
Non : l’approche recommandée reste wp_remote_post() via l’HTTP API. C’est stable et compatible avec les environnements où cURL n’est pas directement accessible en PHP.
8) Comment “forcer” une regénération pour un article déjà résumé ?
Supprimez la meta _ai_summary (via un outil de meta, ou un petit script admin), puis mettez à jour l’article. Variante plus propre : ajouter un bouton “Regénérer” avec nonce et capacité.
9) Est-ce que l’IA peut inventer des informations dans un résumé ?
Oui, c’est possible. Réduire la température aide, mais ne garantit pas zéro erreur. Pour des contenus sensibles, gardez une validation humaine ou n’utilisez pas l’IA.
10) Pourquoi limiter BPCAB_AI_MAX_INPUT_CHARS plutôt que compter les tokens ?
Compter les tokens précisément demande une librairie dédiée. Pour un site débutant, une limite en caractères est simple, robuste, et réduit déjà fortement les coûts.