Répondre à partir de pages d'orientation publiques
- Étude de cas 1
- 240 min
- Facultative · 1 étude de cas requise pour le certificat
Étude de cas 1 sur 3 · RAG : créer une IA qui utilise vos donnéesRépondre à partir de pages d'orientation publiques
1. Définir le problème de connaissances
2. Ingérer les documents de façon reproductible
3. Créer et comparer la récupération
4. Construire des réponses fondées sur les sources
5. Évaluer et déboguer le pipeline
6. Maintenir et protéger la base de connaissances
Ce que vous saurez faire
Créez un assistant qui répond aux questions sur les droits au travail à partir de vraies pages d’orientation de GOV.UK. Chaque affirmation cite un passage. Chaque source affiche son adresse et la date de récupération. Quand les pages ne donnent pas de réponse, l’assistant le dit. Vous mesurez la recherche et les réponses sur un ensemble fixe de questions. Vous comparez aussi le coût d’un petit modèle et d’un modèle performant.
Le projet de départ suit la structure de policy-assistant, le projet du cours. Il contient déjà l’instantané, les questions et les réponses enregistrées. Il contient aussi les éléments que vous avez créés dans le cours : recherche, reclassement, contexte, schéma de réponse, vérifications des citations et fournisseur simulé. Vous écrivez les trois fichiers propres aux pages d’orientation publiques : l’analyseur, le découpeur et le code qui affiche les sources. Le projet terminé présente le résultat, si vous avez besoin d’aide ou voulez comparer. Vous n’avez besoin d’aucun compte : la simulation rejoue les réponses données par de vrais modèles.
Avant de commencerCe qu’il faut savoir et avoir sous la main
Ce que vous devez savoir
Cette étude de cas applique tout le cours à de nouveaux documents. Voici les leçons les plus utilisées :
- Les blocs qui conservent le chemin vers la source, dans Analyser et normaliser.
- Les identifiants de segments stables, dans Conserver les métadonnées.
- BM25, les vecteurs, la fusion et le reclassement, dans Recherche lexicale, dense et hybride et Reclassement et reformulation des requêtes.
- Les affirmations avec identifiants de passages et les vérifications des citations, dans Répondre et citer, et l’abstention, dans Traiter les preuves difficiles.
- recall@k et les mesures des réponses, dans Mesurer la récupération et Mesurer les réponses.
Ce qu’il vous faut sur votre ordinateur
- Python 3.12 ou une version plus récente, VS Code et un terminal, comme dans le cours.
- Environ 2 Go d’espace disque libre : 0,9 Go pour les paquets et 1 Go pour les deux modèles locaux. Si vous avez suivi le cours, les modèles sont déjà dans votre cache Hugging Face.
Testé sur macOS (puce Apple) avec Python 3.12.13. Les versions sont celles du cours : sentence-transformers 6.1.0, torch 2.14.1, pydantic 2.14.0, pytest 9.1.1. Les commandes Windows suivent celles du cours, mais elles n’ont pas été testées.
La situation
Section intitulée « La situation »Joy Mensah dirige les séances d’un petit centre de conseil dans une ville anglaise. Des bénévoles aident les gens à résoudre un problème au travail : congés refusés, indemnités de maladie interrompues, lettre de licenciement économique. La plupart des réponses se trouvent dans les pages d’orientation de GOV.UK, réparties sur des centaines de pages. Quand des gens attendent, un bénévole ne peut pas lire un guide en huit parties pour chaque question.
Ravi Patel, un développeur bénévole, propose de créer un assistant qui trouve les passages et donne une réponse courte avec ses pages. Joy accepte à trois conditions. Chaque réponse doit montrer sa source et l’ancienneté de la page. Un bénévole doit pouvoir ouvrir la page et la lire avec la personne. Quand les pages d’orientation ne répondent pas, l’assistant doit le dire. Une réponse fausse mais assurée sur un congédiement peut faire perdre à quelqu’un son recours. Et l’assistant ne doit jamais se présenter comme un service gouvernemental ou un avocat.
Joy, Ravi et le centre sont fictifs. Les pages sont réelles, tout comme les questions que les gens posent à leur sujet.
Ce que vous allez créer
Section intitulée « Ce que vous allez créer »Vous allez créer guidance-assistant, un petit projet Python. L’image montre le parcours complet d’une question.
Description textuelle du diagramme
- Un instantané de 137 pages de GOV.UK, récupérées une seule fois le 9 octobre 2026 et vérifiées avec SHA-256, est analysé et découpé en 504 segments. Chaque segment conserve son identifiant, l’adresse de sa page avec une ancre et ses dates.
- La question d’un conseiller passe à la recherche du cours : BM25 et vecteurs e5, fusion avec RRF, puis reclassement. Les cinq meilleurs segments sont conservés.
- Le contexte présente chaque passage avec sa page, sa section, sa date de récupération et son adresse.
- Le modèle répond par des affirmations, et chacune cite des identifiants de passages.
- Le code vérifie chaque citation : l’identifiant existe, il figurait dans le contexte, ses nombres et ses mots sont dans le passage.
- Si les pages répondent à la question, le conseiller voit une fiche de réponse avec sources numérotées, adresses et dates. Un avis précise qu’il s’agit d’information générale, pas de conseils juridiques. Sinon, la fiche le dit et renvoie vers Acas ou un conseiller.
Trois fichiers sont nouveaux. L’analyseur lit le HTML de GOV.UK, le découpeur donne une adresse Web à chaque segment, et sources.py affiche les sources.
Les données
Section intitulée « Les données »Les pages proviennent de GOV.UK, le site Web du gouvernement britannique. Nous avons choisi un sujet qu’un centre de conseil rencontre chaque jour : les droits au travail en Angleterre, en Écosse et au pays de Galles (congés, indemnités de maladie, congés familiaux, préavis, licenciement économique et congédiement, heures de travail, contrats, salaire minimum). L’instantané contient 38 guides répartis sur 137 pages, soit environ 36 000 mots.
La licence et ses exigences
Section intitulée « La licence et ses exigences »La plupart des contenus de GOV.UK sont protégés par le droit d’auteur de la Couronne sous Open Government Licence v3.0 (OGL). Tout le monde peut les copier, les adapter et les partager, même à des fins commerciales. La licence exige une chose : nommer la source. GOV.UK ne prescrit aucune formulation particulière pour ces pages. Nous utilisons donc le texte par défaut de la licence : « Contient des informations du secteur public sous licence Open Government Licence v3.0. » Le projet l’affiche sous chaque réponse.
La licence ne couvre pas les données personnelles, les logos, les armoiries ni les droits de tiers. Notre instantané contient seulement le corps du texte, sans ces éléments. La licence n’accorde pas non plus le droit « d’utiliser l’information d’une manière qui suggère un statut officiel ». Les conditions de GOV.UK ajoutent que le site ne publie pas de conseils : « Vous devriez obtenir des conseils professionnels ou spécialisés avant d’agir sur la base du contenu. » Ces deux phrases deviennent des règles du projet, dans Utilisation responsable.
Nous avons aussi examiné les pages d’orientation de Canada.ca. Leurs conditions permettent seulement la reproduction non commerciale sans autorisation. Elles ne respectent donc pas la règle du cours sur les licences. La Licence du gouvernement ouvert – Canada couvre les jeux de données sur open.canada.ca, pas les pages Web.
Un instantané, récupéré une seule fois
Section intitulée « Un instantané, récupéré une seule fois »Un instantané est une copie des pages à un moment précis, conservée avec une empreinte de chaque fichier. Imaginez une photocopie datée d’un tableau d’affichage : le tableau changera, mais pas la copie. Sans elle, les questions, les mesures et les réponses enregistrées ne correspondraient plus dès que GOV.UK modifierait une phrase.
Nous n’avons pas copié les pages Web. GOV.UK propose une Content API officielle, qui renvoie chaque page en JSON avec son contenu en HTML et ses dates. Le script scripts/fetch_snapshot.py a demandé une seule fois 47 guides candidats, à raison d’une requête toutes les 1,2 secondes. Il a d’abord lu le fichier robots.txt de GOV.UK, qui autorise l’API. Nous en avons conservé 38. guidance/pages.jsonl contient une ligne par page, avec :
| Champ | Exemple |
|---|---|
page_id, url |
statutory-sick-pay/eligibility, https://www.gov.uk/statutory-sick-pay/eligibility |
guide_title, part_title |
Statutory Sick Pay (SSP), Eligibility (indemnités légales de maladie, admissibilité) |
public_updated_at |
2015-03-09 : date de la dernière modification majeure selon GOV.UK |
updated_at |
2026-10-05 : date de la dernière modification, quelle que soit son ampleur |
retrieved_at |
2026-10-09T06:21:10Z : moment de la récupération |
api_sha256, body_sha256 |
empreintes de la réponse de l’API et du corps du texte |
body_html |
corps de la page, exactement comme l’API l’a renvoyé |
Premières vérifications
Section intitulée « Premières vérifications »Nous avons lu l’instantané avant d’écrire la recherche. Quatre constats orientent le projet :
- La date de « dernière mise à jour » peut tromper.
public_updated_atest antérieur à 2020 pour 26 des 38 guides (le plus ancien date du 17 novembre 2014). Pourtant, le guide des indemnités de maladie donne le montant d’avril 2026, soit 123,25 £.updated_at(du 24 septembre au 5 octobre 2026) reflète mieux la réalité, mais aucun champ ne précise quelle phrase a changé. L’assistant affiche donc la date de récupération etupdated_at, et demande de vérifier la page. - Certaines pages contiennent leur propre historique. La page du salaire minimum contient quatre tableaux : les taux actuels et trois tableaux de taux antérieurs. Certaines règles portent une date sur la page : les soins néonatals concernent les bébés nés à partir du 6 avril 2025. Une règle de préavis pour le congé de paternité concerne les bébés dont la naissance est prévue avant le 25 juillet 2026.
- L’Irlande du Nord est exclue. Plusieurs pages disent « Les règles sont différentes si vous vivez en Irlande du Nord », sans donner ces règles.
- Une ligne répétée. Neuf pages contiennent la ligne « Ce guide est aussi disponible en gallois (Cymraeg). » Elle ne répond à aucune question et rend ces pages semblables pour la recherche.
Les questions
Section intitulée « Les questions »Avant toute recherche, Joy et Ravi définissent une bonne réponse, comme dans Le corpus et les questions. Le fichier questions/questions.jsonl contient 44 questions de six types. Chacune comporte une réponse de référence, les passages pertinents (page et section) et des clés : des expressions régulières auxquelles une bonne réponse doit correspondre, comme dans le cours.
| Type | Nombre | Exemple | Bonne réponse |
|---|---|---|---|
| Fait | 12 | Pendant combien de temps peut-on recevoir les indemnités légales de maladie? | Jusqu’à 28 semaines |
| Condition | 14 | Ai-je droit à l’indemnité légale de licenciement économique? | Normalement oui, si vous êtes salarié avec 2 ans de service |
| Tableau | 3 | Quel est le salaire horaire minimum pour une personne de 18 ans? | 10,85 £ (à partir d’avril 2026) |
| Date | 5 | Quel était le salaire minimum pour une personne de 22 ans en juin 2023? | 10,18 £ |
| Calcul | 2 | Je travaille 4 jours par semaine. À combien de jours de congé légal ai-je droit? | 22,4 jours (4 × 5,6) |
| Non couvert | 8 | Quel est le montant des indemnités légales de maladie en Irlande du Nord? | « Les pages d’orientation ne le précisent pas » |
Les questions de condition comptent le plus pour un centre de conseil. En droit du travail, la réponse à « Puis-je…? » est souvent « oui, si… ». Une réponse qui omet la condition est fausse, même si ses nombres sont justes. Les clés vérifient la condition, par exemple « 2 ans ». Les questions non couvertes sont des pièges. Certaines concernent l’Irlande du Nord, une demande un conseil personnel et une invite à prendre un nombre dans la mauvaise règle.
L’auteur de cette étude de cas a rédigé chaque question, réponse et clé en lisant les pages. Aucun modèle n’a rédigé ni étiqueté de question. Un script vérifie que chaque passage existe et que chaque clé correspond à sa réponse de référence. Après avoir lu les premières réponses enregistrées, nous avons modifié deux vérifications, comme nous l’indiquons dans DATASET.md. La clé de Q34 accepte maintenant aussi « not met » (non remplie), car une bonne réponse disait « the birth-date condition is not met » (la condition de date de naissance n’est pas remplie). Un motif must_not de Q41 ne détecte plus « whether you should accept » (si vous devriez accepter). Il avait classé cette abstention comme un conseil. Nous avons ensuite figé l’ensemble, avant toute comparaison. Mesurer les réponses explique pourquoi les données de test doivent être figées avant une comparaison finale.
Préparer le projet
Section intitulée « Préparer le projet »Les étapes utilisent VS Code et son terminal. Toutes les commandes s’exécutent dans le dossier du projet.
-
Téléchargez le projet de départ dans l’encadré en haut de cette page. Décompressez-le dans votre dossier de projets. Vous obtenez le dossier
guidance-assistant. -
Dans VS Code, choisissez File > Open Folder (fichier, ouvrir un dossier), puis ouvrez
guidance-assistant. Ouvrez ensuite un terminal : Terminal > New Terminal (terminal, nouveau terminal). -
Créez et activez un environnement virtuel :
Fenêtre de terminal python3 -m venv .venvsource .venv/bin/activateFenêtre de terminal python -m venv .venv.venv\Scripts\Activate.ps1Fenêtre de terminal python3 -m venv .venvsource .venv/bin/activateL’invite commence par
(.venv). Si VS Code demande d’utiliser le nouvel environnement pour ce dossier, choisissez Yes (oui). -
Installez les versions du cours. Sur Linux, installez d’abord PyTorch pour processeur seulement :
python -m pip install torch==2.14.1 --index-url https://download.pytorch.org/whl/cpu.Fenêtre de terminal python -m pip install -r requirements.txt -
Exécutez les tests et vérifiez l’instantané :
Fenêtre de terminal python -m pytestpython scripts/check_snapshot.py
========================= 5 passed, 9 skipped in 0.46s =========================OK pages.jsonl SHA-256 b48f97342a68…OK sources.csv SHA-256 499806c88713…137 pages from 38 GOV.UK guides, fetched 2026-10-09Ce que cela nous apprend. Les paquets fonctionnent, et l’instantané est celui que nous avons testé. Chaque chargement compare d’abord les fichiers avec guidance/SHA256SUMS. Un test remplace 123,25 £ par 132,25 £ dans une copie et vérifie que le chargement s’arrête. Les 9 tests ignorés concernent les trois fichiers que vous écrivez. Chacun s’exécute dès que son fichier existe.
Examinez les pages et les questions que les pages d’orientation ne couvrent pas :
python -m guidance_assistant pagespython -m guidance_assistant questions --kind not_covered 1 page 2026-10-05 Payslips: employee rights 1 page 2026-10-05 Pay and work rights helpline and complaints137 pages from 38 GOV.UK guides | fetched 2026-10-09 | checksums OK...Q43 not_covered What is the rate of Statutory Sick Pay in Northern Ireland?Q44 not_covered How much compensation will I get if I win an unfair dismissal claim?8 questionsPartie 1 : lire les pages et conserver le chemin vers la source
Section intitulée « Partie 1 : lire les pages et conserver le chemin vers la source »Le corps d’une page GOV.UK est en HTML : titres avec un id, paragraphes, listes, quelques tableaux et encadrés d’avis. La recherche a besoin de texte propre. Le conseiller a besoin du chemin vers la source : la page, la section et l’adresse de cette section sur GOV.UK. C’est l’idée d’Analyser et normaliser, appliquée à un nouveau format : analyser en blocs. Chaque bloc conserve sa section, l’id de son titre et sa position dans le texte propre. Cet identifiant est l’ancre, la partie d’une adresse après #.
La bibliothèque standard de Python contient un analyseur HTML. Le projet n’a donc besoin d’aucun nouveau paquet. Créez guidance_assistant/parse.py :
"""Parse a GOV.UK page body (HTML) into clean blocks that remember where they came from.
A block is a heading, a paragraph, a list item, a table or a notice. Each block keeps:- section: the heading it is under ("" before the first heading),- anchor: that heading's id, so a citation can link to https://www.gov.uk/<page>#<anchor>,- start, end: its character positions in the page's clean text.
What the parser removes, and why:- tags (links keep their words, abbreviations their short form: "SSP");- the line "This guide (or page) is also available in Welsh (Cymraeg)": it repeats on many pages, says nothing about the question, and makes those pages look alike to a search.Tables become one block with one line per row ("cell | cell | cell"): a table is never split."""
import htmlimport refrom dataclasses import dataclass, fieldfrom html.parser import HTMLParser
DROP = re.compile(r"^This (guide|page) is also available in Welsh", re.I)BLOCK_TAGS = {"p": "paragraph", "li": "item", "h2": "heading", "h3": "heading", "h4": "heading"}
@dataclass(frozen=True)class Block: kind: str # heading, paragraph, item, table, notice text: str section: str anchor: str start: int = 0 end: int = 0
@dataclassclass ParsedPage: page_id: str title: str blocks: list = field(default_factory=list) text: str = "" dropped: list = field(default_factory=list) # texts removed as boilerplate
def clean(text: str) -> str: return re.sub(r"\s+", " ", html.unescape(text)).strip()
class _Reader(HTMLParser): """Collects (kind, text, heading id) in reading order."""
def __init__(self): super().__init__(convert_charrefs=True) self.out: list[tuple[str, str, str]] = [] self.buffer: list[str] | None = None self.kind = "" self.heading_id = "" self.notice_depth = 0 # inside <div role="note"> (info and help notices) self.table: list[list[str]] | None = None self.cell: list[str] | None = None
def handle_starttag(self, tag, attrs): a = dict(attrs) if tag == "div" and a.get("role") == "note": self.notice_depth += 1 if tag == "table": self.table = [] elif self.table is not None: if tag == "tr": self.table.append([]) elif tag in ("td", "th"): self.cell = [] return if tag in BLOCK_TAGS and self.buffer is None: self.buffer, self.kind = [], BLOCK_TAGS[tag] if self.kind == "heading": self.heading_id = a.get("id", "") elif tag == "li" and self.buffer is not None: self.buffer.append(" ") # a nested list item continues its parent item if tag == "br" and self.buffer is not None: self.buffer.append(" ")
def handle_endtag(self, tag): if self.table is not None: if tag in ("td", "th") and self.cell is not None: self.table[-1].append(clean("".join(self.cell))) self.cell = None elif tag == "table": rows = [" | ".join(r) for r in self.table if any(r)] self.out.append(("table", "\n".join(rows), "")) self.table = None return if tag in BLOCK_TAGS and self.buffer is not None and BLOCK_TAGS[tag] == self.kind: text = clean("".join(self.buffer)) if text: kind = "notice" if self.notice_depth and self.kind == "paragraph" else self.kind self.out.append((kind, text, self.heading_id if kind == "heading" else "")) self.buffer = None if tag == "div" and self.notice_depth: self.notice_depth -= 1
def handle_data(self, data): if self.cell is not None: self.cell.append(data) elif self.buffer is not None: self.buffer.append(data)
def parse_page(page) -> ParsedPage: """page: a snapshot.Page. Returns its blocks and clean text, with positions.""" reader = _Reader() reader.feed(page.body_html) reader.close() out = ParsedPage(page.page_id, page.title) section, anchor, parts, pos = "", "", [], 0 for kind, text, heading_id in reader.out: if DROP.search(text): out.dropped.append(text) continue if kind == "heading": section, anchor = text, heading_id shown = f"- {text}" if kind == "item" else text start = pos parts.append(shown) pos += len(shown) out.blocks.append(Block(kind, shown, section, anchor, start, pos)) parts.append("\n") pos += 1 out.text = "".join(parts) return outTrois choix comptent. Un tableau devient un seul bloc, pour qu’aucun segment ne puisse le couper. La ligne sur le gallois est supprimée, et dropped l’indique : un nettoyage visible est un nettoyage vérifiable. Enfin, start et end permettent de retrouver chaque bloc.
Exécutez les tests, puis analysez la page du salaire minimum :
python -m pytestpython -m guidance_assistant parse national-minimum-wage-rates========================= 8 passed, 8 skipped in 0.05s =========================National Minimum Wage and National Living Wage rates | https://www.gov.uk/national-minimum-wage-rates[ 0-90 ] paragraph | The hourly rate for the minimum wage depends on your age an…[ 275-288 ] heading Current rates | Current rates[ 289-328 ] paragraph Current rates | The rates change on 1 April every year.[ 329-417 ] table Current rates | | 21 and over | 18 to 20 | Under 18 | Apprentice April 2026…...[ 1095-1139 ] heading Rates between 1 April 202… | Rates between 1 April 2024 and 31 March 2026[ 1221-1384 ] table Rates between 1 April 202… | | 21 and over | 18 to 20 | Under 18 | Apprentice April 2025…...[ 1887-2077 ] table Rates before 1 April 2021 | | 25 and over | 21 to 24 | 18 to 20 | Under 18 | Apprentice…dropped: This page is also available in Welsh (Cymraeg).33 blocks, 2373 charactersCe que cela nous apprend. La page contient quatre tableaux, chacun dans un seul bloc sous son propre titre. Le tableau actuel et les anciens ont les mêmes colonnes et des nombres semblables. Seul le titre indique lequel est actuel. Gardez cela en tête pour la partie 3.
Partie 2 : des segments qui connaissent leur adresse
Section intitulée « Partie 2 : des segments qui connaissent leur adresse »Découpez les blocs en segments comme dans Découper de façon réfléchie. Chaque section forme un segment d’au plus 200 mots, sans couper un tableau, avec le titre de la page au début. Deux éléments sont nouveaux. L’identifiant du segment utilise le hachage de la page, de son updated_at et des positions, comme dans Conserver les métadonnées. Une page modifiée par GOV.UK reçoit de nouveaux identifiants : une ancienne citation ne peut donc pas pointer vers un nouveau texte. Chaque segment conserve aussi son adresse Web avec l’ancre et ses trois dates. Une citation peut ainsi mener à la section exacte.
Créez guidance_assistant/chunk.py :
"""Structure-aware chunks with stable IDs, as in the course's policy assistant.
One chunk per section (from a heading to the next heading; the text before the first heading is asection too). A section longer than MAX_WORDS is split between blocks, never inside a table.Each chunk starts with the page title (and the section heading when a part does not start with it),so a chunk keeps its context: "Exceptions" alone does not say which guide.
The chunk ID is the first 12 hexadecimal characters of SHA-256 over "page_id|updated_at|start|end":the same text of the same page gives the same ID every time, a changed page (a new updated_at)gives new IDs, and adding a page changes no other ID."""
import hashlibfrom dataclasses import asdict, dataclass
from .parse import ParsedPage
MAX_WORDS = 200
def chunk_id(page_id: str, updated_at: str, start: int, end: int) -> str: return hashlib.sha256(f"{page_id}|{updated_at}|{start}|{end}".encode("utf-8")).hexdigest()[:12]
@dataclass(frozen=True)class Chunk: chunk_id: str page_id: str title: str # the page title: "Guide: Part" section: str # the heading ("" = the top of the page) anchor: str # the heading's id on GOV.UK position: int start: int end: int text: str # what is indexed and shown to the model url: str # the page, with #anchor when the section has one public_updated_at: str updated_at: str retrieved_at: str
def to_dict(self) -> dict: return asdict(self)
def structure(parsed: ParsedPage, page, max_words: int = MAX_WORDS) -> list[Chunk]: sections: list[list] = [] for b in parsed.blocks: if b.kind == "heading" or not sections: sections.append([]) sections[-1].append(b) chunks: list[Chunk] = [] for blocks in sections: part: list = [] for b in blocks: words = sum(len(x.text.split()) for x in part) if part and words + len(b.text.split()) > max_words: chunks.append(_make(parsed, page, part, len(chunks))) part = [] part.append(b) if part: chunks.append(_make(parsed, page, part, len(chunks))) return chunks
def _make(parsed: ParsedPage, page, blocks: list, position: int) -> Chunk: start, end = blocks[0].start, blocks[-1].end section, anchor = blocks[0].section, blocks[0].anchor prefix = page.title if blocks[0].kind == "heading" or not section else f"{page.title}\n{section}" return Chunk( chunk_id=chunk_id(page.page_id, page.updated_at, start, end), page_id=page.page_id, title=page.title, section=section, anchor=anchor, position=position, start=start, end=end, text=f"{prefix}\n{parsed.text[start:end]}", url=page.url + (f"#{anchor}" if anchor else ""), public_updated_at=page.public_updated_at[:10], updated_at=page.updated_at[:10], retrieved_at=page.retrieved_at[:10])Exécutez les tests, construisez l’index et examinez les segments d’une page :
python -m pytestpython -m guidance_assistant ingestpython -m guidance_assistant chunks statutory-sick-pay/eligibility======================== 18 passed, 1 skipped in 0.14s =========================index index.sqlite: 504 chunks from 137 pages | embeddings intfloat/multilingual-e5-small | 31.5 s0a1832934dc9 #0 chars 0-221 | 48 words | https://www.gov.uk/statutory-sick-pay/eligibilitydaa76305e45b #1 chars 222-449 | 49 words | https://www.gov.uk/statutory-sick-pay/eligibility#telling-your-employerda9155fa4f2f #2 chars 450-580 | 28 words | https://www.gov.uk/statutory-sick-pay/eligibility#exceptions261d90e47a19 #3 chars 581-887 | 63 words | https://www.gov.uk/statutory-sick-pay/eligibility#linked-periods-of-sicknessa09f0d23000a #4 chars 888-1270 | 72 words | https://www.gov.uk/statutory-sick-pay/eligibility#fit-notes-and-asking-for-proofe668f89c2ed3 #5 chars 1271-1566 | 56 words | https://www.gov.uk/statutory-sick-pay/eligibility#other-proof-of-sickness1229b76afc00 #6 chars 1567-2114 | 110 words | https://www.gov.uk/statutory-sick-pay/eligibility#if-youre-not-eligible-or-your-ssp-ends7 chunksCe que cela nous apprend. Chaque section forme un segment, et chaque segment possède une adresse qui ouvre GOV.UK à cette section. Le segment médian contient 72 mots; le plus long en contient 211, dans un seul bloc impossible à diviser. ingest a pris 31,5 secondes sur notre Mac avec 2 fils d’exécution. L’essentiel du temps servait à charger le modèle e5 et à créer les plongements des 504 segments. Le seul test ignoré concerne sources.py.
Partie 3 : mesurer la recherche
Section intitulée « Partie 3 : mesurer la recherche »L’index contient les quatre méthodes de recherche du cours, sans modification. Mesurez-les sur les 36 questions ayant des passages pertinents (les 8 questions non couvertes n’en ont aucun) :
python -m guidance_assistant eval --method bm25python -m guidance_assistant eval --method densepython -m guidance_assistant eval --method hybridpython -m guidance_assistant eval --method rerankbm25, top 5: hit@5 0.778 | recall@5 0.701 | precision@5 0.244 | MRR 0.549 (36 questions with relevant passages)dense, top 5: hit@5 0.972 | recall@5 0.861 | precision@5 0.300 | MRR 0.867 (36 questions with relevant passages)hybrid, top 5: hit@5 0.889 | recall@5 0.829 | precision@5 0.289 | MRR 0.785 (36 questions with relevant passages)rerank, top 5: hit@5 0.944 | recall@5 0.859 | precision@5 0.294 | MRR 0.872 (36 questions with relevant passages)Le graphique ajoute le rappel à d’autres valeurs de k, comme dans Mesurer la récupération.
- bm25 (dashed)
- dense
- hybrid
- rerank
Description textuelle du graphique
Quatre courbes montrent le rappel pour k = 1, 3, 5 et 10. BM25 : 0,301, 0,595, 0,701, 0,843. Dense : 0,535, 0,792, 0,861, 0,956. Hybride : 0,484, 0,755, 0,829, 0,914. Reclassement : 0,604, 0,729, 0,859, 0,951. BM25 a le rappel le plus faible pour chaque k. Le reclassement a le plus élevé pour k = 1; la recherche dense et le reclassement sont proches à 5 et 10.
Ce que cela nous apprend. La tendance observée dans la collection de Larkfield se maintient sur des pages réelles. Les mots-clés manquent beaucoup de questions, car les gens n’utilisent pas les mêmes mots que les pages. Les vecteurs en trouvent davantage. La recherche hybride avec RRF reste inférieure à la recherche dense seule, pour la raison expliquée dans Recherche lexicale, dense et hybride. Le modèle de reclassement place le bon passage en premier le plus souvent (recall@1 de 0,604, MRR de 0,872). Cela a un coût : environ 340 ms par question sur notre Mac, contre 5,5 ms pour la recherche dense. Avec 36 questions, une question représente 0,028. La recherche dense et le reclassement ne se distinguent donc pas clairement. Nous gardons le reclassement, le choix par défaut du cours. Chaque méthode manque une des trois questions portant sur des tableaux, et cet échec est le plus instructif.
Une page, quatre tableaux
Section intitulée « Une page, quatre tableaux »Description textuelle du diagramme
La question demande le salaire horaire minimum d’une personne de 18 ans. Elle mène à une page contenant quatre tableaux de même forme. Le modèle de reclassement place le tableau des taux d’avant le 1er avril 2021 au rang 3. Il place celui de 2024 à 2026 au rang 4. Le tableau actuel, avec 10,85 £, arrive au rang 6, juste hors des 5 premiers. Le tableau de 2021 à 2024 arrive au rang 7. Le modèle reçoit donc deux anciens tableaux, mais pas le tableau actuel. chat-small s’est abstenu : « Les pages d’orientation ne précisent pas le salaire horaire minimum d’une personne de 18 ans à la date de récupération. »
Chez Larkfield, une ancienne politique était un document distinct avec des dates d’entrée en vigueur. Un filtre l’excluait. Ici, une page contient son propre historique : les taux actuels et trois tableaux antérieurs, sous des titres avec des dates. Pour un modèle de reclassement, « taux pour les 18 à 20 ans » se ressemble en 2020 et en 2026. BM25 réussit mieux sur cette question (le tableau actuel au rang 2). La recherche dense le place au rang 6, comme le modèle de reclassement.
Deux éléments ont rendu cet échec sûr. Le modèle a reçu le titre du tableau avec chaque passage (« section : taux avant le 1er avril 2021 »). De plus, la règle 3 de la requête interdit de deviner. chat-small n’a donc pas présenté un ancien taux comme le taux actuel. Il s’est abstenu. Pour corriger la récupération elle-même, attribuez à chaque section historique les dates de son titre comme dates d’entrée en vigueur. Filtrez ensuite par date, comme dans Recherche lexicale, dense et hybride. C’est une bonne prochaine étape pour votre version.
Partie 4 : une référence de base, le modèle seul
Section intitulée « Partie 4 : une référence de base, le modèle seul »Avant de compter sur les pages, mesurez ce que le modèle sait sans elles. C’est l’examen à livre ouvert de Ce qu’est la génération augmentée par récupération, mais sans le livre. Le projet peut poser chaque question à chat-small sans passages :
python -m guidance_assistant ask Q33 --closed-bookQuestion: My baby is due on 1 July 2026 and I joined my employer 10 weeks ago. Do I need to give 15 weeks' notice for paternity leave?The model alone, without the guidance:No—you must notify your employer **by the end of the 15th week before the expected week of childbirth**; it is not 15 weeks’ notice counted from when you joined. For a due date of 1 July 2026, that deadline was around 21 March 2026, so if you missed it, tell your employer as soon as possible. Since paternity leave became a day-one right for babies due from 6 April 2026, your short service should not prevent leave, though statutory paternity pay has separate eligibility rules.
gpt-6-luna-2026-09-22 | 75 tokens in, 1152 out | 7.874 sCette réponse est assurée et fausse. La page contient une règle pour ce cas précis. Pour un bébé attendu avant le 25 juillet 2026, une personne ayant moins de 26 semaines de service n’a pas à donner un préavis de 15 semaines. Le modèle dit que la date limite « était autour du 21 mars 2026 » et conseille à la personne de se dépêcher.
Sur l’ensemble des questions, le modèle seul a mieux réussi que prévu : selon les corrigés, 31 des 36 questions couvertes ont reçu une bonne réponse. Nous avons lu les 5 réponses signalées comme incorrectes. Trois disent la bonne chose autrement, une omet la principale limite (Q15), et une est fausse (Q33). L’exactitude n’est donc pas la raison pour laquelle Joy a besoin des pages. Voici les raisons :
- Aucune source ni date. Aucune réponse ne peut être vérifiée en ouvrant une page.
- Il répond à ce qu’il ne peut pas savoir à partir de GOV.UK. Sur les 8 questions non couvertes, il a répondu à 7 à partir de ses propres connaissances. Pour l’Irlande du Nord, il a dit « 123,25 £ par semaine pour 2026/27 ». Pour l’entente de règlement, il a donné un conseil personnel : « Ne l’acceptez pas et ne la signez pas avant… ». Une partie peut être vraie, mais rien ne vient des pages d’orientation, et rien ne l’indique.
- Les réponses fausses et justes se ressemblent. La réponse à Q33 a le même ton assuré que les 31 bonnes réponses.
Comme le cours l’a montré dans Ce qu’est la génération augmentée par récupération, les pages apportent des preuves et des dates. Elles ne font pas que réduire les inventions.
Partie 5 : les réponses avec leurs sources
Section intitulée « Partie 5 : les réponses avec leurs sources »Le pipeline de réponse est la fonction ask() du cours. Il manque ce que la personne voit. Le modèle donne seulement des affirmations et des identifiants de passages. L’application écrit les sources, en cherchant chaque identifiant cité dans l’index. Une ligne de source correspond donc toujours à une vraie page, avec son adresse et ses dates, même si le modèle se trompe. L’avis et l’attribution exigée par la licence sont des textes fixes, sous chaque réponse.
Créez guidance_assistant/sources.py :
"""Show an answer the way a person should see it: every claim numbered, every source with its addressand its dates, and what the assistant is not.
The application writes the sources and the notice, not the model: the model gives only claims andpassage IDs, and this code looks up each ID in the index. So a source line is always a real page,with the date we fetched it, even when the model is wrong."""
from .snapshot import ATTRIBUTION
NOTICE = ("This is what GOV.UK guidance says, not legal advice. This assistant is not a government service. " "Pages fetched on {fetched}: check the page for changes.")
def sources_for(answer, store) -> list: """The cited chunks, in the order of first citation, without repeats.""" chunks = [store.chunk(cid) for cid in answer.cited()] return [c for c in chunks if c is not None]
def render(answer, checks: list, store) -> str: if not answer.answerable: lines = [f"Answer: {answer.answer}", "", "The pages in the snapshot do not answer this question.", "Ask the Acas helpline or an adviser."] return "\n".join(lines + ["", NOTICE.format(fetched=_fetched(store)), ATTRIBUTION]) sources = sources_for(answer, store) number = {c.chunk_id: i for i, c in enumerate(sources, 1)} lines = [f"Answer: {answer.answer}", ""] for claim, check in zip(answer.claims, checks): refs = "".join(f"[{number[i]}]" for i in claim.chunk_ids if i in number) or "[no source]" status = "" if check.ok else " (check: " + "; ".join(check.problems) + ")" lines.append(f"- {claim.text} {refs}{status}") lines += ["", "Sources:"] for i, c in enumerate(sources, 1): section = f', section "{c.section}"' if c.section else "" lines.append(f"[{i}] {c.title}{section}") lines.append(f" {c.url}") lines.append(f" fetched {c.retrieved_at}; GOV.UK last updated {c.updated_at}") return "\n".join(lines + ["", NOTICE.format(fetched=_fetched(store)), ATTRIBUTION])
def _fetched(store) -> str: dates = sorted({c.retrieved_at for c in store.chunks()}) return dates[-1] if dates else "?"Exécutez les tests, puis posez une question enregistrée :
python -m pytestpython -m guidance_assistant ask Q14============================== 20 passed in 0.19s ==============================Question: I have worked for my employer for 18 months and was just dismissed. Can I claim unfair dismissal?Passages: 59652cdb4e72, f4443fade2e6, f44be5705d22, 407a19ade544, 023fd2596ef3 (1100 tokens, estimated)
Answer: The guidance says you must be classed as an employee and normally have 2 years’ service; with 18 months, you would not meet that usual qualifying period. In Northern Ireland, the usual period is 1 year, so 18 months meets the service-length requirement; there is no qualifying period if you were dismissed since 25 June 2013 because of your political opinions or affiliation. Any unfair dismissal claim must be made to a tribunal within 6 months of dismissal.
- You must be classed as an employee and normally have 2 years’ service to qualify, so 18 months does not meet the usual qualifying period. [1]- In Northern Ireland the usual qualifying period is 1 year, and there is no qualifying period for a dismissal since 25 June 2013 because of political opinions or affiliation. [1]- An unfair dismissal claim must be made to a tribunal within 6 months of dismissal. [1]
Sources:[1] Dismissal: your rights: What to do if you're dismissed, section "Qualifying period to claim unfair dismissal" https://www.gov.uk/dismissal/what-to-do-if-youre-dismissed#qualifying-period-to-claim-unfair-dismissal fetched 2026-10-09; GOV.UK last updated 2026-10-05
This is what GOV.UK guidance says, not legal advice. This assistant is not a government service. Pages fetched on 2026-10-09: check the page for changes.Contains public sector information licensed under the Open Government Licence v3.0.
gpt-6-luna-2026-09-22 | 1413 tokens in, 570 out | 4.761 sCe que cela nous apprend. La réponse conserve les conditions : statut d’employé, « normalement » 2 ans, l’exception et la limite de 6 mois. Elle dit ce que les pages d’orientation disent, plutôt que « vous ne pouvez pas déposer de demande ». C’est ce qu’exige la règle 5 de la requête. Toutes les affirmations ont passé les vérifications, et la personne qui conseille peut ouvrir la section avec la personne concernée.
Quand une vérification signale une bonne réponse
Section intitulée « Quand une vérification signale une bonne réponse »python -m guidance_assistant ask Q35- Almost all people classed as workers are entitled to 5.6 weeks’ paid holiday a year. [1]- At 4 working days a week, 5.6 weeks is 22.4 working days. [1][2] (check: number_not_found:22.4)La réponse est juste : 4 × 5,6 = 22,4. Mais 22,4 n’est pas écrit sur la page, donc la vérification des nombres le signale. C’est la limite présentée dans Répondre et citer. Le code peut vérifier qu’un nombre figure dans le passage, mais pas qu’un calcul est juste. Sur les 44 réponses, les vérifications ont signalé 4 affirmations sur 70, et toutes les 4 sont justes. Deux sont des calculs (Q35, Q36), et deux emploient d’autres mots que le passage (Q25, Q34). Aucune réponse n’a cité un identifiant inexistant ou absent de ses passages.
Quand les pages ne répondent pas
Section intitulée « Quand les pages ne répondent pas »python -m guidance_assistant ask Q43Answer: The guidance does not say what rate of Statutory Sick Pay applies in Northern Ireland.
The pages in the snapshot do not answer this question.Ask the Acas helpline or an adviser.Les passages sur les indemnités de maladie, avec 123,25 £, figuraient dans le contexte. Mais aucun passage ne dit qu’ils s’appliquent à l’Irlande du Nord. C’est l’abstention de Traiter les preuves difficiles; le modèle seul avait donné un montant.
Erreur courante
La ligne de source dit une chose, la page en dit une autre
Symptôme. Une personne qui conseille ouvre une adresse citée, et la section dit autre chose que l’affirmation. Ou l’adresse mène à une page GOV.UK qui ne figure pas du tout dans l’instantané.
Cause. Le modèle a écrit les lignes de source (la requête lui demandait d’« inclure l’URL »). Un modèle peut écrire une adresse qui semble réelle pour une page qu’il n’a jamais vue.
Correction. Laissez le modèle donner seulement les identifiants des passages. Cherchez chaque identifiant dans l’index, et rejetez ceux qui ne figurent pas dans le contexte. Écrivez le titre, l’adresse et les dates à partir de l’index, comme le fait sources.py. La page affichée est alors toujours celle d’où vient le passage.
Partie 6 : une comparaison équitable
Section intitulée « Partie 6 : une comparaison équitable »Toutes les exécutions ci-dessous utilisent les mêmes 44 questions, le même instantané, la requête answer_v1 et les 5 premiers passages. Le simulateur rejoue les réponses enregistrées sur Azure. D’abord, la question principale du cours : une meilleure récupération améliore-t-elle les réponses?
python -m guidance_assistant eval --answers --method rerankpython -m guidance_assistant eval --answers --method bm25chat-small, rerank, top 5, answer_v1: 44 questions answerable, correct: 34/36 answerable, abstained: 2/36 not covered, abstained: 8/8 shown but wrong: 0 (none) claims flagged by the checks: 4 of 70 tokens in / out (median): 1235 / 206 latency median / p95: 2.26 / 5.04 s (recorded) cost per question (median): US$0.00023chat-small, bm25, top 5, answer_v1: 44 questions answerable, correct: 28/36 answerable, abstained: 8/36 not covered, abstained: 7/8 shown but wrong: 1 (Q40) claims flagged by the checks: 3 of 59 tokens in / out (median): 1260 / 182 latency median / p95: 2.12 / 4.02 s (recorded) cost per question (median): US$0.00022La mesure la plus importante pour Joy est « affichée mais fausse » : une réponse qui n’est ni une abstention ni une bonne réponse. Une personne qui conseille la lirait comme si elle venait des pages d’orientation. Avec le reclassement, cette mesure est à zéro. Une meilleure récupération a donné 6 bonnes réponses de plus (28 → 34), avec le même modèle et le même coût. Avec BM25, le modèle s’est abstenu sur 8 questions couvertes. Sept fois, le bon passage n’avait pas été trouvé; une fois (Q32), il l’avait été. La seule réponse BM25 affichée mais fausse, Q40, est peu grave. La question demandait ce que verse le régime de maternité propre à un employeur. La réponse donnait la règle selon laquelle un régime ne peut pas verser moins que le montant légal. Elle aurait dû dire que les pages d’orientation ne le précisent pas.
Avec le reclassement, les deux échecs sont des échecs de récupération. Le passage pertinent ne figurait pas parmi les 5 premiers, et le modèle s’est abstenu. Q27 correspond aux quatre tableaux de la partie 3. Pour Q19 (indemnités de maternité après 3 mois dans un emploi), la section sur l’admissibilité n’a pas été trouvée. C’est l’analyse par étape de Trouver où ça échoue : aucune réponse fausse ne venait du modèle.
Ensuite, le modèle. Les réponses de chat-strong ont été enregistrées sur un échantillon : les 22 questions à numéro pair, qui incluent tous les types. Comparez-le à chat-small sur les mêmes 22 questions :
python -m guidance_assistant eval --answers --ids Q02,Q04,Q06,Q08,Q10,Q12,Q14,Q16,Q18,Q20,Q22,Q24,Q26,Q28,Q30,Q32,Q34,Q36,Q38,Q40,Q42,Q44python -m guidance_assistant eval --answers --ids Q02,Q04,Q06,Q08,Q10,Q12,Q14,Q16,Q18,Q20,Q22,Q24,Q26,Q28,Q30,Q32,Q34,Q36,Q38,Q40,Q42,Q44 --model chat-strong| Sur les 22 questions à numéro pair | chat-small | chat-strong |
|---|---|---|
| Couvertes, bonnes réponses | 18/18 | 18/18 |
| Non couvertes, abstentions | 4/4 | 4/4 |
| Affichées mais fausses | 0 | 0 |
| Affirmations (signalées) | 37 (2) | 51 (3) |
| Latence, médiane / p95 (enregistrée) | 2,35 / 4,28 s | 3,25 / 10,37 s |
| Coût par question (médiane) | 0,00023 $ US | 0,00417 $ US |
| Coût pour 1 000 questions | environ 0,25 $ US | environ 4,65 $ US |
Ce que cela nous apprend. Sur cet échantillon, le modèle plus puissant n’est pas plus juste. Il écrit plus d’affirmations (51 contre 37), est plus lent et coûte environ 18 fois plus par question. Pour quelques centaines de questions par mois, les deux coûtent très peu. Sans différence dans les mesures, chat-small est le choix à retenir. Un échantillon de 22 questions ne peut pas révéler une petite différence. Enregistrez donc les versions et mesurez de nouveau plus tard, comme dans La passation opérationnelle.
Avec le modèle de reclassement (environ 0,34 seconde sur un processeur) et l’appel au modèle (environ 2,3 secondes), l’attente est d’environ 3 secondes.
La conception finale, de bout en bout
Section intitulée « La conception finale, de bout en bout »La conception finale : les 5 premiers passages après reclassement, chat-small, la requête answer_v1 et les sources écrites par l’application. Voici une question du début à la fin. Elle contient une date, et le modèle seul y avait mal répondu :
python -m guidance_assistant ask Q33Question: My baby is due on 1 July 2026 and I joined my employer 10 weeks ago. Do I need to give 15 weeks' notice for paternity leave?Passages: 96d822f2d09f, dbdb4b6565f7, 1d938f602c0b, e30d70b6e089, 6f79065eed0a (1075 tokens, estimated)
Answer: Your baby’s due date is before 25 July 2026. You do not need to give 15 weeks’ notice if you’ll have been employed for less than 26 weeks up to any day in the qualifying week, which is the 15th week before the baby is due; you’ll still need to give 28 days’ notice of when you want leave to start and whether you want 1 or 2 weeks.
- A baby due on 1 July 2026 is due before 25 July 2026. [1]- For a baby due before 25 July 2026, 15 weeks’ notice is not required if the employee will have been employed for less than 26 weeks up to any day in the qualifying week. The qualifying week is the 15th week before the baby is due. [1]- The employee must still give 28 days’ notice of when they want leave to start and whether they want 1 or 2 weeks. [1]
Sources:[1] Paternity pay and leave: How to claim, section "If your baby is due before 25 July 2026" https://www.gov.uk/paternity-pay-leave/how-to-claim#if-your-baby-is-due-before-25-july-2026 fetched 2026-10-09; GOV.UK last updated 2026-10-05
This is what GOV.UK guidance says, not legal advice. This assistant is not a government service. Pages fetched on 2026-10-09: check the page for changes.Contains public sector information licensed under the Open Government Licence v3.0.
gpt-6-luna-2026-09-22 | 1496 tokens in, 649 out | 5.048 sLa recherche a trouvé la seule section écrite pour ce cas. La réponse applique la date de la question à celle de la page, conserve la condition et cite la section.
Pour poser votre propre question, vous avez besoin d’un modèle actif. Le simulateur connaît seulement les questions enregistrées et le précise : « Aucun enregistrement pour cette requête ». Copiez .env.example vers .env. Choisissez ensuite un modèle local gratuit avec Ollama, ou votre propre clé, comme dans le cours. Vos réponses différeront des réponses enregistrées; les vérifications et les sources fonctionnent de la même façon.
Constats et limites
Section intitulée « Constats et limites »- Les pages apportent des preuves et des dates plus que de l’exactitude. Modèle seul : 31 sur 36 selon les corrigés, aucune source, et 7 questions non couvertes sur 8 ont quand même reçu une réponse. Avec les pages : 34 sur 36, 8 abstentions sur 8, aucune réponse affichée mais fausse.
- La récupération comptait plus que le modèle : +6 bonnes réponses de BM25 au reclassement; aucune de chat-small à chat-strong, pour un coût 18 fois supérieur.
- Les deux échecs étaient des échecs de récupération, et les deux abstentions étaient sûres.
- Les pages contiennent leur propre historique, et la date de changement majeur de GOV.UK remontait à plusieurs années sur des pages affichant les taux de 2026.
- Limites. 44 questions écrites par une seule personne; 36 avec des passages, donc une question fait varier une mesure d’environ 0,03. Un seul instantané d’un seul sujet. Les réponses ont été enregistrées une fois; une autre exécution du même modèle peut différer. Les vérifications automatiques ne détectent pas tout, donc nous avons lu chaque réponse.
Utilisation responsable
Section intitulée « Utilisation responsable »- Pas de conseils juridiques. GOV.UK dit lui-même qu’il ne publie pas de conseils. L’assistant dit ce que les pages d’orientation disent, pas ce qu’une personne devrait faire (règle 5 de la requête). L’avis le précise sous chaque réponse. Une personne ayant un litige réel a besoin de conseils, d’Acas ou d’un avocat. Les délais comptent : les pages donnent des limites de 6 mois pour plusieurs demandes au tribunal.
- Pas un service officiel. La licence ne permet pas de suggérer un statut officiel. GOV.UK exige une autorisation avant que quiconque affirme avoir son approbation. N’utilisez pas le nom, la couronne ou le style visuel de GOV.UK dans le nom ou les écrans de l’assistant. L’avis dit : « Cet assistant n’est pas un service gouvernemental. »
- Affichez la source et la date de chaque page. Les pages d’orientation changent au moins chaque avril. Chaque réponse affiche la date de récupération et la dernière mise à jour de GOV.UK, et invite à vérifier la page. Actualisez l’instantané, puis exécutez les tests et l’évaluation avant de remplacer l’ancien.
- Dites où cela ne s’applique pas. Les pages concernent l’Angleterre, l’Écosse et le pays de Galles. Les questions d’Irlande du Nord nécessitent nidirect et la Labour Relations Agency. L’assistant s’abstient, et la personne qui conseille devrait savoir pourquoi.
- Données personnelles. Les personnes qui conseillent saisiront des situations réelles : noms, employeurs, santé. Gardez les questions hors des journaux (logs). Ne les envoyez pas à un modèle hébergé sans consentement et sans contrat couvrant cet usage. Seul l’appel au modèle quitte l’ordinateur.
- Attribution. Conservez la ligne d’attribution exigée par la licence partout où vous affichez ou publiez le texte des pages, y compris dans votre dépôt.
Faites-en un projet de portfolio
Section intitulée « Faites-en un projet de portfolio »Ce projet montre ce que les employeurs recherchent dans un travail de RAG. Il comprend une source de données sous licence, la provenance, une récupération mesurée, des citations vérifiées et l’abstention. Il offre aussi une comparaison honnête incluant le coût.
Quoi mettre dans le dépôt :
guidance-assistant/ README.md what it is, what it is not, how to run and check it, the results DATASET.md source, licence, attribution, fetch date, checksums, limits LICENSE MIT, for the code guidance/ the snapshot (OGL v3.0, with the attribution line in README and DATASET.md) guidance_assistant/ the code, including parse.py, chunk.py and sources.py questions/ the 44 questions with keys and passages recordings/ the recorded answers, so that anyone can replay them scripts/ check_snapshot.py, fetch_snapshot.py, build_snapshot.py tests/ pytest tests (20, no model download needed)Un modèle de README :
# guidance-assistant
## What it isA cited Q&A assistant over 137 GOV.UK pages on rights at work (snapshot of 2026-10-09),for volunteer advisers. Not legal advice; not a government service.
## Results (44 fixed questions, recorded 2026-10-09)- Search: rerank hit@5 0.944, MRR 0.872 (BM25 0.778, 0.549).- Answers, chat-small + rerank: 34/36 right, 8/8 abstained, 0 shown but wrong.- The model alone: 31/36 by the keys, no sources, answered 7 of 8 uncovered questions.- chat-strong: same scores on a 22-question sample, about 18 times the cost.
## Design choices- Official Content API, one fetch, SHA-256 checked snapshot; OGL v3.0 attribution.- Chunks keep the GOV.UK address with #anchor and the dates.- The model gives passage IDs; the application writes the sources.
## Limits(one snapshot, one topic, one author's questions, England, Scotland and Wales only)Comment le décrire en entrevue, en deux phrases : « J’ai créé un assistant avec citations à partir d’un instantané vérifié de 137 pages GOV.UK; le code vérifie chaque citation, et l’application écrit chaque source avec sa date. Sur 44 questions fixes, il a répondu correctement à 34 des 36 questions couvertes et s’est abstenu sur les 8 autres. »
Allez plus loin : chacune de ces pistes est une bonne prochaine étape, avec un changement principal.
- Des dates sur les sections. Convertissez les dates des titres, comme « Taux entre le 1er avril 2024 et le 31 mars 2026 », en dates d’entrée en vigueur. Filtrez selon la date de la question, puis mesurez de nouveau Q27 et les questions contenant une date.
- Une actualisation. Récupérez les mêmes chemins dans quelques mois. Comparez les sommes de contrôle, listez les pages modifiées et exécutez l’évaluation sur les deux instantanés. Quelles réponses enregistrées sont maintenant périmées?
- Une deuxième nation. Ajoutez les pages d’Irlande du Nord de nidirect (vérifiez d’abord leur licence) avec une étiquette « nation ». Filtrez selon cette étiquette, comme le cours filtrait selon l’accès.
- Un modèle actif. Exécutez les 44 questions avec un modèle local dans Ollama. Comparez la mesure « affichée mais fausse » à celle des modèles enregistrés.
Vérification des connaissances
Section intitulée « Vérification des connaissances »Vérifiez votre compréhension
Un collègue dit : « Le modèle seul a donné 31 bonnes réponses sur 36. Les pages ajoutent seulement 3 bonnes réponses. La recherche ne vaut donc pas le travail nécessaire. » Qu'oublie-t-il? Utilisez les chiffres de cette étude de cas.
Afficher la réponse
Il compte seulement les bonnes réponses. Sans les pages, aucune réponse n’a de source ni de date. Un conseiller ne peut donc pas distinguer les 31 bonnes réponses de la mauvaise; Q33 semblait tout aussi certaine. Pour les 8 questions non couvertes par les pages d’orientation, le modèle seul a répondu à 7 selon ses propres connaissances. Deux exemples : un taux d’indemnité de maladie pour Northern Ireland et des conseils personnels sur une entente de règlement. Avec les pages et le reclassement, il s’est abstenu pour les 8 questions, sans afficher de mauvaise réponse. Il a aussi lié chaque affirmation à une section datée. Pour un centre de conseil, une réponse vérifiable et un honnête « les pages d’orientation ne le précisent pas » comptent davantage que trois bonnes réponses supplémentaires.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Le tableau actuel du salaire minimum arrivait au 6e rang, après deux anciens tableaux de la même page. Pourquoi aucun mauvais taux n'a-t-il atteint le conseiller, et quel changement corrigerait la recherche elle-même?
Afficher la réponse
Chaque passage parvenait au modèle avec son titre de section, par exemple « Taux avant le 1er avril 2021 ». La requête interdit de deviner. Sans tableau actuel, chat-small s’est abstenu plutôt que de citer un ancien taux. Pour corriger la recherche, attribuez à chaque section historique les dates de son titre comme dates d’entrée en vigueur. Filtrez ensuite selon la date de la question avant de sélectionner les 5 premiers résultats, comme pour les versions des politiques dans le cours. Mesurez ensuite de nouveau les résultats des questions datées et des questions sur les tableaux. Les questions datées doivent utiliser leur propre date.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Pourquoi sources.py cherche-t-il chaque identifiant cité dans l'index au lieu de demander au modèle d'écrire l'adresse de la page?
Afficher la réponse
Un modèle peut écrire une adresse qui semble réelle, mais mène à une autre page ou à une page absente de l’instantané. Les identifiants sont vérifiables : les contrôles des citations rejettent tout identifiant inexistant ou absent du contexte. L’application écrit le titre, l’adresse et les dates à partir de l’index. Chaque ligne de source correspond donc à la page d’origine du passage, avec ses dates réelles. Le modèle reste responsable des affirmations. Les contrôles signalent celles dont les nombres ou les mots ne figurent pas dans le passage.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Récapitulatif
- Vérifiez la licence des pages exactes et ses conditions : attribution et aucune suggestion de statut officiel.
- Récupérez les pages une seule fois avec l’API officielle. Conservez un instantané avec les sommes de contrôle et la date de récupération de chaque page.
- Conservez le chemin vers la source : blocs avec section et ancre, segments avec identifiants et adresses stables.
- Les pages contiennent leur propre historique : les titres et les dates doivent accompagner chaque passage.
- Le modèle fournit les affirmations et les identifiants; l’application écrit les sources, les dates et l’avis.
- Une meilleure récupération a donné 6 bonnes réponses supplémentaires; le modèle puissant n’en a ajouté aucune, pour un coût 18 fois supérieur.
Vous avez créé un assistant dont un bénévole peut vérifier les réponses page par page. La prochaine étude de cas porte sur la recherche dans une documentation technique, où les noms exacts et les reformulations comptent.
Leçon suivante : Chercher dans une documentation technique