# Atelier : tester les preuves du contexte retrouvé

Ce diagnostic local exécute un découpage et une recherche lexicale sur trois petits documents français fictifs. Il vérifie si les faits de référence sont intégralement présents dans le contexte final préparé pour un modèle. Aucune API, aucun secret, aucun modèle et aucune génération de réponse ne sont utilisés. Les champs `words_sent` comptent les mots de ce contexte préparé, sans envoi effectif à un modèle.

## Exécuter

Prérequis : Python 3 et une distribution SQLite avec FTS5. Aucune installation de paquet n’est nécessaire. L’exécution de référence utilise Python 3.14.7 et SQLite 3.53.4. Le script échoue explicitement si FTS5 est indisponible.

Depuis le dossier décompressé :

```sh
python3 atelier.py --self-test
python3 atelier.py --output mon-resultat.json
```

Ouvrir `cas_fr.json` pour lire les documents, les six questions et les citations de référence. Ouvrir `mon-resultat.json` pour inspecter les requêtes, les passages classés, les positions source et les textes réellement conservés après application du budget. Les positions commencent à zéro ; l’intervalle `[start, end[` exclut `end`.

## Ce qui reste identique

Le corpus contient 432 mots, répartis en trois documents, avec le pays `FR` et le statut `current`. Les cinq questions répondables demandent chacune deux faits. Une sixième question porte sur un forfait satellite absent du corpus et reste hors du dénominateur de couverture.

Chaque variante utilise le même corpus, les mêmes questions et les mêmes filtres `FR` et `current`. La question devient une requête OR après suppression d’une petite liste explicite de mots fréquents. SQLite FTS5 utilise `unicode61 remove_diacritics 2`, puis classe par `bm25` croissant et par identifiant en cas d’égalité. Le meilleur score FTS5 est le plus petit. Il n’y a ni lemmatisation, ni synonymes, ni embeddings, ni reranker.

Les trois premiers résultats au maximum sont concaténés selon leur rang, avec un budget strict de 120 mots. Le dernier passage est tronqué à la frontière d’un mot si nécessaire. Aucun titre supplémentaire n’est préfixé et aucune répétition n’est retirée. Les titres déjà présents dans les documents comptent dans les mots.

Un **mot** est ici un élément de `str.split()`, séparé des autres par des blancs. Ainsi `d’un` compte pour un mot. Cette unité n’est ni un token de modèle, ni un caractère, ni forcément l’unité lexicale interne de SQLite.

## Variantes fixées avant la première exécution

1. `fixe_48_0` : fenêtres de 48 mots, sans recouvrement.
2. `fixe_96_0` : fenêtres de 96 mots, sans recouvrement. Seule la taille change par rapport à la référence.
3. `fixe_48_16` : fenêtres de 48 mots, avec 16 mots repris du passage précédent. Seul le recouvrement change par rapport à la référence.
4. `paragraphes` : chaque paragraphe source forme un passage. C’est une exploration de la structure, avec des longueurs variables, pas une comparaison où une seule valeur change.

Le corpus a été écrit pour exposer des frontières entre règles et exceptions. Les questions emploient souvent les mots exacts du corpus. Les tailles n’ont pas été sélectionnées après observation des résultats. Ce jeu est délibérément pédagogique ; il ne représente pas des documents clients ni une charge de production.

## Lire les mesures

Chaque fait possède une citation exacte, localisée une seule fois dans son document source. Il est couvert seulement si **tous ses mots**, identifiés par leurs positions source, figurent dans l’union des passages conservés après le budget. Des fragments répartis dans plusieurs passages peuvent donc couvrir une même citation. La mesure établit la présence matérielle de la preuve ; elle ne vérifie ni l’ordre de lecture d’un modèle, ni sa compréhension, ni la validité sémantique d’une réponse.

Un contexte est complet si ses deux faits requis sont couverts. Une citation tronquée, même presque entière, ne l’est pas. `redundant_words` compte les occurrences envoyées en plus d’une première occurrence de la même position source, pas les mots de vocabulaire qui se répètent naturellement.

Résultats de l’exécution de référence, sur les cinq questions répondables :

