Aller au contenu
English

Mises à jour et suppressions

  • Leçon 16
  • 50 min
  • Module 6 : Maintenir et protéger la base de connaissances

Ce que vous saurez faire

Gardez l’index conforme à la collection pendant que les documents changent. Lisez seulement les fichiers modifiés, retirez les anciennes versions selon leurs dates et refusez les doublons. Faites disparaître un document supprimé de la recherche et des réponses.

Avant de commencerCe qu’il faut savoir et avoir sous la mainEnviron 50 minutes

Ce que vous devez savoir

Ce qu’il vous faut sur votre ordinateur

  • Le projet à la fin du module 5 (l’instantané end-of-m05). python -m pytest affiche 77 passed, 1 deselected (77 tests réussis, 1 exclu).
  • Un index créé avec python -m policy_assistant ingest, et un terminal dans ~/projects/policy-assistant avec .venv actif.

Aucun appel de modèle et aucun compte : les réponses viennent des enregistrements. Testé avec Python 3.12.13 sur macOS (Apple silicon). Les commandes Windows ne sont pas testées.

Une bibliothèque reçoit de nouveaux livres chaque semaine. La bibliothécaire ne refait pas tout le catalogue chaque fois. Elle compare la livraison au catalogue. Un nouveau livre reçoit une fiche, et une nouvelle édition remplace l’ancienne fiche. Un livre inchangé ne demande aucun travail.

Retirer un livre est plus difficile qu’il n’y paraît. Le livre quitte le rayon. Mais sa fiche reste au catalogue, il reste dans la liste des « nouveautés », et les notes d’un lecteur le citent encore. Tant que toutes les copies de la référence ne sont pas supprimées, les gens continuent de demander ce livre. Pourtant, la bibliothèque ne l’a plus.

Le problème : la recherche trouve encore un document supprimé

Section intitulée « Le problème : la recherche trouve encore un document supprimé »

La commande ingest du module 5 reconstruit tout l’index chaque fois. Elle supprime chaque ligne, puis analyse, découpe et transforme de nouveau les 36 documents en plongements. C’est simple et toujours correct. Sur le Mac du cours, cela a pris 2.09 s, avec le modèle de plongement déjà chargé. Une collection de 36 000 documents prendrait environ mille fois plus de temps, pour chaque petite modification.

Mei écrit donc la version plus rapide qui semble évidente. Pour chaque fichier du dossier, elle ignore ceux que l’index contient déjà et lit ceux qui sont nouveaux ou modifiés. C’est rapide. Puis l’équipe People déplace les règles de rabais du personnel vers son propre système. Elle demande à Grace de retirer staff-discount-and-perks.md de la collection du service de soutien. Mei supprime le fichier et lance sa synchronisation. Voici une version construite de ce code, exécutée sur une copie de la collection (reference/c09/m06/naive_incremental.py) :

Output (a constructed sync that only adds and updates)
NaiveStore: after deleting staff-discount-and-perks.md: 0 added, 0 updated, 35 unchanged, 0 removed | chunks: +0 -0
chunks in the index: 185; keyword search for 'What is the staff discount?': ['[f02ee0910bbf] staff-discount-and-perks', '[c13d27947ed1] staff-discount-and-perks']

Rien n’a échoué, et le rapport semble rassurant : 35 fichiers inchangés. Mais la synchronisation regarde seulement les fichiers qui existent. Elle ne remarque donc jamais un fichier disparu. L’index contient encore les 185 segments, et la recherche renvoie encore le document supprimé en premier. C’est une des idées fausses contre lesquelles ce cours vous met en garde : laisser le contenu supprimé accessible par la recherche. Le fichier a disparu, alors les gens croient que le texte a disparu. Ce n’est pas le cas.

L’idée : comparer les empreintes et supprimer partout

Section intitulée « L’idée : comparer les empreintes et supprimer partout »

La correction comporte deux parties.

La première consiste à reconnaître ce qui a changé. Quand ingest lit un fichier, il conserve son empreinte de contenu : l’empreinte SHA-256 de ses octets. C’est la même fonction de hachage qui produit les identifiants des segments. Au prochain passage, il recalcule l’empreinte de chaque fichier et compare les deux listes. C’est comme la bibliothécaire qui compare la livraison au catalogue. Chaque fichier correspond à l’un de ces quatre cas :

  • Nouveau (aucune ligne pour ce fichier) : l’analyser, le découper, créer ses plongements et l’ajouter.
  • Modifié (une empreinte différente) : supprimer d’abord les anciennes lignes, puis ajouter de nouveau le fichier.
  • Inchangé (la même empreinte) : l’ignorer. Rien n’est analysé ni transformé en plongements.
  • Disparu (une ligne, mais aucun fichier) : le supprimer.

