
Mesurer une recherche que personne ne croyait défaillante
Une recherche peut perdre une grande part de ses résultats alors que les pages se chargent, que les tests restent verts et que les premiers résultats paraissent plausibles. Les utilisateurs ne voient pas ce qui a été omis : les signaux produit habituels sont donc de faibles indices du rappel (recall).
Cet article est le quatrième d’une série sur le fonctionnement de la recherche dans freedam. Le premier traitait de la récupération : fabriquer un document interrogeable, fusionner BM25 et pgvector, garantir le rappel sous plafond. Le deuxième écartait le texte du problème pour étudier le hash perceptuel. Le troisième le remettait et demandait dans quelle langue se trouve l’index. Tous trois citent des chiffres. Celui-ci explique d’où viennent ces chiffres.
Nous avons construit un panel de soixante cas de pertinence et l’avons exécuté à la main sur une réplique de la bibliothèque d’un client, qui contient 22 944 médias. La première exécution sérieuse a révélé quinze échecs bloquants. Au cours des huit exécutions suivantes, le panel a corrigé un diagnostic plausible, mesuré le coût d’un correctif et mis au jour un trou dans son propre jeu de cas.
Remplacer « plutôt bien » par une mesure
Aucun problème de recherche n’avait été signalé, et les gens trouvaient assez de médias pour que la bibliothèque paraisse raisonnablement bonne à l’usage courant.
La première exécution sérieuse du panel a renvoyé le code de sortie 1 : quarante-cinq réussites, quinze échecs bloquants, zéro cas en erreur, sur soixante cas, avec une réplique de 22 944 médias d’un vrai catalogue de commerce de détail.
On juge d’ordinaire une recherche à ses premiers résultats. Savoir si 178 autres médias correspondants étaient accessibles est difficile à déduire de l’interface, et peu d’utilisateurs examinent des positions assez profondes pour révéler un problème de limite de candidats. La qualité perçue en dit donc peu sur le rappel au-delà du haut du classement.
Par exemple, le panel a montré que galaxy a52 case renvoyait douze résultats alors que 178 médias portaient cette valeur d’appareil. L’écart est devenu visible une fois l’ensemble attendu écrit et comparé au chemin de production.
Ce que contient un cas de pertinence utile
Un cas de pertinence associe une affirmation sur le corpus à une attente sur le comportement de la recherche : la vérité terrain (ground truth) doit donc survivre aux changements normaux de la réplique.
Chaque cas porte un identifiant, une catégorie, la requête telle qu’un distributeur la saisirait, une vérité terrain, et des assertions réparties en bloquantes et souples. Les assertions bloquantes font échouer l’exécution ; les objectifs souples sont signalés sans changer le verdict, une limite discutée dans la section sur le test de défaillance de l’instrument. Le champ intéressant est la vérité terrain, et surtout le fait que ce n’est presque jamais une liste d’identifiants de médias.
Nous dérivons la plupart des vérités terrain parce que la réplique est remplacée périodiquement à partir de la production et que les identifiants de médias ne survivent pas à ce processus. Un cas lié à 178 identifiants serait en erreur après une nouvelle capture, ce qui rendrait le panel coûteux à maintenir et moins souvent exécuté.
Un cas dérivé déclare donc ce qu’il veut dire plutôt que ce qu’il a trouvé la dernière fois. Quatre types de spécification couvrent le panel : un champ de métadonnées égal à une valeur, un champ correspondant à un préfixe ou à une sous-chaîne, l’appartenance à une collection nommée, et une correspondance de titre au niveau du token. Le résolveur exécute la spécification dans la base au début de chaque exécution et remet à l’évaluateur un nouvel ensemble d’identifiants.
Le schéma autorise aussi une vérité terrain curated, une liste explicite d’identifiants, pour les cas où la pertinence est un jugement plutôt qu’une propriété de la base. Le panel actuel n’en utilise aucune, parce que ces cas demandent de la maintenance après une nouvelle capture, et qu’exprimer une attente comme une spécification révèle souvent une règle qu’on peut dériver à la place.
Les noms de collection ne sont pas uniques, et les clés de métadonnées ne le sont qu’au sein d’une classe de médias : les deux résolveurs peuvent donc légitimement trouver plusieurs enregistrements. Plutôt que de prendre le premier et de réduire silencieusement l’ensemble attendu, le panel marque le cas comme une erreur avec le code de sortie 2. Une erreur explicite se corrige plus facilement qu’un cas réussi sur la base d’une mauvaise vérité terrain.
Douze cas n’ont pas de vérité terrain par identifiants, parce qu’ils couvrent les suggestions « did you mean », les suggestions, les chaînes d’autocomplétion et les requêtes négatives. L’utilité étant en partie subjective, chaque cas d’aide à la saisie consigne une justification fondée sur le menu déroulant réel capturé sur la réplique et relu par une personne.
Consigner les conditions de chaque exécution
Deux choses déterminent si un chiffre de pertinence signifie quelque chose, et toutes deux sont des propriétés de l’instrument et non de la recherche : la configuration qui l’a produit, et ce que le plafond de candidats lui a fait avant que quiconque regarde.
Chaque exécution imprime un instantané des réglages avant le premier cas : limites de candidats, hybrid.rrf_k, poids de fusion, et activation ou non des replis sur les fautes de frappe et les préfixes. Un résultat sur une faute d’orthographe dépend de la correction de frappe, comme le rappel dépend de la limite de candidats : consigner ces valeurs permet de distinguer un changement de code d’un changement de réglages. Les exécutions 1 à 8 ont toutes utilisé hybrid.limit 1000, hybrid.rrf_k 60, des poids de 1,0 et 1,0, avec la correction de frappe activée.
L’instantané montrait aussi prefix.enabled à null sur le tenant utilisé pour les trois premières exécutions, ce qui signifie qu’aucune ligne de réglages n’existait. La valeur par défaut du code était true, de sorte que la recherche par préfixe s’exécutait quand même, mais le rapport indiquait clairement que le comportement venait d’une valeur par défaut et non d’un choix explicite du tenant. Nous consignons désormais les valeurs effectives et les valeurs stockées là où cette distinction compte.
Le plafond de candidats limite aussi ce qu’un cas peut affirmer. Sur ce tenant, une requête qui a plus de 1 000 correspondances plausibles en annonce 1 000 à cause de la fenêtre de candidats, et non parce que le corpus en contient exactement ce nombre. Un ensemble de vérité terrain de 2 189 médias ne peut donc pas exiger l’appartenance complète par ce chemin ; le faire créerait un échec permanent qui rendrait les nouvelles régressions plus difficiles à remarquer.
Une saturation n’excuse pas toute correspondance manquante. Le produit garantit qu’un média dont le titre contient la requête reste retrouvable quels que soient le rang ou le plafond : une correspondance directe de titre manquante reste donc un échec. Le rapport l’étiquette not findable within candidate window, ce qui préserve à la fois le verdict et sa cause probable.
Un détail d’échantillonnage mérite d’être repris. L’échantillon est tiré par hash plutôt que par un tirage aléatoire à graine, si bien qu’un même cas tire les mêmes cinquante médias à chaque exécution, et qu’un rappel échantillonné de 0,76 dans l’exécution 1 contre 0,76 dans l’exécution 2 est une vraie comparaison, et non deux tirages de la même urne.
Regrouper les échecs par mécanisme
Les quinze échecs couvraient sept catégories du panel, mais se réduisaient à six mécanismes sous-jacents.
Le premier mécanisme illustre bien pourquoi les termes d’index demandent une inspection directe.
Les valeurs de compatibilité d’appareil de ce catalogue ressemblent à Galaxy A52/A52S et iPhone 12/12 Pro. L’analyseur anglais de PostgreSQL classe une valeur jointe par une barre oblique comme un seul token de type file : Galaxy A52/A52S est donc indexé comme le lexème a52/a52s, et une recherche de a52 ne peut pas le trouver. Le texte était dans le document ; une recherche de sous-chaîne le trouvait ; le tsvector ne le contenait pas. Dans l’ensemble du corpus, 178 documents contenaient le texte brut A52 et douze contenaient le lexème, ces douze arrivant par des titres séparés par des espaces.
Le symptôme suggérait d’abord des métadonnées manquantes ou un index périmé, mais les métadonnées étaient présentes et la reconstruction produisait les mêmes lexèmes. Nous avons corrigé la normalisation du vocabulaire pour que les libellés joints par une barre oblique émettent leurs segments en plus de la valeur d’origine.
Le dictionnaire de fautes de frappe utilisait un autre tokenizer, qui découpait déjà sur les barres obliques : il signalait donc a52 dans 178 documents alors que BM25 n’en atteignait que douze. Cet écart faisait du dictionnaire un mauvais substitut des termes stockés dans l’index BM25 ; le panel se compare donc désormais au chemin de récupération lui-même.
L’exécution 2 a mesuré séparément le correctif de tokenisation et le changement de portée, dans un plan à quatre points. galaxy a52 case est passé de 12 résultats à 178, avec zéro média introuvable, iphone 12 case d’un rappel échantillonné de 0,06 à 1,00, et les deux cas limités à une collection de 1 sur 28 et 10 sur 24 à l’appartenance complète. Entre le bras sans portée et le bras avec portée, rien d’autre dans le panel n’a bougé : ni un verdict, ni un total, ni un rappel, ni un MRR. La contrainte de conception selon laquelle les recherches sans portée ne paient aucun coût supplémentaire a cessé d’être une affirmation pour devenir une mesure.
Tester un diagnostic plausible
Le correctif de tokenisation a eu un effet de bord. Il a d’abord été livré en appliquant sa règle à tout le document normalisé et non aux seuls libellés de vocabulaire : il découpait donc tout token joint par une barre oblique, quelle qu’en soit la source : vitesses d’obturation EXIF comme 1/100, tensions, URL, références de directives européennes comme 2009/125/EC, conjonctions ordinaires. Environ la moitié du corpus a grossi, et les documents contenant une barre oblique faisaient en moyenne 3 758 caractères contre 917 pour les autres.
BM25 normalise par la longueur du document : tout document qui a grossi a perdu un peu de score sur chaque terme qu’il contenait, et tout document qui n’a pas grossi a gagné du rang gratuitement. Comme prévu, cinq cas ont régressé en recall@10 entre l’exécution 1 et l’exécution 2, dont deux d’un score parfait à zéro. L’analyse allait de soi : l’expansion avait gonflé la longueur des documents, la longueur gonflée avait rétrogradé la vérité terrain, et restreindre l’expansion aux libellés de vocabulaire inverserait cela tout en gardant le gain de rappel.
Nous avons consigné cette prédiction avant l’exécution 3, puis restreint l’expansion, reconstruit le corpus et mesuré les cas concernés.
Le resserrement a fait exactement ce pour quoi il était conçu au niveau du corpus. Le lexème a52 correspondait toujours à 178 documents ; le lexème 2009, qui fuyait depuis 2009/125/EC, correspondait à zéro. Et quatre des cinq inversions prédites n’ont pas bougé d’un cheveu.
Le vrai mécanisme est apparu quand quelqu’un a cessé de regarder les tokens pour regarder les longueurs. La requête greenland était gagnée par onze documents Product Video de 155 à 493 caractères, chacun essentiellement le titre répété selon le poids de son champ, plus un peu de métadonnées de codec. La première image produit, dont se compose la vérité terrain du cas, arrivait au rang 12 avec 751 caractères contre une moyenne de 1 280 pour la vérité terrain. Un rapport de longueur de trois à huit suffit à décider d’un classement à lui seul, et le document à la frontière ne contenait aucune barre oblique : aucune des deux versions de l’expansion ne l’avait jamais touché.
Le diagnostic était cohérent et assez précis pour passer une relecture, mais la prédiction par cas l’a rendu testable. La mesure a montré que quatre des cinq inversions attendues ne se sont pas produites, ce qui a réorienté l’enquête vers la longueur des documents plutôt que vers l’expansion des tokens.
Le resserrement a été livré malgré tout, car c’était toujours le bon changement. Il a aussi coûté un cas. compat-iphonexxs-01, la requête iphone x case, est passé d’un rappel échantillonné de 1,00 à 0,74 et d’une réussite à un échec bloquant, parce que son ensemble de résultats sature au plafond à chaque exécution et que l’expansion trop large avait allongé ses concurrents juste assez pour admettre plus de médias iPhone X et Xs. On retire la pollution, les concurrents raccourcissent, obtiennent un meilleur score et reviennent se presser. Le panel est passé de 48 réussites à 47, et le compromis a été consigné avec le correctif.
Le classement dans une fenêtre plafonnée est proche d’un jeu à somme nulle : une amélioration peut donc en déplacer un autre résultat. Quand un changement ne montre que des gains, nous vérifions si le jeu de cas couvre ce qui a reculé. Les régressions connues restent dans le rapport avec leur justification, pour qu’on les distingue des nouveaux échecs.
L’habitude utile est de consigner des prédictions par cas avant l’exécution, y compris pour les cas qui ne doivent pas bouger. L’exécution 2 a confirmé dix prédictions, l’exécution 3 une sur cinq ; les deux étaient instructives, car les contrôles négatifs ont montré si le mécanisme proposé était le bon.
Un panel ne voit que ce que ses cas couvrent
L’exécution 4 était un canari pour une autre fonctionnalité : la garantie de retrouvabilité par le titre, qui promet qu’un média dont le titre contient la requête est retrouvable par cette requête quels que soient le rang ou le plafond. Bras témoin désactivé, bras traité activé, même corpus, même build, mêmes réglages. Une réserve accompagne désormais chaque chiffre : l’exécution 4 a mesuré une réplique fraîche de la production, volontairement non réindexée depuis la livraison du correctif de tokenisation, et son corpus est donc celui d’avant le correctif. Les deux bras le partagent, ce qui suffit à un A/B, mais les verdicts de l’exécution 4 ne sont pas comparables cas par cas à ceux de l’exécution 3, même quand les totaux coïncident.
Le résultat était plat sur les verdicts, 47 réussites dans les deux bras, et sans ambiguïté sur les métriques. Le recall@10 moyen est tombé de 0,6667 à 0,6333, le recall@50 moyen de 0,7446 à 0,7338, et trois cas ont régressé. Aucun cas ne s’est amélioré sur aucune métrique de pertinence.
Pris seul, le résultat suggérait que la fonctionnalité réduisait la pertinence sans rien apporter. Pourtant, aucun des soixante cas ne demandait si un média au-delà du plafond restait retrouvable par son titre, ce qui était le contrat de la fonctionnalité. Les cas existants utilisaient une vérité terrain dérivée des métadonnées : le panel pouvait donc mesurer le coût, mais pas le bénéfice visé.
L’exécution 4 a donc identifié un trou de couverture, et l’exécution 5 a ajouté trois cas de retrouvabilité par le titre.
Le cas témoin est celui qui ressemble à du remplissage. Sa bande de titres compte 188 médias : sous le plafond de 1 000 candidats, et sous le seuil de 200 médias à partir duquel les assertions d’appartenance se dégradent en échantillonnage. Rien n’est jamais coupé : il réussit avec la garantie désactivée comme activée, à l’identique dans les deux bras.
Un cas utilise une bande de titres sous le plafond de candidats et doit réussir avec la fonctionnalité désactivée comme activée. Ce témoin distingue la garantie voulue d’un changement plus large qui réordonne des résultats déjà accessibles sans elle. Il est resté vert et inchangé pendant que les deux cas au-delà du plafond mesuraient le nouveau comportement.
L’ajout a aussi permis au panel de voir ce que les soixante cas ne pouvaient jamais voir. title-product-01 a renvoyé 2 587 résultats pour une vérité terrain de 2 156, soit 431 correspondances hors titre qui ont survécu à côté d’une bande plus de deux fois supérieure au plafond. Une version antérieure de la garantie avait numéroté ensemble la bande et le groupe classé : la bande prenait toutes les positions sous le plafond, était réadmise de toute façon par sa propre clause, et supprimait silencieusement les correspondances classées qu’elle déplaçait. Ces 431 médias sont ce correctif, rendu visible sur un vrai corpus.
Le panel a aussi montré que la première tentative de rétrogradation des lignes de la bande de titres n’avait aucun effet : l’exécution 6 était identique octet pour octet à l’exécution 5b. L’ordre avait été modifié dans une sous-requête, mais les deux consommateurs qui déterminent l’ordre visible par l’utilisateur ne le préservaient pas. Appliquer la rétrogradation dans les deux consommateurs a supprimé les régressions mesurées sur les soixante-trois cas, tandis que les deux cas de titre au-delà du plafond continuaient de réussir.
Le rejeu de journal de requêtes mesure le changement, pas la justesse
Tout ce qui précède vient d’un seul instrument, avec un angle mort fixe. Ses cas sondent des modes de défaillance que nous soupçonnions déjà et sa vérité terrain est dérivée des métadonnées : il répond précisément à « le bon élément est-il retrouvable », mais pas à « un vrai utilisateur le remarquerait-il ». L’expérience sur la retrouvabilité par le titre montrait plus haut pourquoi cette distinction compte.
Le second instrument rejoue des requêtes de production sans vérité terrain. Il compare deux bras sur le même corpus et rapporte ce qui a bougé : il mesure donc le changement et non la justesse. Un top dix identique est inchangé mais non vérifié, et un ensemble de résultats plus grand contient plus de médias accessibles sans prouver qu’ils sont pertinents.
La source contenait 140 recherches couvrant 99 termes distincts sur dix semaines, sur un seul tenant de production. Nous n’avons extrait que le terme, le nombre d’occurrences, le type de recherche et le nombre de résultats ; les identifiants d’utilisateur et de session n’étaient pas inclus, car la comparaison n’en avait pas besoin.
Sur les 99 termes distincts, 55 étaient des recherches par mot-clé et 44 des arbres de filtres avancés sérialisés. Nous avons exclu les arbres : ceux qui n’ont pas de feuille plein texte n’atteignent jamais le chemin hybride et ne pouvaient donc pas changer dans cette comparaison. Les recherches restantes comprenaient des SKU, des noms de fichiers, des expressions de marque, des descriptions, des variantes de casse, une faute d’orthographe, un terme danois et un collage de 79 SKU.
Le top dix est resté identique pour les 55 recherches. Cinq totaux ont augmenté, chacun pour une requête déjà au plafond de 1 000 candidats : le changement n’a donc touché que l’accessibilité au-delà du plafond : greenland est passé de 1 000 à 1 014, iphone à 1 010, et phone et blue products à 1 005. Aucune requête n’a perdu de résultats, et les deux requêtes vides sont restées vides.
C’est une preuve sur la portée du changement, pas sur sa qualité. Le panel constitué à la main se concentre volontairement sur des mécanismes sensibles, tandis que le rejeu reflète les requêtes que les clients ont faites par hasard ; les deux instruments répondent à des questions différentes.
Le rejeu a trouvé une chose que le panel ne pouvait pas trouver. Une fois la bande intégrée, la limite de candidats rapportée est devenue null sur 52 des 55 requêtes alors que l’indicateur de troncature restait activé sur toutes : l’interface passe de « peut être tronqué, limite 1000 » à « peut être tronqué, profondeur non descriptible par un seul nombre ». C’est honnête, et c’est une décision de formulation à prendre avant le déploiement plutôt qu’à découvrir après.
La première mesure de latence suggérait à tort que la bande de titres accélérait la recherche : les bras s’exécutaient l’un après l’autre et le second héritait de caches chauds. En comparant des exécutions à chaud avec des exécutions à chaud, il restait environ cinq millisecondes de différence moyenne, les requêtes individuelles les plus lentes étant moins bonnes de huit à dix millisecondes. Les deux bras ont donc besoin des mêmes conditions de cache.
La limite est l’échantillon. Cinquante-cinq termes distincts d’un seul tenant sur dix semaines, orientés vers les recherches de SKU, forment une petite fenêtre sur exactement la forme de requête la moins susceptible d’être affectée par ce qu’on teste.
Vérifier que les contrôles peuvent échouer
L’instrument lui-même demandait aussi un test de défaillance. Nous avons trouvé quatre contrôles qui apparaissaient dans la sortie des tests ou de la supervision mais produisaient le même signal quand le comportement qu’ils surveillaient était cassé.
Un code de sortie calculé que rien ne consommait. Le runner renvoyait soigneusement 0 pour une réussite, 1 pour des échecs bloquants et 2 pour des erreurs de configuration. Le trait qui l’invoquait jetait l’entier et ne réagissait qu’à une exception levée, et le runner était conçu pour ne jamais lever d’exception : toute invocation réelle sortait donc avec 0, quel que soit le nombre de cas en échec. Les tests propres de la suite passaient, parce que l’environnement de test prend une branche antérieure qui propage bien la valeur de retour. Le seul chemin exercé était le seul chemin correct.
Des métriques qui rapportent mais ne peuvent pas bloquer. Les objectifs souples émettent une note disant que le premier résultat pertinent est arrivé au rang 17 pour un objectif de 10, et les notes ne changent pas un code de sortie. Après le changement du constructeur de documents, cinq cas ont perdu la pertinence dans le top dix, dont deux passés d’un score parfait à zéro, et quatre d’entre eux indiquaient encore pass.
Une supervision qui rapportait le silence pendant que la tâche surveillée mourait, parce qu’elle ne filtrait que les marqueurs de progression et qu’un plantage n’en produit aucun. Un test de départage resté vert après suppression du départage, parce que l’assertion se vérifiait pour des raisons sans rapport avec le comportement qu’elle nommait.
Dans chaque cas, un signal d’échec existait, mais la couche suivante ne le consommait pas selon le même contrat.
Pour chaque barrière, nous demandons maintenant ce qu’elle afficherait si le comportement surveillé cassait, puis nous introduisons une défaillance contrôlée et vérifions que le résultat passe au rouge. Les deux défauts trouvés par cette méthode étaient plus faciles à localiser que les deux découverts par la lecture du code.
Le défaut du code de sortie aurait fait annoncer un succès aux exécutions complètes sur la réplique, quel que soit le nombre d’échecs bloquants. Les objectifs souples restent diagnostiques plutôt que bloquants : le rapport les étiquette donc clairement, sinon une métrique affichée se prend facilement pour une garantie appliquée.
Ce que nous dirions à quiconque construit cela
La version transposable, débarrassée de nos particularités.
- Partez du principe que vous avez tort sur votre propre recherche, jusqu’à ce qu’un instrument vous contredise. La qualité perçue n’a aucune résolution sous les trois premiers résultats : un système peut donc perdre la plus grande partie d’un ensemble de résultats et paraître correct à tous ceux qui l’utilisent. Quinze échecs sur soixante cas, et pas un seul ticket de support.
- Exprimez la vérité terrain comme une spécification, pas comme une liste d’identifiants. Un panel qui meurt quand le corpus est remplacé cesse d’être exécuté, et une suite que personne n’exécute est supprimée dans le mois. Les cas constitués à la main sont de futurs événements de maintenance : gardez-les près de zéro.
- Faites énoncer au rapport la configuration qu’il a mesurée, et imprimez les valeurs stockées plutôt que les valeurs effectives. Un chiffre de rappel sans son plafond de candidats n’est qu’une rumeur, et l’écart entre stocké et effectif dit si quelqu’un a réellement décidé.
- Dégradez l’assertion sous un plafond, jamais le verdict. Échantillonnez au lieu d’exiger l’appartenance complète, mais gardez une correspondance directe manquante comme échec bloquant, sinon l’instrument devient aveugle à la classe de défauts pour laquelle il a été construit.
- Classez les échecs par mécanisme avant d’en corriger un seul. Cinq échecs dans une catégorie étaient trois bogues demandant trois correctifs, et un seul concernait les limites de récupération. Corriger une cause déplace plusieurs cas à la fois ; corriger un cas en déplace un et en casse un autre.
- Écrivez la prédiction avant l’exécution, cas par cas, y compris ce qui ne doit pas bouger, et attendez-vous à payer une facture. Les prédictions négatives révèlent un mauvais diagnostic. Le classement est proche d’un jeu à somme nulle dans un plafond : un changement qui ne montre que des améliorations signifie en général que rien dans votre jeu de cas ne pointe vers ce que vous avez déplacé.
- Vérifiez que votre instrument peut voir ce que vous lui demandez, et incluez un témoin qui doit réussir avec la fonctionnalité désactivée. Zéro meilleur et trois pires était une mesure vraie et une conclusion fausse, parce qu’aucun cas n’exerçait le contrat testé. Un jeu de cas où tout ne réussit qu’avec la fonctionnalité activée montre que les cas ont été écrits en fonction de la réponse.
- Utilisez deux instruments aux angles morts différents, et demandez pour chaque barrière ce qu’elle afficherait si ce qu’elle surveille cassait. Un panel constitué à la main connaît la bonne réponse mais pas ce que quelqu’un a demandé ; un journal de requêtes sait ce que tout le monde a demandé mais pas la bonne réponse. Puis cassez vos contrôles exprès et vérifiez qu’ils passent au rouge, car la lecture ne les trouve que si vous étiez déjà méfiant.
Où cela se trouve dans le produit
Rien de tout cela n’est une fonctionnalité. C’est l’instrument qui décide quelles fonctionnalités sont livrées, et sa sortie n’est visible que dans le comportement des recherches ordinaires : la galerie, l’API REST, le SDK TypeScript et tout agent IA connecté via MCP. Les correctifs qu’il a arbitrés sont ceux décrits dans le premier article : les garanties de rappel sous plafond, la sélection des candidats, le rapport d’exhaustivité qui permet à un résultat tronqué de le dire. Le même sujet, vu du produit, est la recherche et la découverte, et le fait que tout cela s’exécute dans une seule instance PostgreSQL est ce qui le rend reproductible sur une réplique, ce qui rend aussi freedam praticable en auto-hébergement.
Les limites méritent d’être énoncées aussi simplement que les résultats, car un instrument qui se survend est exactement la défaillance dont parle cet article. Le panel est exécuté à la main et volontairement hors de l’intégration continue : il demande une réplique d’un vrai corpus client, un sidecar d’embeddings et environ quatre-vingt-dix secondes par exécution, et ses chiffres ne signifient rien sur un autre corpus. Ses requêtes sont uniquement en anglais, parce que le catalogue pour lequel il a été construit est en anglais : il ne mesure donc rien du comportement multilingue décrit dans l’article précédent. Ses cas résolvent leur vérité terrain de nouveau à chaque exécution et survivent à une nouvelle capture, mais un corpus modifié modifie quand même les chiffres : les exécutions 4 à 8 ont mesuré une réplique fraîche de la production, non réindexée depuis le correctif de tokenisation, et ne sont donc pas comparables cas par cas à l’exécution 3, même quand les totaux coïncident.
Prochain article de la série : reconnaissance faciale et recherche de personnes, où les problèmes intéressants cessent de porter sur la qualité de la récupération pour porter sur qui est autorisé à affirmer que deux visages sont une même personne.



