Le WordPress d'aujourd'hui, décodé pour les développeurs

IA & MCP

Cache sémantique d’un chatbot HubSpot : ne pas payer deux fois

Deux visiteurs sur trois posent la même question avec des mots différents. Un cache basé sur la similarité, plutôt que sur le texte exact, réduit la facture d'un chatbot HubSpot à fort trafic.

Par WordPress Développement • 18 septembre 2024 • 5 min de lecture • Aucun commentaire
Cache sémantique d'un chatbot HubSpot : ne pas payer deux fois

Combien de fois « comment annuler mon abonnement ? » et « je veux résilier, comment je fais ? » déclenchent-elles, à tort, deux appels distincts à un modèle de langage facturé au token ? Sur l’assistant conversationnel installé par l’éditeur SaaS Novarim en façade de son support HubSpot, la réponse initiale était : systématiquement.

L’assistant répondait aux questions des visiteurs en s’appuyant sur la base de connaissances HubSpot, avec un appel à un LLM à chaque message. Un cache classique, indexé sur le texte exact de la question, existait déjà mais son taux de succès plafonnait à moins de 10 %, tant les visiteurs reformulent une même demande de dizaines de façons différentes. La facture mensuelle de l’API grimpait avec le trafic sans que le nombre de questions réellement distinctes augmente autant.

Pourquoi un cache texte-à-texte ne suffit pas

Un cache classique fonctionne comme une table de correspondance : la clé est le texte de la question, la valeur est la réponse déjà générée. Deux questions identiques au caractère près donnent un accès en cache ; la moindre variation (majuscule, ponctuation, synonyme) provoque un nouvel appel au modèle. Or les visiteurs d’un site ne tapent jamais deux fois la même phrase, ils expriment la même intention avec un vocabulaire différent.

Un cache sémantique change de logique : il compare le sens des questions plutôt que leur forme, en s’appuyant sur des embeddings, ces vecteurs numériques qui placent les phrases proches en sens à proximité dans un espace mathématique. Deux formulations différentes de la même intention obtiennent des vecteurs proches, ce qui permet de détecter la similarité même sans mot commun.

Le snippet : comparer avant d’appeler le modèle

Avant chaque appel au LLM, la question du visiteur est transformée en embedding, comparée à celles déjà en cache via une similarité cosinus, et la réponse existante est renvoyée si le score dépasse un seuil fixé empiriquement.

L'essentiel à retenir : Un cache exact rate la majorité des questions reformulées ; Le cache sémantique compare des vecteurs, pas des chaînes ; Un seuil de similarité mal réglé renvoie de mauvaises réponses
async function getCachedOrGenerate(question, cache, llmClient, embedClient) {
  const queryVector = await embedClient.embed(question);
  let best = null;
  for (const entry of cache) {
    const score = cosineSimilarity(queryVector, entry.vector);
    if (!best || score > best.score) best = { entry, score };
  }
  if (best && best.score >= 0.92) {
    return { answer: best.entry.answer, fromCache: true };
  }
  const answer = await llmClient.complete(question);
  cache.push({ question, vector: queryVector, answer });
  return { answer, fromCache: false };
}

Le seuil de 0,92 n’est pas arrivé du premier coup. Un seuil trop bas (0,80 par exemple) renvoyait parfois la réponse à « comment changer de forfait » à quelqu’un qui demandait « comment changer de mot de passe », deux questions dont le vecteur reste proche parce qu’elles partagent le même registre de vocabulaire de gestion de compte. Un seuil trop haut, à l’inverse, ramenait le taux de cache à son niveau initial.

Le réglage du seuil, étape par étape

  1. Constituer un jeu de cent paires de questions manuellement annotées « même intention » ou « intention différente ».
  2. Calculer la similarité cosinus pour chaque paire et observer la distribution des scores.
  3. Choisir le seuil qui sépare le mieux les deux groupes, en acceptant un léger taux de faux négatifs plutôt que des faux positifs.
  4. Revérifier ce seuil chaque mois, la base de connaissances évoluant avec de nouveaux articles.

Un cache sémantique trop optimiste coûte plus cher qu’un cache absent : une mauvaise réponse renvoyée avec assurance génère un ticket support, alors qu’un appel au modèle raté ne coûte qu’un peu d’argent.

Variantes possibles

Le cache en mémoire vive convient pour un trafic modéré, mais se vide à chaque redémarrage du service. Une variante persistante stocke les embeddings dans une base vectorielle externe, ce qui permet de conserver le cache entre les déploiements et de le partager entre plusieurs instances de l’assistant. Une autre variante ajoute une expiration : les réponses liées à une actualité ponctuelle (tarifs, promotion en cours) sont invalidées après quelques jours, pour éviter qu’un visiteur reçoive une information périmée à cause d’un cache trop généreux.

Enfin, il est possible de ne mettre en cache que les réponses jugées fiables, en excluant celles où le modèle a exprimé une incertitude ou a explicitement renvoyé vers un agent humain, pour ne pas propager une mauvaise réponse à toutes les reformulations suivantes de la même question.

En résumé

Après un mois de réglages successifs du seuil et de la stratégie d’expiration, 61 % des requêtes de l’assistant Novarim ont pu être servies depuis le cache sémantique plutôt que par un nouvel appel au modèle. Le gain ne se limite pas à la facture : les réponses mises en cache s’affichent aussi plus vite, ce qui compte tout autant pour l’expérience du visiteur que pour le budget de l’éditeur.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Partager :

À propos de l'auteur

WordPress Développement

Développeur WordPress, passionné par Elementor, le FSE et l’automatisation par IA.

Voir tous ses articles

Dans la même veine

À lire aussi