C’est l’ingestion incrémentale : chaque exécution fait seulement le travail nécessaire pour les changements. Mei avait oublié le quatrième cas.

La deuxième partie concerne le sens de « supprimer ». Un document existe à plusieurs endroits : sa ligne dans documents, ses segments, leurs lignes de mots-clés et leurs vecteurs. Tout processus en cours peut aussi garder des copies en mémoire. Des réponses qui le citent peuvent aussi être stockées quelque part. Une suppression est complète seulement quand tous ces endroits ont oublié le document.

Prenez la politique sur les cartes-cadeaux. Comparez d’abord l’empreinte du fichier à celle que l’index a conservée au dernier ingest :

Python session, in ~/projects/policy-assistant
from pathlib import Path
from policy_assistant.store import Store, file_hash
file_hash(Path("corpus/documents/gift-cards.md"))[:12]
[d["content_hash"][:12] for d in Store("index.sqlite").documents() if d["doc_id"] == "gift-cards"]
Sortie
'dbfb9792f883'
['dbfb9792f883']

Les deux sont égales, alors ingest ignore ce fichier. Grace corrige maintenant une phrase dans Utiliser une carte-cadeau : « votre compte » devient « votre compte Larkfield ». La nouvelle empreinte est 7b83eafaf8b6. Un seul mot modifié suffit à changer toute l’empreinte, alors ingest voit le changement :

Output of python -m policy_assistant ingest
0 added, 1 updated, 35 unchanged, 0 removed | chunks: +3 -3
updated: documents/gift-cards.md
index index.sqlite: 36 documents, 185 chunks | chunker structure | embeddings intfloat/multilingual-e5-small

Il a supprimé les 3 anciens segments, avec leurs lignes de mots-clés et leurs vecteurs, puis en a ajouté 3 nouveaux. Comparez les identifiants des segments avant et après :

Segment Avant Après Pourquoi
#0 Acheter une carte-cadeau 8c4bc4299042, caractères 12–125 8c4bc4299042, caractères 12–125 Même version, même position : même identifiant
#1 Utiliser une carte-cadeau f27bb50ce8b7, 127–230 76f2d13b502f, 127–240 La phrase compte 10 caractères de plus
#2 Cartes perdues et retours 52ad3a57c1d1, 232–424 6c36ce3a91a6, 242–434 Sa position s’est déplacée de 10 caractères

Les identifiants des segments viennent du document, de la version et de la position. La section modifiée et la suivante ont donc reçu de nouveaux identifiants. Une ancienne réponse qui cite f27bb50ce8b7 cite maintenant un segment inexistant, et la vérification des citations le signale. C’est correct : le texte cité a réellement changé.

Toute la vie d’un document, du premier ingest à la dernière vérification, ressemble à ceci :

Six étapes en deux groupes. Dans l'index : 1 Publié, un nouveau fichier dans corpus/documents; 2 Ingéré, empreinte enregistrée, segments, mots-clés et vecteurs; 3 En vigueur à partir de effective_from, la recherche le renvoie; une flèche pointillée de 3 vers 2 indique une correction, nouvelle empreinte. Puis, quand la version suivante entre en vigueur, vers la sortie : 4 Retiré selon la date, après effective_to encore dans l'index mais exclu par le filtre; 5 Supprimé, fichier retiré, ingest supprime chaque ligne; 6 Vérifié, aucune réponse, aucun cache ni processus ne l'utilise encore.
La vie d'un document dans l'index. Une date retire une version; seule une suppression l'élimine, et seule une vérification prouve sa disparition.
Description textuelle du diagramme

