Pourquoi nous ne dédupliquons pas les faits de conformité par similarité d'embeddings
Dans notre évaluation de déduplication en mémoire, les contradictions affichaient une similarité cosinus moyenne de 0,938 avec leur fait stocké le plus proche, tandis que les doublons réels atteignaient 0,940. Aucun seuil unique ne permettait de séparer clairement les paires qui ne devaient pas fusionner de celles qui le devaient. Ainsi, le cosinus ne prend jamais la décision de fusion dans notre pipeline.

Un score de similarité polyvalent répond bien à une question : ces deux déclarations parlent-elles de la même chose ? Sur le modèle que nous avons mesuré, il n’a pas répondu à la question dont une mémoire de faits de conformité a réellement besoin : ces deux déclarations sont-elles d’accord ? Nous avons mesuré l’écart sur nos propres données, et ce n’est pas une marge que l’on peut ajuster. Dans notre évaluation de déduplication en mémoire, les doublons réels affichaient une similarité cosinus moyenne de 0,940 avec leur fait stocké le plus proche, tandis que les contradictions directes atteignaient 0,938, et aucun seuil global unique dans ces données ne permettait de séparer clairement les reformulations que nous souhaitions supprimer des contradictions que nous ne pouvions pas fusionner. Nous appelons cela le piège de la thématique, et c’est pourquoi, dans notre pipeline, le cosinus ne prend jamais la décision de fusion.
L’échec est silencieux et unidirectionnel. Une déduplication manquée laisse une ligne redondante, ce qui est cosmétique. Une fusion erronée supprime l’un des deux faits qui ne sont pas d’accord, et si le fait qu’elle supprime est « L’authentification multifactorielle (MFA) n’est pas appliquée à tous les utilisateurs », un assistant destiné aux audits détient désormais l’opposé de la vérité. Nous traitons ces deux erreurs comme catégoriquement différentes, et toute la conception découle de cette asymétrie.
La mesure
L’évaluation exécute le chemin de décision réel du backend (la même fonction evaluateCandidate utilisée par le pipeline) sur un jeu de données synthétique, adversarial et sans PII, composé de paires de déclarations GRC. Chaque paire est étiquetée selon sa relation : un doublon reformulé à supprimer, ou l’une des trois autres catégories qui ne doivent pas fusionner — un fait distinct différant d’un seul token, une contradiction directe, ou un affinement de portée. Résultat enregistré : 2026-06-25, mistral-embed (le modèle v23.12 derrière l’alias) pour les vecteurs et un petit juge Mistral (mistral-small-2603, température 0) pour la décision, 261 paires, 522 appels au modèle, environ 0,036 $.
L’étude sur les embeddings est celle qui mérite toute notre attention. Similarité cosinus moyenne du candidat avec son fait stocké le plus proche, par relation (mistral-embed v23.12, résultat du 2026-06-25) :
| relation | similarité cosinus moyenne | n |
|---|---|---|
| doublon | 0,940 | 10 |
| contradiction | 0,938 | 7 |
| non-doublon (différence d’un token) | 0,902 | 239 |
| affinement | 0,847 | 5 |
Lisez les deux premières lignes. La moyenne des contradictions se situe à 0,002 en dessous de la moyenne des reformulations d’un même fait. Le mot unique qui inverse la valeur de vérité (« pas », « non ») déplace à peine le vecteur, car les deux phrases portent massivement sur l’application du MFA, et sur ce modèle, la proximité suit bien plus le sujet partagé que la négation.
Les faits distincts différant d’un seul token sont presque aussi proches. « Nous avons terminé un rapport SOC 2 Type 1 » versus « Type 2 » n’est pas une question de pédanterie : un rapport SOC 2 Type 1 atteste de la conception des contrôles à une date donnée, tandis qu’un rapport Type 2 atteste en plus de leur efficacité opérationnelle sur une période spécifiée (Guide de reporting SOC 2 de l’AICPA). Les fusionner revient à indiquer à l’équipe sécurité d’un prospect que nous détenons une preuve que nous n’avons pas. Dans un test smoke end-to-end contre les modules réels, « SOC 2 Type 1 » et « Type 2 » ont été embeddés avec une similarité cosinus de 0,961, supérieure à la moyenne des doublons réels du tableau. Cette seule instance est la plus importante : une paire qui ne doit jamais fusionner, avec un score supérieur à la moyenne des paires qui le devraient. La classe des non-doublons affichait en moyenne 0,902.
Pourquoi un seuil unique ne les sépare pas
L’instinct, une fois que l’on voit qu’un doublon a un score élevé, est de définir un seuil de fusion juste en dessous. Nos données s’y opposent de deux manières. Les moyennes des classes ne se séparent pas : 0,940 pour les doublons et 0,938 pour les contradictions sont, pour le seuillage, le même nombre. Et nous avons un contre-exemple concret au-dessus de ce nombre : la paire Type 1 vs Type 2 à 0,961, une paire qui ne doit pas fusionner et qui dépasse la moyenne des doublons réels. Un seuil global suffisamment bas pour supprimer les reformulations quotidiennes supprimerait, sur ces données, également des faits que nous avons mesurés comme devant rester distincts. Nous ne prétendons pas avoir prouvé qu’aucune frontière séparatrice ne peut exister pour un jeu de données ou un modèle quelconque. Nous rapportons simplement que, sur ce jeu de données GRC adversarial, avec ce modèle d’embeddings, aucun seuil global unique de similarité cosinus ne permettait de séparer les paires sûres à fusionner des paires dont la fusion serait fatale.
Ainsi, dans notre pipeline, les embeddings n’ont qu’une seule fonction : la recherche. Le seuil minimal de similarité cosinus est fixé à 0,55, délibérément bas, et il ne sert qu’à sélectionner quel fait stocké mérite d’être comparé au candidat. Il ne décide pas s’il faut fusionner. Une fois le voisin le plus proche identifié, la décision est prise par un mécanisme qui examine les tokens que le cosinus sous-évalue.
L’architecture qui maintient les contradictions à distance
Deux couches, et la couche déterministe est autoritaire.
Premièrement, une vérification déterministe des tokens protégés. Nous comparons le candidat avec son voisin récupéré pour les tokens porteurs de sens en conformité : la négation (« pas », « plus »), les quantificateurs et la portée (« tous », « uniquement », « administrateurs »), les juridictions et régions, les versions de référentiels, les types SOC 2, les nombres et unités. Une différence unilatérale dans une classe protégée bloque la fusion, et elle prime sur le juge, quel que soit le résultat de ce dernier. « Le MFA est appliqué » versus « Le MFA n’est pas appliqué » est bloqué par la règle de négation, quelle que soit la similarité apparente des deux phrases.
Deuxièmement, un juge conservateur. Le modèle doit atteindre un niveau de confiance de 0,8 pour supprimer un candidat en tant que doublon ; en dessous, il conserve les deux lignes. Il est biaisé vers la conservation, car les deux directions d’erreur ne sont pas symétriques : un doublon conservé est cosmétique, une contradiction fusionnée est une corruption.
Le seuil que le système doit franchir : zéro fusion erronée sur les 251 paires qui ne doivent pas fusionner. Il l’a franchi, avec zéro fusion erronée observée. En règle empirique, 3/251 représente environ 1,2 %, mais sur un jeu fixe et curaté, cela reste un indicateur heuristique, et non une limite de confiance réelle ni une garantie de production. La propriété sur laquelle nous nous appuyons réellement est plus étroite et plus solide : la vérification déterministe seule, sans modèle dans la boucle, a bloqué les 251 paires, donc le plancher est maintenu par du code lisible plutôt que par un modèle à qui il faut faire confiance.
Ce qui a trompé le juge
La partie honnête. Une évaluation de nettoyage complémentaire deux jours plus tard, le 2026-06-27, a révélé une fausse fusion qu’une version antérieure de la vérification avait manquée : « Le SSO est disponible pour les clients entreprise » versus « Le SSO est disponible pour tous les clients ». Le modèle avait jugé que la déclaration plus large était un affinement inoffensif de la plus étroite et avait fusionné. C’est le piège de la thématique à un niveau supérieur : les deux phrases portent presque entièrement sur la disponibilité du SSO, et le juge, comme l’embedding, sous-évalue le quantificateur qui modifie à qui s’applique le fait. La solution a consisté à déplacer un changement unilatéral dans une classe porteuse de portée (quantificateurs, sujets, négation, modalité) dans la vérification déterministe plutôt que de le laisser à l’appréciation du juge, ainsi qu’à ajouter une règle explicite de portée d’applicabilité dans l’invite du juge en tant que défense en profondeur. Après la correction, cette évaluation a montré zéro fausse fusion, 18 paires distinctes sur 18 maintenues séparées, et 8 doublons réels sur 8 rappelés.
Le schéma se répète à travers les couches. L’embedding sous-évalue structurellement la négation et la portée sur ce modèle. Le juge sous-évalue ces éléments de manière plus subtile. La couche qui les a fiablement détectés dans nos tests est celle qui nomme les tokens exacts qui inversent un fait de conformité, et cette couche est un code déterministe, et non un autre modèle chargé d’être prudent.
Limites
Il s’agit d’une porte de qualité décisionnelle sur des paires étiquetées, un candidat contre son voisin apparié, et non un test de charge de récupération à grande échelle. Les classes des contradictions et des affinements sont petites (7 et 5 paires), donc leurs moyennes sont indicatives, et non des estimations précises. Les valeurs de similarité cosinus sont spécifiques à mistral-embed v23.12 sur ces phrases GRC ; un autre modèle d’embeddings les modifierait, et l’insensibilité à la négation que nous avons observée est spécifique au modèle que nous avons mesuré, et non une loi des embeddings de similarité. Les modèles entraînés par contraste peuvent être ajustés pour éloigner les contradictions, en les utilisant comme négatifs difficiles (SimCSE, EMNLP 2021), donc la bonne interprétation est « mesurez votre propre modèle », et non « les embeddings ne pourront jamais faire cela ». Le rappel des doublons dans l’exécution de déduplication était de 0,5, cinq des dix doublons réels ayant été supprimés. C’est le prix que nous avons choisi : le juge conserve les deux lignes chaque fois qu’il est incertain, donc le rappel est le levier que nous ajustons tandis que le nombre observé de fausses fusions reste à zéro. Nous préférons porter quelques faits redondants plutôt que de fusionner deux faits qui ne sont pas d’accord.
Le piège de la thématique, et une liste de vérification
L’idée à retenir de cet article : sur le modèle que nous avons mesuré, un score de similarité élevé signifiait que deux déclarations parlaient de la même chose, et non qu’elles étaient d’accord, et la déduplication nécessite de l’accord. Lorsque cette mémoire constitue une information documentée dont un SMSI dépend, l’ISO/IEC 27001:2022, article 7.5.3, exige qu’elle soit protégée contre la perte d’intégrité (ISO/IEC 27001:2022, article 7.5.3, édition 2022-10) ; si une fusion silencieuse rend un enregistrement requis inexact, cela constitue une perte d’intégrité au sens propre. Si vous construisez une mémoire ou une couche de connaissances avec une déduplication sémantique, testez-la avec :
- Mesurez la distribution, ne faites pas d’hypothèses. Embeddez des paires de doublons, de contradictions et de faits presque distincts étiquetés à partir de votre propre domaine, sur votre propre modèle, et examinez la similarité cosinus par relation. Si les scores des contradictions se chevauchent avec ceux des doublons, aucun seuil basé sur ce score ne pourra les séparer.
- Utilisez les embeddings pour la recherche, pas pour la décision de fusion. Un seuil minimal de similarité cosinus bas pour sélectionner un voisin à comparer est acceptable. Utiliser ce même score pour décider d’une fusion revient à supprimer silencieusement une contradiction.
- Placez une vérification déterministe en amont de la fusion. Énumérez les tokens qui inversent le sens dans votre domaine (négation, quantificateurs, portée, versions, juridictions, unités) et laissez un changement unilatéral sur l’un d’eux bloquer la fusion, quel que soit le résultat du modèle.
- Rendez le juge conservateur et asymétrique. Biaisez-le pour qu’il conserve les deux lignes. Décidez quelle erreur est cosmétique et laquelle est une corruption, et ne laissez jamais le modèle échanger la seconde pour éviter la première.
- Contrôlez le taux d’erreurs fatales à zéro, ajustez le rappel séparément. Maintenez les fausses fusions à zéro comme seuil ; améliorez le rappel des doublons tout en conservant ce même seuil sans le déplacer.
- Intégrez les quasi-erreurs dans votre jeu de données. La paire qui élargit la portée et ressemble à un affinement est celle qui se fait fusionner. Les nombres écrits en toutes lettres, les acronymes en minuscules et les changements de quantificateurs sont là où un juge échoue discrètement.
Toutes les données ci-dessus sont nos propres mesures sur nos propres jeux de données GRC synthétiques, avec mistral-embed v23.12 et mistral-small-2603 à température 0, datées du 2026-06-25 et du 2026-06-27. Le jeu de données ne contient aucune donnée client.
Articles connexes

Le test de symétrie : livrer une correction de prompt que vous ne pouvez pas reproduire
Quand un bug de production ne se reproduit pas hors ligne, vous ne pouvez pas valider une correction de prompt en vérifiant qu’elle fonctionne. Vous la validez en vérifiant si votre changement déplace la sortie dans une direction cohérente ou s’ajoute simplement au bruit de variabilité du modèle. Voici le test que nous avons mené sur 126 paires A/B en aveugle.

La loterie de l'ID de modèle : même requête, tirage différent
Derrière une passerelle multi-fournisseurs, le même ID de modèle a produit son premier token à un temps médian de 312 ms sans sortie de raisonnement un jour, et à 3 073 ms avec 2 627 caractères de raisonnement des jours plus tard. Le drapeau de routage que nous attendions pour éviter ce problème n'a pas fonctionné.

La collision de vocabulaire : quand un classificateur de sécurité marque tout votre domaine
Un classificateur de modération polyvalent a marqué 15 messages sur 15 comme incorrects sur une période de 17 jours dans notre produit de conformité, car nos utilisateurs discutent de menaces au quotidien. La solution a déplacé le risque plutôt que de l'éliminer.
