panneau d'affichage couvert d'annonces datées

Empêcher un chatbot RAG de présenter une information périmée comme actuelle

Sur le chatbot d’un site communal, une question très simple a donné une réponse fausse : qui s’occupe des finances ? Le bot a répondu avec un nom, une fonction et le présent de l’indicatif.

Le nom existait bien. La fonction était juste, mais elle l’était en 2019 : la personne avait quitté l’exécutif à la législature suivante. La réponse venait d’un procès-verbal en PDF, indexé comme le reste du site.

Le bot n’a rien halluciné. Il a présenté une information périmée comme la situation du jour, avec la même assurance que pour le reste. C’est le genre d’erreur qui coûte cher en confiance.

Pourquoi un RAG sert une information périmée sans le savoir

Deux raisons se cumulent : l’archive gagne au score, et rien ne signale qu’elle parle du passé.

Un procès-verbal contient des phrases complètes : M. X, municipal en charge des finances, présente le budget. La page officielle de l’exécutif, elle, ressemble à une liste de noms avec des intitulés à côté.

Pour la question qui est en charge des finances ?, le PDF de 2019 est donc un meilleur match que la page correcte : il est plus bavard et plus proche de la formulation.

Le vrai problème vient ensuite. Une fois le chunk récupéré, rien dedans ne dit qu’il décrit le passé.

Le modèle reçoit un paragraphe au présent, sans date. Il n’a aucun moyen de savoir que ce présent a cinq ans, alors il le recopie.

Forcer la page à jour dans le contexte

Première parade : la page officielle entre dans le contexte sans passer par le score, et les archives en sortent.

if (this.isAuthorityContext(ctx)) {
  filters.pdf = false;
  filters.linked = ['https://example.ch/executif'];
  filters.linkedAlwaysInclude = true;
}

Sauf que ce bloc ne sert à rien s’il ne se déclenche pas. Au départ, il dépendait du routage : la question devait être classée comme portant sur les personnes ou sur les autorités.

En pratique, le routage passait à côté de la moitié des cas. Qui s’occupe du tourisme ? ne contient aucun mot qui ressemble à une autorité.

J’ai donc arrêté de faire confiance au routage et détecté ces questions au vocabulaire.

// les mots de fonction, avec une garde de chaque côté pour ne pas attraper
// un mot plus long qui les contient
const ROLES = /(?<!\p{L})(?:syndic|syndique|municipalité|municipa(?:l|ux)|dicastères?)(?!\p{L})/iu;

// une tournure de responsabilité, puis un domaine, à moins de 40 caractères
const AREAS = /(?:responsable|chargée?|s'occupe|en charge)[^.?!]{0,40}(?:finances|tourisme|voirie|sports|culture)/iu;

private isAuthorityContext(ctx: RequestContext): boolean {
  if (ctx.hasRoute('people') || ctx.hasRoute('government')) {
    return true;
  }
  const question = ctx.getCleanedQuestion() ?? '';

  return ROLES.test(question) || AREAS.test(question);
}

Puis un deuxième trou est apparu, plus vicieux : la question qui ne contient qu’un nom. Prénom Nom, sans verbe et sans fonction.

Aucun vocabulaire de rôle à détecter, donc la règle ne se déclenche pas et les archives repassent devant. Il a fallu une détection séparée.

Dater les archives pour que le modèle parle au passé

Exclure les archives règle un cas et en casse un autre. Mieux vaut les garder et rendre leur âge lisible.

Sortir les archives supprime bien l’information périmée sur la composition actuelle. Mais si on demande une personne qui n’est plus en poste, l’archive est le seul endroit du site où elle apparaît, et le bot répond alors qu’il ne trouve rien.

La parade se joue à l’indexation, pas dans le prompt. Chaque chunk d’un PDF d’archive est préfixé par sa date et par une mise en garde.

private markArchiveDocumentAsDated(document: ZaasDocument): void {
  if (document.metadata.type !== 'pdf') {
    return;
  }
  const date = this.getArchiveDateLabel(document);
  document.pageContent =
    `[Document d'archive${date ? ` daté du ${date}` : ''}, décrit une situation ` +
    `passée, ne pas présenter son contenu comme la situation actuelle]\n` +
    document.pageContent;
}

La date se cherche en cascade : la métadonnée du document, sinon une date dans l’URL, sinon juste l’année. Une année suffit à faire basculer le modèle au passé.

Le prompt système vient ensuite poser la hiérarchie des sources, en s’appuyant sur ce marqueur :

  • un procès-verbal, un préavis ou un rapport de gestion décrit une situation passée et ne prouve jamais qu’une personne occupe actuellement une fonction ;
  • les extraits d’archives sont introduits par [Document d'archive daté du ...], il faut employer le passé pour tout ce qu’ils décrivent ;
  • la seule source faisant foi pour la composition actuelle est la page officielle à jour de l’exécutif ;
  • si la personne demandée ne figure pas sur cette page, il faut le dire explicitement, puis donner au passé la fonction qu’elle occupait selon les archives.

La dernière règle est celle qui change l’expérience. Le bot ne se contente plus d’éviter l’erreur, il explique : cette personne a été municipale en charge de tel domaine, elle ne l’est plus, et voici l’exécutif actuel.

Le petit problème : le titre au mauvais genre

Un dernier détail m’a demandé trois essais, et c’est lui qui donne la leçon la plus réutilisable.

À la question qui est la syndique ?, le bot répondait La syndique est M. X. Il reprenait le genre de la question au lieu de celui de la personne.

Ma première règle disait accorde le titre au genre réel de la personne. Elle n’a rien changé, et pour une bonne raison : le modèle ne peut pas connaître le genre de quelqu’un à partir de son nom. Je lui demandais de deviner.

La deuxième disait reprends le titre tel qu’il figure sur la page officielle. Mieux, mais le bot dérivait dès que la question insistait.

La troisième a fonctionné, parce qu’elle pointe un marqueur concret présent dans le texte récupéré :

Ignore la forme employée dans la question. Détermine le genre de la personne en fonction d’après la page officielle à jour (M. = homme, Mme = femme), puis emploie le syndic pour un homme et la syndique pour une femme.

Une règle de prompt qui demande au modèle de déduire quelque chose échoue. Une règle qui lui montre où regarder dans le contexte tient.

Et la parade générale tient en deux couches : le retrieval doit garantir que la source à jour est présente sans dépendre d’un score, et l’indexation doit rendre l’âge d’un document lisible dans le texte. Ni le prompt seul ni la recherche vectorielle seule n’y arrivent, et c’est pour ça qu’une information périmée continue de passer chez ceux qui ne règlent qu’une seule des deux.

À lire aussi

Laisser un commentaire