Six étapes en deux groupes. Le premier groupe est « Dans l’index ». Étape 1, Publié : un nouveau fichier dans corpus/documents. Étape 2, Ingéré : son empreinte est enregistrée, et ses segments, lignes de mots-clés et vecteurs sont ajoutés. Étape 3, En vigueur : à partir de sa date effective_from, la recherche le renvoie. Une flèche pointillée va de l’étape 3 à l’étape 2 : une correction donne une nouvelle empreinte au fichier. Il est donc ingéré de nouveau. Quand la version suivante entre en vigueur, le document passe au deuxième groupe, « Vers la sortie ». Étape 4, Retiré selon la date : après sa date effective_to, il reste dans l’index, mais le filtre de date l’exclut. Étape 5, Supprimé : le fichier est retiré, et ingest supprime chaque ligne créée à partir de celui-ci. Étape 6, Vérifié : aucune réponse, aucun cache ni processus en cours ne l’utilise encore.

L’index de Larkfield : retirer selon la date, supprimer par décision

Section intitulée « L’index de Larkfield : retirer selon la date, supprimer par décision »

Le 1 novembre 2026, la version 4 de la politique de retour remplace la version 3. Personne n’a besoin de toucher à l’index ce jour-là. La version 3 a effective_to: 2026-10-31, alors le filtre de date du module 3 la retire à minuit :

Fenêtre de terminal
python -m policy_assistant search "Can a customer return an unused item?" --method bm25 --k 2 --as-of 2026-10-31
Sortie
1. 11.6078 [7839ded2d52b] returns-policy v3 | Return window | public
Return window You can return an unused item within 30 days of delivery for a full refund of the ite…
2. 10.9722 [c236034e24d2] help-centre-faq v7 | How do I return an item? | public
How do I return an item? Go to Orders in your account, choose the item and select Return an item. O…
bm25, top 2, as of 2026-10-31, audience staff

Avec --as-of 2026-11-01, le premier résultat est [963dae4229eb] returns-policy v4 | Return window (délai de retour : « dans les 45 jours »). L’article d’aide reste deuxième. La version 3 doit rester dans l’index. Comment les politiques de Larkfield sont gérées indique que l’ancienne version reste dans les archives. Il précise aussi qu’un retour suit la version en vigueur à la date de livraison. En décembre, un agent posera encore une question sur une commande livrée le 20 octobre. Seule la version 3 y répond.

La règle est donc simple. Une nouvelle version reçoit son propre fichier, un nouveau numéro de version et sa valeur effective_from. L’ancienne version reçoit une valeur effective_to. Supprimez une version seulement quand aucune question encore ouverte ne peut en avoir besoin.

Le document sur le rabais du personnel est différent : l’équipe People veut le retirer de la collection. Avec le nouveau ingest du projet, la suppression atteint l’index :

Output of python -m policy_assistant ingest, after the file was deleted
0 added, 0 updated, 35 unchanged, 1 removed | chunks: +0 -2
removed: documents/staff-discount-and-perks.md
index index.sqlite: 35 documents, 183 chunks | chunker structure | embeddings intfloat/multilingual-e5-small

La recherche par mots-clés renvoie maintenant Utiliser un code de rabais en premier. La commande chunks staff-discount-and-perks répond No chunks for staff-discount-and-perks (aucun segment pour staff-discount-and-perks). Posez maintenant la question que les enregistrements connaissent :

Fenêtre de terminal
python -m policy_assistant ask Q51
Sortie
Question (date 2026-10-09): What is the staff discount?
Passages: 094a8535171c, 1b1c047750bf, d753fb02bc8c, b16f983ce250, a559fb986971 (568 tokens, estimated)
Note: Recorded with passages c13d27947ed1, f02ee0910bbf, 094a8535171c, 1b1c047750bf, d753fb02bc8c; yours: 094a8535171c, 1b1c047750bf, d753fb02bc8c, b16f983ce250, a559fb986971. The recorded answer is replayed; its citations are checked against your passages.
Answer (answerable): The staff discount is 20% off all products except gift cards, on up to $2,000 of purchases per calendar year. It cannot be combined with other promotions or discount codes.
- The discount is 20% off all products except gift cards, and applies to at most $2,000 of purchases per calendar year. [c13d27947ed1] CHECK: missing_id:c13d27947ed1
- The staff discount cannot be combined with other promotions or discount codes. [c13d27947ed1] CHECK: missing_id:c13d27947ed1
gpt-6-luna-2026-09-22 | 951 tokens in, 127 out | 1.416 s