| Variante | Passages indexés | Mots indexés | Contextes complets | Faits couverts | Mots du contexte | Mots redondants |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| 48 mots, sans recouvrement | 10 | 432 | 4 sur 5 | 9 sur 10 | 600 | 0 |
| 96 mots, sans recouvrement | 6 | 432 | 3 sur 5 | 8 sur 10 | 600 | 0 |
| 48 mots, recouvrement de 16 | 13 | 592 | 5 sur 5 | 10 sur 10 | 600 | 101 |
| Paragraphes | 6 | 432 | 5 sur 5 | 10 sur 10 | 600 | 0 |

Ces petits comptes ne démontrent aucune supériorité générale. En changeant le découpage, on change aussi le nombre de lignes de l’index, leurs longueurs et les fréquences utilisées par BM25. L’atelier permet d’observer une chaîne réelle, pas d’attribuer tous ses effets à une seule frontière.

## Deux cas à ouvrir dans le rapport

**Hôtel : premier passage incomplet, contexte final complet.** La question `q_hotel` demande le plafond à Paris pendant un salon. En `fixe_48_0`, le rang 1 contient le plafond ordinaire de 140 euros. Le rang 2 contient l’exception : 190 euros si un accord écrit a précédé la réservation. Les deux citations figurent dans les 120 mots finaux. Agrandir les passages n’est donc pas nécessaire pour faire apparaître cette exception dans ce cas précis.

**Train : passage retrouvé, preuve coupée par le budget.** Pour `q_train`, `fixe_96_0` retrouve au rang 2 un passage dont la version intégrale contient l’exception de première classe. Après les 96 mots du rang 1, il ne reste que 24 mots de budget. Le rang 2 est coupé avant l’exception ; aucun de ses 22 mots n’atteint le contexte final. Le rapport distingue `hits_before_budget`, qui conserve le texte intégral, le rang, le document, les positions et le nombre de mots de chaque passage retrouvé avant limitation, de `context`, qui montre les mots conservés. La comparaison avant/après ne demande pas de reconstruire les passages depuis le corpus. En `fixe_48_16`, la preuve complète figure dans le contexte, avec 32 occurrences source répétées sur les 120 mots.

**Absence : des passages peuvent revenir sans répondre.** `q_absente` demande un forfait satellite. Le mot « remboursement » peut ramener des passages sur d’autres dépenses. `all_required_covered` vaut `null` pour ce cas, et non `true` par une convention de liste vide. L’atelier ne mesure pas une abstention de modèle.

## Contrôler l’évaluateur et adapter le jeu

`--self-test` vérifie treize cas : identifiants dupliqués ou inconnus, citation absente, références incohérentes, document vide, règle présente avec exception absente, citation tronquée, mauvais document de contexte, passage inconnu et recouvrement invalide. Une référence mal formée produit une erreur explicite. Les contrôles empêchent plusieurs faux positifs ; ils ne décident pas à votre place quels faits une réponse correcte doit contenir.

Pour adapter le diagnostic, remplacer le corpus par des documents autorisés, écrire les questions puis fixer les citations nécessaires **avant** d’examiner les résultats des variantes. Ajouter notamment conditions, exceptions, versions et cas absents. Garder un jeu de validation distinct des questions utilisées pour choisir les paramètres. Pour un moteur utilisant des embeddings ou une recherche hybride, réutiliser le contrôle du contexte assemblé avec ce moteur réel ; les classements de cet atelier ne permettent pas de prédire ses résultats.

La prochaine décision dépend du défaut observé : citation absente de l’index, passage mal classé, preuve coupée à la frontière, preuve retirée par le budget, ou référence de test incorrecte. Consigner ce défaut avant de modifier les paramètres.

Sources de mécanisme : [Azure AI Search](https://learn.microsoft.com/en-us/azure/search/vector-search-how-to-chunk-documents), [séparateur récursif LangChain](https://docs.langchain.com/oss/python/integrations/splitters/recursive_text_splitter), [SQLite FTS5 et BM25](https://www.sqlite.org/fts5.html#the_bm25_function). Les bibliothèques LangChain et LlamaIndex ne sont pas exécutées dans cet atelier.
