Le module « articles similaires » d’un site WordPress est souvent implémenté par une requête sur les catégories ou étiquettes communes, une méthode simple mais qui échoue régulièrement à repérer des articles réellement proches sur le fond mais classés différemment. Ce tutoriel construit une alternative par embeddings : des vecteurs numériques qui capturent le sens d’un texte, permettant de mesurer une proximité sémantique plutôt qu’une simple coïncidence de taxonomie.
Cet article ne reprend pas l’approche par taxonomies classiques ni la recherche sémantique en temps réel dans une barre de recherche, deux sujets traités séparément : il se concentre sur le calcul et le stockage d’embeddings pour un module de recommandation en fin d’article.
Ce qu’est un embedding, en une phrase utile
Un embedding est un vecteur de plusieurs centaines ou milliers de nombres décimaux, produit par un modèle spécialisé, qui représente le sens d’un texte dans un espace mathématique. Deux textes au sens proche produisent des vecteurs proches dans cet espace, ce que l’on mesure par une similarité cosinus : plus la valeur se rapproche de 1, plus les textes sont sémantiquement proches.
Calculer l’embedding d’un article à la publication
Le calcul se déclenche à la publication, comme pour un résumé automatique, et le vecteur obtenu est stocké dans une métadonnée sérialisée. Le contenu est nettoyé de ses balises avant l’envoi, pour ne conserver que le texte porteur de sens.
add_action( 'wpm_calculer_embedding', function ( $post_id ) {
$contenu = wp_strip_all_tags( get_post_field( 'post_content', $post_id ) );
$titre = get_the_title( $post_id );
$reponse = wp_remote_post( 'https://api.openai.com/v1/embeddings', array(
'timeout' => 20,
'headers' => array(
'Authorization' => 'Bearer ' . WPM_LLM_API_KEY,
'Content-Type' => 'application/json',
),
'body' => wp_json_encode( array(
'model' => 'text-embedding-3-small',
'input' => $titre . "\n\n" . mb_substr( $contenu, 0, 4000 ),
) ),
) );
if ( is_wp_error( $reponse ) ) {
return;
}
$corps = json_decode( wp_remote_retrieve_body( $reponse ), true );
$vecteur = $corps['data'][0]['embedding'] ?? null;
if ( $vecteur ) {
update_post_meta( $post_id, '_wpm_embedding', wp_json_encode( $vecteur ) );
}
} );

Calculer la similarité entre deux vecteurs
La similarité cosinus se calcule directement en PHP, sans dépendance externe, à partir des deux tableaux de nombres. C’est une opération purement mathématique qui ne nécessite aucun nouvel appel à l’API une fois les vecteurs déjà stockés.
function wpm_similarite_cosinus( array $vecteur_a, array $vecteur_b ) {
$produit_scalaire = 0;
$norme_a = 0;
$norme_b = 0;
foreach ( $vecteur_a as $i => $valeur ) {
$produit_scalaire += $valeur * $vecteur_b[ $i ];
$norme_a += $valeur ** 2;
$norme_b += $vecteur_b[ $i ] ** 2;
}
return $produit_scalaire / ( sqrt( $norme_a ) * sqrt( $norme_b ) );
}
Pourquoi le calcul en tâche de fond, pas à l’affichage
Comparer le vecteur d’un article à tous les autres vecteurs du site à chaque affichage de page serait bien trop coûteux en temps de calcul dès que le site dépasse quelques centaines d’articles. Le rapprochement des articles similaires est donc calculé une fois en tâche de fond, via Action Scheduler, et son résultat stocké en meta, pour un affichage instantané côté public.
function wpm_calculer_articles_similaires( $post_id ) {
$vecteur_ref = json_decode( get_post_meta( $post_id, '_wpm_embedding', true ), true );
if ( ! $vecteur_ref ) {
return;
}
$candidats = get_posts( array(
'post_type' => 'post',
'posts_per_page' => 300,
'post__not_in' => array( $post_id ),
'meta_key' => '_wpm_embedding',
) );
$scores = array();
foreach ( $candidats as $candidat ) {
$vecteur_candidat = json_decode( get_post_meta( $candidat->ID, '_wpm_embedding', true ), true );
if ( $vecteur_candidat ) {
$scores[ $candidat->ID ] = wpm_similarite_cosinus( $vecteur_ref, $vecteur_candidat );
}
}
arsort( $scores );
update_post_meta( $post_id, '_wpm_articles_similaires', array_slice( array_keys( $scores ), 0, 4, true ) );
}
Ne recalculer qu’en cas de changement réel
Comme pour les résumés automatiques, un hash du contenu évite de recalculer un embedding à chaque sauvegarde mineure. Le recalcul des articles similaires, lui, doit aussi se déclencher quand un nouvel article est publié, puisqu’il peut devenir le meilleur candidat pour des articles plus anciens déjà en ligne, ce qui suppose un recalcul périodique global plutôt qu’uniquement déclenché à la publication.
Ce que ce module a changé sur le taux de rebond
Sur le site où ce module a été mis en place, le taux de clic sur les suggestions d’articles en fin de page a nettement dépassé celui du module précédent basé uniquement sur les catégories communes, en particulier sur les articles de fond dont le sujet traverse plusieurs catégories du site.
Deux articles peuvent partager zéro catégorie commune et traiter pourtant exactement le même sujet sous un angle différent. C’est précisément ce cas que les embeddings permettent de rattraper.
Ce qu’il faut retenir
Un module de recommandation par embeddings tient sur trois piliers : un vecteur calculé une seule fois par article et stocké en base, une comparaison purement mathématique effectuée en tâche de fond, et un affichage public qui ne fait jamais d’appel réseau au moment du rendu de la page.