La recherche ne trouve plus le document, mais la réponse dit encore « 20 % de rabais ». Le simulateur rejoue la réponse donnée plus tôt par chat-small (gpt-6-luna sur Azure), quand le document existait. Dans ce projet, c’est ainsi que fonctionne le simulateur. Dans un véritable assistant, la même chose arrive avec un cache de réponses. Ce stockage conserve les réponses précédentes pour économiser du temps et de l’argent. Un cache qui trouve les réponses seulement par le texte de la question fournit du texte supprimé à la prochaine personne. Ici, la vérification des citations le détecte (missing_id), mais seulement parce que chaque affirmation cite sa source.

La nouvelle commande audit examine donc chaque réponse enregistrée. Elle énumère chaque citation d’un passage que l’index ne fournit plus pour cette question. Le passage est supprimé, ou n’est pas en vigueur à la date de la question :

Output of python -m policy_assistant audit
Q51 chat-small rerank answer_v1: [c13d27947ed1] removed from the index
Q51 chat-small bm25 answer_v1: [c13d27947ed1] removed from the index
Q51 chat-strong rerank answer_v1: [c13d27947ed1] removed from the index
Q51 chat-strong rerank answer_v1: [f02ee0910bbf] removed from the index
Q51 chat-strong bm25 answer_v1: [c13d27947ed1] removed from the index
Q51 chat-strong bm25 answer_v1: [f02ee0910bbf] removed from the index
268 recorded answers | 6 citations to passages that are removed, not in force or not allowed

Ces quatre réponses à Q51 sont celles à retirer de tout cache de réponses. Un autre endroit garde le souvenir du document : le fichier inventory.csv de Grace. inventory contient encore staff-discount-and-perks, parce que personne n’a modifié la liste. Le schéma rassemble tous les endroits et montre ce que chaque synchronisation a fait :

Un tableau pour le fichier supprimé staff-discount-and-perks.md. À gauche, une synchronisation qui ajoute et met à jour seulement; à droite, l'ingest du projet. Dossier : le fichier a disparu des deux côtés. Tables de l'index : la première contient encore 185 segments et n'a rien supprimé; le projet a supprimé 1 document et 2 segments, 183 segments. Index de mots-clés et vecteurs sur disque : la première trouve encore f02ee0910bbf et c13d27947ed1 en premier; le projet a supprimé les lignes, et le premier résultat est promotions-and-discount-codes. Mémoire d'un processus en cours : avec le code du module 5, la recherche vectorielle renvoyait encore c13d27947ed1; le projet vérifie la version de l'index avant chaque recherche. Réponses enregistrées : Q51 rejoue encore 20 % de rabais des deux côtés; audit trouve 6 citations de segments supprimés. inventory.csv : contient encore le document des deux côtés; le mettre à jour à la main.
Où un document supprimé peut encore être trouvé. Orange : le texte est encore là. Chaque valeur vient d'une exécution réelle.
Description textuelle du diagramme

Un tableau de six lignes pour le fichier supprimé staff-discount-and-perks.md. Ses deux colonnes sont une synchronisation qui ajoute et met à jour seulement, et l’ingest du projet. Ligne 1, le dossier corpus/documents : le fichier a disparu dans les deux colonnes. Ligne 2, les tables documents et chunks de l’index : la première synchronisation n’a rien supprimé et contient encore 185 segments. Elle ne cherche jamais les fichiers disparus. L’ingest du projet indique 1 suppression et 2 segments de moins, donc 183 segments. Ligne 3, l’index de mots-clés et les vecteurs sur disque : avec la première synchronisation, « Quel est le rabais du personnel? » trouve encore f02ee0910bbf et c13d27947ed1 en premier. Avec le projet, les lignes sont supprimées avec les segments. Le premier résultat est promotions-and-discount-codes. Ligne 4, la mémoire d’un processus qui continue de fonctionner : avec le code du module 5, la recherche vectorielle renvoyait encore c13d27947ed1 à partir des vecteurs en mémoire. Le projet vérifie la version de l’index avant chaque recherche et recharge la mémoire. Ligne 5, les réponses enregistrées, qui fonctionnent comme un cache de réponses : Q51 rejoue encore « 20 % de rabais sur tous les produits » dans les deux colonnes. audit trouve 6 citations de segments supprimés, et ces réponses doivent être retirées. Ligne 6, le fichier inventory.csv de Grace : il contient encore le document dans les deux colonnes; le mettre à jour à la main.

Un doublon est le même contenu stocké deux fois. Dans ce projet, il prend trois formes.

  • Le même fichier deux fois. Quelqu’un enregistre gift-cards-copy.md à côté de gift-cards.md. Les deux indiquent qu’ils sont la version 1 de gift-cards. ingest refuse, et l’index reste inchangé : Nothing changed in the index. documents/gift-cards-copy.md and documents/gift-cards.md are both gift-cards version 1. Keep one file per document version; a new version needs a new version number. (Rien n’a changé dans l’index. Les deux fichiers sont la version 1 de gift-cards. Gardez un fichier par version de document; une nouvelle version exige un nouveau numéro.) Sans cette vérification, les deux copies se disputeraient une place parmi les 5 premiers résultats à chaque recherche.
  • Un fichier renommé. Mei renomme gift-cards.md en gift-cards-2025.md. ingest supprime d’abord les fichiers disparus, puis ajoute le nouveau nom : 1 added, 0 updated, 35 unchanged, 1 removed | chunks: +3 -3 (1 ajouté, 0 mis à jour, 35 inchangés, 1 supprimé; segments : +3 -3). Les identifiants des segments restent les mêmes. Ils viennent du document, de la version et de la position, pas du nom de fichier.
  • Des paragraphes presque identiques dans différents documents. « Comment commencer un retour » apparaît dans quatre documents. Chacun est une véritable source avec son propre responsable, alors l’index garde les quatre. Assembler le contexte élimine les répétitions quand les passages entrent dans une même requête (prompt). Deux versions d’un document ne sont jamais fusionnées : leurs dates diffèrent, et la réponse doit préciser celle qu’elle utilise.

Erreur courante

Le serveur qui connaît encore le texte supprimé

Symptôme : l’assistant fonctionne comme un service : un processus Python qui répond aux questions toute la journée. Dans un autre terminal, Mei supprime le fichier du rabais du personnel et lance ingest. Le service répond encore à « Quel est le rabais du personnel? » à partir des segments supprimés. Voici une exécution réelle avec le code du module 5 (reference/c09/m06/running_process.py) :

Sortie
end-of-m05: before: dense [c13d27947ed1] staff-discount-and-perks, [f02ee0910bbf] staff-discount-and-perks
another process: 35 documents, 183 chunks (rebuilt from scratch)
end-of-m05: after: dense [c13d27947ed1] staff-discount-and-perks, [f02ee0910bbf] staff-discount-and-perks
end-of-m05: after: bm25 [ea0bff365b2c] promotions-and-discount-codes, [f78646d93435] policy-governance

Cause : pour gagner du temps, le service charge tous les vecteurs et segments en mémoire une seule fois. La recherche par mots-clés interroge SQLite chaque fois, alors elle voit la suppression. La recherche vectorielle utilise la copie en mémoire, qui contient encore les vecteurs supprimés. La mémoire est un cache, et personne ne l’a averti que l’index avait changé.

Correction : vérifiez la version de l’index avant chaque recherche, et rechargez la mémoire quand elle a changé. SQLite compte les changements effectués par les autres connexions (PRAGMA data_version). Le nouveau store.py combine ce compte avec celui de ses propres écritures dans Store.generation(), et search.py l’utilise. Avec l’instantané end-of-m06, la même exécution renvoie deux segments de privacy-policy après la suppression. Le test test_a_running_assistant_sees_the_deletion garantit ce comportement.

Pour un véritable assistant : énumérez tous les caches avant de faire confiance à une suppression. Par exemple : vecteurs en mémoire, caches de réponses, résultats de recherche conservés par un serveur Web et copies sur d’autres machines. Chacun a besoin d’une règle qui indique quand le recharger.

Quand l’ingestion incrémentale vaut la peine, et ce qu’elle coûte

Section intitulée « Quand l’ingestion incrémentale vaut la peine, et ce qu’elle coûte »

Sur le Mac du cours, un deuxième ingest sans changement a pris 2 ms. Un document modifié a pris 28.4 ms. Une reconstruction complète a pris 2.09 s. Avec 36 documents, les deux sont rapides. L’écart compte quand la collection grandit, ou quand les plongements viennent d’un service payant. Chaque reconstruction paie de nouveau pour chaque segment.

Le coût, c’est davantage de cas à traiter correctement. Les renommages, doublons, suppressions et processus en cours ont chacun demandé du code et un test ici. Un changement d’outil de découpage ou de modèle de plongement ne peut pas être incrémental. Les vecteurs de deux modèles ne peuvent pas être mélangés. Alors ingest le refuse, et ingest --rebuild crée un nouvel index.

Utilisez l’ingestion incrémentale quand les documents changent souvent et que la collection est grande. Gardez la reconstruction complète pour les petites collections et après un changement d’outil de découpage ou de modèle. Utilisez-la aussi comme vérification régulière : un index reconstruit doit donner les mêmes identifiants de segments et les mêmes mesures.

Exercice guidé : ajouter l’ingestion incrémentale

Section intitulée « Exercice guidé : ajouter l’ingestion incrémentale »
  1. Copiez le nouveau code d’indexation, le code de recherche, la ligne de commande et les tests du stockage depuis l’instantané :

    Fenêtre de terminal
    cp ../nextia-learning-labs/C09/snapshots/end-of-m06/policy_assistant/store.py policy_assistant/
    cp ../nextia-learning-labs/C09/snapshots/end-of-m06/policy_assistant/search.py policy_assistant/
    cp ../nextia-learning-labs/C09/snapshots/end-of-m06/policy_assistant/__main__.py policy_assistant/
    cp ../nextia-learning-labs/C09/snapshots/end-of-m06/tests/test_store.py tests/

    Le nouveau __main__.py comprend aussi --prompt, audit et info. Les deux prochaines leçons les utilisent.

  2. Ouvrez policy_assistant/store.py et trouvez sync. Repérez les quatre cas : supprimé, inchangé, modifié et nouveau.

  3. Exécutez les tests. Vous voyez 83 passed, 1 deselected (83 tests réussis, 1 non sélectionné) :

    Fenêtre de terminal
    python -m pytest
  4. Mettez l’index à jour. Votre index du module 5 contient déjà une empreinte pour chaque fichier :

    Fenêtre de terminal
    python -m policy_assistant ingest
    Sortie
    0 added, 0 updated, 36 unchanged, 0 removed | chunks: +0 -0
    index index.sqlite: 36 documents, 185 chunks | chunker structure | embeddings intfloat/multilingual-e5-small
  5. Dans VS Code, ouvrez corpus/documents/gift-cards.md. Remplacez « in your account » (dans votre compte) par « in your Larkfield account » (dans votre compte Larkfield), puis enregistrez.

  6. Exécutez ingest, puis chunks gift-cards. Vous voyez 1 updated | chunks: +3 -3 (1 mis à jour | segments : +3 -3) et les nouveaux identifiants 76f2d13b502f et 6c36ce3a91a6, comme dans l’exemple détaillé.

  7. Rétablissez le fichier d’origine et exécutez de nouveau ingest :

    Fenêtre de terminal
    cp ../nextia-learning-labs/C09/corpus/documents/gift-cards.md corpus/documents/
    python -m policy_assistant ingest

    chunks gift-cards affiche de nouveau f27bb50ce8b7 et 52ad3a57c1d1 : le même texte donne les mêmes identifiants.

  8. Supprimez le document sur le rabais du personnel et mettez l’index à jour :

    Fenêtre de terminal
    rm corpus/documents/staff-discount-and-perks.md
    python -m policy_assistant ingest

    Vous voyez 1 removed | chunks: +0 -2 (1 supprimé | segments : +0 -2).

  9. Cherchez-le avec des mots-clés :

    Fenêtre de terminal
    python -m policy_assistant search "What is the staff discount?" --method bm25 --k 3

    Le premier résultat est [ea0bff365b2c] promotions-and-discount-codes v2 | Using a discount code (utiliser un code de rabais). Aucun résultat ne vient du document supprimé.

  10. Exécutez ask Q51, puis audit. Vous voyez la réponse enregistrée « 20 % de rabais » avec missing_id (identifiant manquant), et 6 citations to passages that are removed (6 citations de passages supprimés).

  11. Rétablissez le document et vérifiez que tout est comme avant :

    Fenêtre de terminal
    cp ../nextia-learning-labs/C09/corpus/documents/staff-discount-and-perks.md corpus/documents/
    python -m policy_assistant ingest
    python -m policy_assistant audit
    Sortie
    1 added, 0 updated, 35 unchanged, 0 removed | chunks: +2 -0
    added: documents/staff-discount-and-perks.md
    index index.sqlite: 36 documents, 185 chunks | chunker structure | embeddings intfloat/multilingual-e5-small
    268 recorded answers | 0 citations to passages that are removed, not in force or not allowed

Si ingest affiche Nothing changed in the index (rien n’a changé dans l’index), une version de document figure deux fois dans corpus/documents. Supprimez la copie et exécutez de nouveau ingest. Si ingest signale un autre outil de découpage ou modèle de plongement, exécutez python -m policy_assistant ingest --rebuild.

À vous : renommez corpus/documents/gift-cards.md en gift-cards-2025.md et prédisez le rapport de ingest avant de l’exécuter. Rétablissez ensuite le nom d’origine.

Afficher la réponse

1 added, 0 updated, 35 unchanged, 1 removed | chunks: +3 -3 (1 ajouté, 0 mis à jour, 35 inchangés, 1 supprimé | segments : +3 -3), avec added: documents/gift-cards-2025.md (ajouté) et removed: documents/gift-cards.md (supprimé). Les identifiants des segments ne changent pas, car le nom du fichier n’en fait pas partie. Rétablir le nom d’origine donne le même rapport, avec les deux noms inversés.

Vérifiez votre compréhension

La première synchronisation de Mei ignorait les fichiers inchangés et ajoutait les nouveaux. Pourquoi un fichier supprimé restait-il accessible par la recherche? Pourquoi le rapport ne signalait-il aucun problème?

Afficher la réponse

La synchronisation parcourait les fichiers du dossier. Un fichier supprimé n’est plus dans le dossier. La boucle ne le traitait donc jamais, et ses lignes restaient dans toutes les tables. Le rapport comptait seulement les fichiers qu’il voyait : 35 inchangés, 0 supprimé. Pour trouver les suppressions, comparez aussi dans l’autre sens : supprimez chaque document de l’index dont le fichier a disparu.

Vérifiez votre compréhension

Prédisez : le 2 novembre 2026, Grace devrait-elle supprimer returns-policy.v3.md de la collection? Quel problème cela causerait-il?

Afficher la réponse

Non. Le filtre de date écarte déjà la version 3 pour les questions datées du 1er novembre ou après. Mais les retours suivent la version en vigueur à la date de livraison. Un agent posera encore des questions sur des commandes livrées en octobre, comme Q22 (livrée le 20 octobre). Sans la version 3, la recherche trouverait seulement la version 4, et l’assistant répondrait 45 jours plutôt que 30. Supprimez une version seulement quand aucune question en suspens ne peut en avoir besoin.

Vérifiez votre compréhension

Après la suppression, ask Q51 affichait encore « 20 % de rabais ». Quelle partie du système était en cause? Comment corriger ce problème dans un véritable assistant?

Afficher la réponse

L’index était correct : la recherche ne retournait aucun passage sur le rabais du personnel. La réponse stockée posait problème. Le simulateur rejoue un enregistrement trouvé à partir de la question, comme un cache de réponses indexé par le texte de la question. Dans un véritable assistant, supprimez les réponses en cache qui citent un segment supprimé, comme celles que audit liste. Vous pouvez aussi inclure les identifiants des passages dans la clé du cache. La vérification des citations (missing_id) est une dernière mesure de protection, pas la correction.

Récapitulatif

  • L’ingestion incrémentale compare l’empreinte du contenu de chaque fichier à celle qui est stockée. Elle relit les fichiers nouveaux et modifiés, et ignore les fichiers inchangés. Elle supprime les documents dont le fichier a disparu. Un deuxième ingest a pris 2 ms plutôt que 2.09 s.
  • Une suppression doit atteindre chaque copie : les tables, l’index par mots-clés, les vecteurs, la mémoire des processus actifs, les caches de réponses et l’inventaire. Une synchronisation qui ne faisait qu’ajouter et mettre à jour laissait le rabais du personnel supprimé en premier dans la recherche.
  • Retirer une version du service relève du filtre de date : la version 3 de la politique de retour reste dans l’index pour les livraisons avant le 1er novembre.
  • Une version de document provient d’un seul fichier : les copies sont refusées, et les fichiers renommés sont déplacés. Les quasi-doublons dans différents documents sont traités lors de l’assemblage du contexte.
  • audit liste les réponses enregistrées qui citent des passages supprimés ou périmés. La vérification des citations les détecte aussi, mais seulement comme dernier garde-fou.

Votre projet contient maintenant le store.py incrémental. Ensuite, les étiquettes d’accès : qui peut voir quel passage.

Leçon suivante : Autorisations et isolation