Accueil / Articles / Notes pratiques : Créer un pipeline RAG local de niveau production — 100 % gratuit, sans

Notes pratiques : Créer un pipeline RAG local de niveau production — 100 % gratuit, sans

Guide pas à pas des notes pratiques : Créer un pipeline RAG local de niveau production — 100 % gratuit, sans contrats, vérifications ni emplacements prévus pour du code destiné aux équipes utilisant ce modèle.

4700 mots

Ce guide reconstitue le parcours allant des matières premières à un système fonctionnel pour : créer un pipeline RAG local de niveau production — 100 % gratuit, sans nécessité d’utiliser le cloud. L’accent est mis sur des étapes opérationnelles, des vérifications explicites, ainsi que du code que vous pouvez intégrer directement dans un dépôt sans devoir deviner son intention. Pour l’étape d’aperçu, définissez les entrées, le responsable de l’étape et les critères de fin avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu, sans avoir à deviner l’état caché. Documentez conjointement le parcours normal et celui de récupération. Les tentatives de réexécution, les contrôles humains et la gestion des messages non traités font partie intégrante du produit, et non d’une mise en forme ultérieure.

Pourquoi c’est important

Lors de l’étape « Pourquoi c’est important », notez d’abord les éléments essentiels : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Préférez des unités petites et testables à des scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité plutôt qu’un processus embrouillé. Évaluez le taux de rappel sur un ensemble de questions fixe avant d’ajuster les prompts. Un changement fréquent des prompts ne résout que rarement un système de récupération insuffisant.

L’ensemble complet du stack technique

Lors de la phase « The Complete Tech Stack », notez d’abord les conditions du contrat : entrées requises, signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Considérez cette phase comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définez des vérifications de succès et refusez les terminaisons partielles silencieuses. Évaluez le taux de rappel sur un ensemble fixe de questions avant d’ajuster les prompts. Un changement fréquent des prompts ne résout que rarement un système de récupération insuffisant.

Prérequis

Lors de la phase des Prérequis, notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Enregistrez les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les factures inattendues lorsque le processus passe de la démonstration aux environnements partagés. Mesurez le taux de rappel sur un ensemble fixe de questions avant d’ajuster les prompts. Changer fréquemment les prompts ne résout que rarement un système de récupération insuffisant. Lors de la phase des Prérequis, notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Documentez ensemble le parcours optimal et le parcours de récupération. Les tentatives de réessai, les contrôles humains et la gestion des messages non traités font partie intégrante du produit, et non d’une mise en forme ultérieure.

Partie 1 — Mise en place de l’environnement

La phase de configuration de l’environnement, partie 1, fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript parfait, un cas d’échec et la note de réversion avant d’élargir le périmètre. Préférez des unités petites et testables aux scripts complexes. Lorsqu’une étape échoue, l’échec doit pointer vers une seule responsabilité plutôt que vers un processus embrouillé. Séparez la politique de segmentation de la politique de récupération. Modifier l’une ne doit pas obliger à réécrire l’autre lorsque les métriques de qualité changent.

Étape 1 : Créer le projet

La première étape, « Créer la scène », fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un exemple réussi, un cas d’échec ainsi que la note de réversion avant d’élargir le périmètre du projet. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définez des critères de succès et refusez toute mise en œuvre partielle silencieuse. Séparez la politique de segmentation des données de la politique de récupération. Modifier l’une ne doit pas obliger à réécrire l’autre lorsque les métriques de qualité évoluent.

mkdir local-rag
cd local-rag
uv init

Étape 2 : Créer et activer l’environnement virtuel

La étape 2 « Créer et mettre en phase » fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Enregistrez les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les factures inattendues lorsque le processus passe de la démonstration aux environnements partagés. Séparez la politique de segmentation de la politique de récupération. Modifier l’une ne doit pas obliger à réécrire l’autre lorsque les métriques de qualité évoluent.

uv venv
.venv\Scripts\activate

La étape 2 « Créer et mettre en phase » fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Documentez ensemble le parcours réussi et le parcours de récupération. Les tentatives de réessai, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’une mise en forme ultérieure.

Étape 3 : Installer toutes les dépendances

Pour l’Étape 3 « Installer toutes les étapes », définissez les entrées, le responsable de l’étape et les critères de fin avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Préférez des unités petites et testables à des scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité plutôt qu’un processus embrouillé. Citez les passages qui ont réellement servi de base à la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation.

uv add google-genai pypdf chromadb rich python-dotenv huggingface_hub fpdf2

Étape 4 : Créer la structure du projet

Pour l’Étape 4 « Créer l’étape », définissez les entrées, le responsable de l’étape ainsi que les critères de fin avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définites des vérifications de succès et refusez les terminaisons partielles silencieuses. Citez les passages qui ont réellement servi de base à la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation.

mkdir pdfs
mkdir pdfs\versions
type nul > local_rag.ipynb
type nul > .env
type nul > .gitignore
local-rag/
├── .venv/                    ← virtual environment (never commit)
├── pdfs/                     ← drop your PDFs here
│   └── versions/             ← test PDFs for CDC testing
├── chroma_db/                ← auto-created on first ingest
├── memory_checkpoints/       ← auto-created on first memory session
├── staleness_registry.json   ← auto-created
├── chunk_registry.json       ← auto-created
├── local_rag.ipynb           ← your notebook
├── .env                      ← API keys (never commit)
├── .gitignore
└── pyproject.toml

Étape 5 : Configurer les clés API

Pour l’étape 5 « Configurer API », définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Enregistrez les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les factures inattendues lorsque le processus passe d’un environnement de démonstration à des environnements partagés. Citez les passages qui servent réellement de base à la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation. Pour l’étape 5 « Configurer API », définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Documentez ensemble le parcours idéal et les scénarios de récupération. Les tentatives de réexécution, les contrôles humains et la gestion des messages non traités font partie intégrante du produit, et non d’améliorations ultérieures.

GEMINI_API_KEY=your_gemini_key_here
HF_API_KEY=your_huggingface_token_here
.env
chroma_db/
memory_checkpoints/
staleness_registry.json
chunk_registry.json
__pycache__/
.venv/
*.pyc

Étape 6 : Installer VS Code

Lors de l’exécution de l’étape 6 relative à l’installation, notez d’abord les exigences : entrées requises, signal de succès et conséquences en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Préférez des unités petites et testables plutôt que des scripts volumineux. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité et non un processus complexe. Évaluez le taux de rappel sur un ensemble de questions fixe avant d’ajuster les prompts. Un changement fréquent des prompts ne résout généralement pas un système de récupération insuffisant.

Ctrl+Shift+P → Python: Select Interpreter → .venv\Scripts\python.exe

Partie 2 — Décortiquage du pipeline principal

Lorsque vous travaillez sur l’étape du pipeline principal de la Partie 2, notez d’abord les conditions contractuelles : les entrées requises, le signal de succès, ainsi que ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de garantir l’honnêteté des modifications ultérieures du code. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définez des vérifications de succès, et refusez les terminations partielles silencieuses. Mesurez le taux de rappel sur un ensemble de questions fixe avant d’ajuster les prompts. Le simple changement de prompts ne résout que rarement un système de récupération insuffisant.

Cellule 1 — Dépendances (à l’intérieur du cahier de notes)

Lorsque vous travaillez sur les dépendances de la Cellule 1 au sein de l’étape, notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de garantir l’honnêteté des modifications de code ultérieures. Enregistrez les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite des factures inattendues lorsque le processus passe de l’environnement de démonstration à des environnements partagés. Mesurez le taux de rappel sur un ensemble fixe de questions avant d’ajuster les prompts. Un changement fréquent des prompts ne résout que rarement un système de récupération insuffisant.

# Run once inside the notebook if uv add was not used externally
# %pip install google-genai pypdf chromadb rich python-dotenv huggingface_hub fpdf2

Lorsque vous travaillez sur les dépendances de la Cellule 1 au sein de l’étape, notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de garantir l’honnêteté des modifications de code ultérieures. Documentez en même temps le parcours optimal et le parcours de récupération. Les tentatives de réessai, les contrôles humains et la gestion des messages non traités font partie intégrante du produit, et non d’améliorations apportées ultérieurement.

Cellule 2 — Configuration

L’étape de configuration de la Cellule 2 fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript parfait, un cas d’échec et la note de réversion avant d’élargir le périmètre. Préférez des unités petites et testables plutôt que des scripts complexes. Lorsqu’une étape échoue, l’échec doit pointer vers une seule responsabilité et non vers un processus embrouillé. Séparez la politique de segmentation de la politique de récupération. Modifier l’une ne doit pas obliger à réécrire l’autre lorsque les métriques de qualité changent.

import os
from dotenv import load_dotenv

load_dotenv()
GEMINI_API_KEY     = os.environ.get("GEMINI_API_KEY", "")
EMBED_DIM          = 768
HF_API_KEY         = os.environ.get("HF_API_KEY", "")
GEMINI_EMBED_MODEL = "gemini-embedding-001"
HF_LLM_MODEL       = "openai/gpt-oss-20b:groq"
CHROMA_DB_PATH     = "./chroma_db"
COLLECTION_NAME    = "local_rag"
CHUNK_SIZE         = 800
CHUNK_OVERLAP      = 120
TOP_K              = 5
EMBED_BATCH_SIZE   = 50
BATCH_SLEEP_SEC    = 0.3
LLM_MAX_NEW_TOKENS = 1024
LLM_TEMPERATURE    = 0.1
assert GEMINI_API_KEY, "❌ GEMINI_API_KEY not set"
assert HF_API_KEY,     "❌ HF_API_KEY not set"

Cellule 3 — Extraction de texte PDF

La phase de texte PDF de Cell 3 fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript parfait, un cas d’échec et la note de réversion avant d’élargir le périmètre. Considérez cette phase comme un contrat entre les entrées et les sorties validées. Nommez les artefacts, définez des vérifications de succès, et refusez toute complétion partielle silencieuse. Séparez la politique de segmentation en blocs de la politique de récupération. Modifier l’une ne doit pas obliger à réécrire l’autre lorsque les métriques de qualité évoluent.

from pypdf import PdfReader

def extract_text_from_pdf(pdf_path: str) -> tuple[str, int]:
    reader = PdfReader(pdf_path)
    pages = []
    for i, page in enumerate(reader.pages):
        text = page.extract_text()
        if text and text.strip():
            pages.append(f"[Page {i + 1}]\n{text.strip()}")
    return "\n\n".join(pages), len(reader.pages)

Cell 4 — Segmentation par fenêtre glissante

La phase de la fenêtre glissante Cell 4 fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Enregistrez les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les factures inattendues lorsque le processus passe de la démonstration aux environnements partagés. Séparez la politique de segmentation de la politique de récupération. Modifier l’une ne doit pas obliger à réécrire l’autre lorsque les métriques de qualité évoluent.

def chunk_text(text: str) -> list[str]:
    chunks, start = [], 0
    while start < len(text):
        end   = start + CHUNK_SIZE
        chunk = text[start:end].strip()
        if len(chunk) >= 80:
            chunks.append(chunk)
        start += CHUNK_SIZE - CHUNK_OVERLAP
    return chunks

La phase de la fenêtre glissante Cell 4 fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Documentez ensemble le parcours réussi et le parcours de récupération. Les tentatives de réessai, les contrôles humains et le traitement des messages non livrés font partie intégrante du produit, et non d’une mise en forme ultérieure.

Cell 5 — Gemini Embeddings

Pour l’étape des embeddings Gemini de Cell 5, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Préférez des unités petites et testables à des scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité plutôt qu’un pipeline embrouillé. Séparez la construction du client du cycle de messages afin que les fournisseurs puissent être remplacés sans avoir à réécrire la machine d’état de la conversation.

from google import genai
from google.genai import types

genai_client = genai.Client(api_key=GEMINI_API_KEY)
def embed_documents_batch(chunks: list[str]) -> list[list[float]]:
    all_embeddings = []
    for i, chunk in enumerate(chunks, 1):
        result = genai_client.models.embed_content(
            model=GEMINI_EMBED_MODEL,
            contents=chunk,
            config=types.EmbedContentConfig(
                task_type="RETRIEVAL_DOCUMENT",
                output_dimensionality=EMBED_DIM,
            ),
        )
        all_embeddings.append(result.embeddings[0].values)
    return all_embeddings
def embed_query(text: str) -> list[float]:
    result = genai_client.models.embed_content(
        model=GEMINI_EMBED_MODEL,
        contents=text,
        config=types.EmbedContentConfig(
            task_type="RETRIEVAL_QUERY",
            output_dimensionality=EMBED_DIM,
        ),
    )
    return result.embeddings[0].values

Cell 6 — Stockage et récupération dans ChromaDB

Pour l’étape de stockage Cell 6 ChromaDB, définissez les entrées, le responsable de l’étape et les critères de fin avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Nommez les artefacts, définissez des vérifications de succès et refusez toute exécution partielle silencieuse. Citez les passages qui justifient réellement la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation.

import uuid, chromadb

def get_collection():
    client = chromadb.PersistentClient(path=CHROMA_DB_PATH)
    return client.get_or_create_collection(
        name=COLLECTION_NAME,
        metadata={"hnsw:space": "cosine"},
    )
def store_in_chroma(chunks, embeddings, doc_name):
    collection = get_collection()
    ids        = [str(uuid.uuid4()) for _ in chunks]
    metadatas  = [{"source": doc_name, "chunk_index": i}
                  for i in range(len(chunks))]
    collection.add(ids=ids, embeddings=embeddings,
                   documents=chunks, metadatas=metadatas)
    return len(chunks)
def retrieve_context(query: str) -> list[dict]:
    collection      = get_collection()
    query_embedding = embed_query(query)
    results = collection.query(
        query_embeddings=[query_embedding],
        n_results=TOP_K,
        include=["documents", "metadatas", "distances"],
    )
    chunks = []
    for doc, meta, dist in zip(results["documents"][0],
                               results["metadatas"][0],
                               results["distances"][0]):
        chunks.append({
            "text":        doc,
            "source":      meta.get("source", "unknown"),
            "chunk_index": meta.get("chunk_index", -1),
            "score":       round(1 - dist, 4),
        })
    return sorted(chunks, key=lambda x: x["score"], reverse=True)

Cell 7 — Exécution de l’ingestion

Pour l’étape d’ingestion en cours de Cell 7, définissez les entrées, le responsable de l’étape et les critères de fin avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu, sans avoir à deviner l’état caché. Enregistrez les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts permet d’éviter des factures inattendues lorsque le processus passe de l’environnement de démonstration à des environnements partagés. Citez les passages qui ont réellement servi de base à la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation.

PDF_PATH = "./pdfs/attention.pdf"

raw_text, page_count = extract_text_from_pdf(PDF_PATH)
chunks               = chunk_text(raw_text)
embeddings           = embed_documents_batch(chunks)
stored               = store_in_chroma(chunks, embeddings,
                                        os.path.basename(PDF_PATH))

Pour l’étape d’ingestion en cours de Cell 7, définissez les entrées, le responsable de l’étape et les critères de fin avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Documentez conjointement le parcours normal et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’une amélioration ultérieure.

Cell 8 — LLM : gpt-oss-20b via HuggingFace

Lors du travail sur l’étape Cell 8 LLM gpt-oss-20b, notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Préférez des unités petites et testables plutôt que des scripts volumineux. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité et non un processus embrouillé. Cachez les instructions du système stables ainsi que les schémas des outils. Envoyer à nouveau un préambule identique est une cause fréquente de gaspillage.

from huggingface_hub import InferenceClient

hf_client = InferenceClient(api_key=HF_API_KEY)
def build_messages(query: str, context_chunks: list[dict]) -> list[dict]:
    context_str  = "\n\n---\n\n".join([
        f"[Source: {c['source']} | Chunk #{c['chunk_index']} | "
        f"Relevance: {c['score']}]\n{c['text']}"
        for c in context_chunks
    ])
    return [
        {"role": "system",  "content": SYSTEM_MSG},
        {"role": "user",    "content":
            f"CONTEXT:\n{context_str}\n\nQUESTION:\n{query}"},
    ]
def generate_answer(messages: list[dict]) -> str:
    completion = hf_client.chat.completions.create(
        model=HF_LLM_MODEL,
        messages=messages,
        max_tokens=LLM_MAX_NEW_TOKENS,
        temperature=LLM_TEMPERATURE,
    )
    return completion.choices[0].message.content.strip()

Cell 9–11 — Le pipeline ask() et REPL

Lorsque vous travaillez sur l’étape « Cellule 9 11 : La scène », notez d’abord le contrat : les entrées requises, le signal de succès, ainsi que ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définez des vérifications de succès, et refusez les terminations partielles silencieuses. Mesurez le taux de rappel sur un ensemble fixe de questions avant d’ajuster les prompts. Un changement fréquent des prompts ne résout que rarement un système de récupération insuffisant.

query → embed_query() → ChromaDB cosine search → top-5 chunks
      → build_messages() → generate_answer() → printed answer
1. attention.pdf chunk #34  [██████████████████████░░░░░░░░]  0.7335

Partie 3 — Mémoire de conversation

Lors de l’étape 3 « Mémoire de conversation », notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Enregistrez les temps d’exécution ainsi que le coût en tokens ou en requêtes à côté des résultats fonctionnels. Une visibilité précoce du coût évite des factures inattendues lorsque le système passe de l’environnement de démonstration à des environnements partagés. Évaluez la capacité de rappel sur un ensemble fixe de questions avant d’ajuster les prompts. Changer fréquemment les prompts ne résout que rarement un système de récupération inefficace.

Le problème

Lors de l’étape de définition du problème, notez d’abord les exigences : les entrées nécessaires, le signal de succès, ainsi que ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les administrateurs peuvent auditer sans devoir lire l’ensemble du système. Mesurez le taux de rappel sur un ensemble fixe de questions avant d’ajuster les prompts. Un changement fréquent des prompts ne résout que rarement un système de récupération insuffisant.

La solution : ConversationMemory

Lorsque vous travaillez sur l’étape ConversationMemory de la solution, notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Documentez ensemble le parcours normal et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non traités font partie intégrante du produit, et non d’améliorations apportées ultérieurement. Évaluez le taux de rappel sur un ensemble fixe de questions avant d’ajuster les prompts. Le changement fréquent des prompts ne résout que rarement un système de récupération insuffisant.

class ConversationMemory:
    def __init__(self, session_id: str = None):
        self.session_id = session_id or datetime.now().strftime("%Y%m%d_%H%M%S")
        self.filepath   = os.path.join(MEMORY_DIR, f"{self.session_id}.json")
        self.history    = []
        # Auto-loads if resuming an existing session
        if os.path.exists(self.filepath):
            self._load()

    def add_turn(self, question: str, answer: str, chunks: list[dict]):
        # Append user + assistant turns, checkpoint immediately
        ...
        self._save()
    def get_messages_with_history(self, query, context_chunks, system_msg):
        # Injects last 6 Q&A pairs into the message list before the current turn
        ...
# New session
memory = ConversationMemory()

# Resume yesterday's session
memory = ConversationMemory("20260413_104959")
Turn 4 question: "How does that compare to what you said about the BLEU score?"
Turn 4 answer: "The context also reports a BLEU score of 28.4 for the
               Transformer (big) on WMT 2014 English-to-German. This matches
               exactly what I previously stated."

Partie 4 — Suivi de la péremption, CDC et pondération de la fraîcheur

Lors de la phase 4 de suivi de l’obsolescence, notez d’abord les exigences du contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Préférez des unités petites et testables à des scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité plutôt qu’un processus embrouillé. Évaluez le taux de rappel sur un ensemble fixe de questions avant d’ajuster les prompts. Un changement fréquent des prompts ne résout que rarement un système de récupération insuffisant.

Le problème dans le monde réel

Lors de l’étape du « Problème du monde réel », écrivez d’abord le contrat : les entrées requises, le signal de succès, ainsi que ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définez des vérifications de succès, et refusez les terminaisons partielles silencieuses. Mesurez le taux de rappel sur un ensemble de questions fixe avant d’ajuster les prompts. Le changement fréquent des prompts ne résout que rarement un système de récupération insuffisant.

Suivi de la péremption (Cellule 14A)

Lors du travail sur l’étape Cellule de suivi de la péremption 14A, notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de garantir l’honnêteté des modifications ultérieures du code. Enregistrez les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les factures inattendues lorsque le processus passe de l’environnement de démonstration à des environnements partagés. Mesurez le taux de rappel sur un ensemble fixe de questions avant d’ajuster les prompts. Un changement fréquent des prompts ne résout que rarement un système de récupération insuffisant. Lors du travail sur l’étape Cellule de suivi de la péremption 14A, notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de garantir l’honnêteté des modifications ultérieures du code. Documentez ensemble le parcours normal et le parcours de récupération. Les tentatives de réessai, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’une mise en forme ultérieure.

def compute_file_hash(pdf_path: str) -> str:
    sha = hashlib.sha256()
    with open(pdf_path, "rb") as f:
        for block in iter(lambda: f.read(65536), b""):
            sha.update(block)
    return sha.hexdigest()

Moteur CDC (Cellule 14B)

La cellule moteur CDC 14B fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript doré, un cas d’échec et la note de réversion avant d’élargir le périmètre. Préférez des unités petites et testables aux scripts complexes. Lorsqu’une étape échoue, l’échec doit pointer vers une seule responsabilité plutôt que vers un processus embrouillé. Séparez la politique de segmentation de la politique de récupération. Modifier l’une ne doit pas obliger à réécrire l’autre lorsque les métriques de qualité changent.

def compute_chunk_hash(text: str) -> str:
    return hashlib.md5(text.encode("utf-8")).hexdigest()

def diff_chunks(old_registry: dict, new_chunks: list[str]) -> dict:
    new_hash_map = {compute_chunk_hash(c): c for c in new_chunks}
    old_hashes   = set(old_registry.keys())
    new_hashes   = set(new_hash_map.keys())
    return {
        "added":     {h: new_hash_map[h] for h in (new_hashes - old_hashes)},
        "removed":   {h: old_registry[h] for h in (old_hashes - new_hashes)},
        "unchanged": {h: old_registry[h] for h in (old_hashes & new_hashes)},
    }
v1 → v2 CDC result:
  ✅ Unchanged : 8   (kept — zero re-embedding cost)
  ➕ Added     : 6   (embedded + inserted)
  ➖ Removed   : 4   (deleted from ChromaDB)
  💰 API calls saved: 8/14 (57% reuse)

v2 → v3 CDC result:
  ✅ Unchanged : 10  (kept - zero re-embedding cost)
  ➕ Added     : 4   (embedded + inserted)
  ➖ Removed   : 2   (deleted from ChromaDB)
  💰 API calls saved: 10/14 (71% reuse)

Récupération pondérée par récenteté (Cellule 14C)

La phase Recency-Weighted Retrieval Cell 14C fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript parfait, un cas d’échec et la note de réversion avant d’élargir le champ d’action. Considérez cette phase comme un contrat entre les entrées et les sorties validées. Nommez les artefacts, définez des vérifications de succès et refusez toute complétion partielle silencieuse. Séparez la politique de segmentation de la politique de récupération. Modifier l’une ne doit pas obliger à réécrire l’autre lorsque les métriques de qualité évoluent.

def recency_decay(ingested_at_str: str, half_life_days: float = 30) -> float:
    ingested  = datetime.fromisoformat(ingested_at_str)
    days_gone = (datetime.now() - ingested).total_seconds() / 86400
    λ         = math.log(2) / half_life_days
    return round(math.exp(-λ * days_gone), 4)
blended = alpha * cosine_score + (1 - alpha) * recency_score
# Default: 0.85 * cosine + 0.15 * recency

Partie 5 — Tests avec des PDF multi-versions

Le test de la partie 5 avec un environnement de staging fonctionne le mieux lorsqu’il est considéré comme une surface mesurable. Capturez un transcript idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Enregistrez les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les factures inattendues lorsque le processus passe d’un environnement de démonstration à des environnements partagés. Séparez la politique de segmentation de la politique de récupération. Modifier l’une ne doit pas obliger à réécrire l’autre lorsque les métriques de qualité évoluent. Le test de la partie 5 avec un environnement de staging fonctionne le mieux lorsqu’il est considéré comme une surface mesurable. Capturez un transcript idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Documentez ensemble le parcours réussi et le parcours de récupération. Les tentatives de réessai, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’une mise en forme ultérieure.

TEST 1: Full ingest of v1
TEST 2: Staleness check — same file, correctly skipped
TEST 3: Baseline queries against v1
TEST 4: Copy v2 over active file → CDC kicks in
TEST 5: Same queries now return v2 content, newer chunks visible in recency scores
TEST 6: Copy v3 over active file → second CDC cycle
TEST 7: Recency verification — v3 chunks score highest across the board
TEST 8: Full stack test — weighted retrieval + conversation memory combined

Résultats et réponses vérifiées

Pour l’étape des Résultats et Réponses vérifiées, définissez les entrées, le responsable de l’étape ainsi que les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Préférez des unités petites et testables à des scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité plutôt qu’un processus embrouillé. Citez les passages qui ont réellement servi de base à la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation.

Résumé de l’efficacité

Pour l’étape de résumé de l’efficacité, définissez les entrées, le responsable de l’étape et les critères de fin avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définites des vérifications de succès et refusez les terminaisons partielles silencieuses. Citez les passages qui ont réellement servi de base à la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation.

Limites connues

Pendant l’étape des Limites connues, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Enregistrez les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les factures inattendues lorsque le processus passe d’un environnement de démonstration à des environnements partagés. Citez les passages qui ont réellement servi de base à la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation. Pendant l’étape des Limites connues, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Documentez ensemble le parcours idéal et le parcours de récupération. Les tentatives de réessai, les contrôles humains et la gestion des messages non traités font partie intégrante du produit, et non d’une amélioration ultérieure.

Ce que vous avez construit

Lors de l’étape « Ce que vous avez construit », notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Préférez des unités petites et testables aux scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité plutôt qu’un processus embrouillé. Mesurez le taux de rappel sur un ensemble fixe de questions avant d’ajuster les prompts. Un changement fréquent des prompts ne résout que rarement un système de récupération insuffisant.

PDF on disk
 └► SHA256 hash check (staleness)
      ├► Unchanged → skip
      └► Changed → CDC diff
           ├► Unchanged chunks → kept in ChromaDB (zero API cost)
           ├► Removed chunks → deleted from ChromaDB
           └► Added chunks → embed (Gemini) → store (ChromaDB)
                                                    ↓
User question
 └► embed_query() [RETRIEVAL_QUERY task type]
      └► ChromaDB cosine search (TOP_K × 3 candidates)
           └► recency_decay() per chunk
                └► blended score re-ranking
                     └► top-5 chunks as context
                          └► ConversationMemory.get_messages_with_history()
                               └► gpt-oss-20b via Groq/HuggingFace
                                    └► grounded answer + checkpoint to disk

Liste de contrôle opérationnelle

L’étape de la liste de contrôle opérationnelle fonctionne le mieux lorsqu’elle est considérée comme un indicateur mesurable. Capturez une transcription exemplaire, un cas d’échec et des notes de réversion avant d’élargir le périmètre.

Gardez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans avoir à lire l’ensemble du système.

Séparez la politique de segmentation de la politique de récupération. Modifier l’une ne doit pas obliger à réécrire l’autre lorsque les métriques de qualité évoluent.

Ajoutez un test de base qui exécute le parcours critique dans l’environnement CI à l’aide de fichiers de configuration, et non d’API payantes en production, chaque fois que le budget le permet.

Dokumentez conjointement le parcours normal et le parcours de récupération. Les tentatives de réessai, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’améliorations ultérieures.

Séparez la politique de segmentation de la politique de récupération. Modifier l’une ne doit pas obliger à réécrire l’autre lorsque les métriques de qualité évoluent.

Au préalable de promouvoir l’ensemble des composants, figez les versions, conservez une transcription exemplaire pour le chemin critique, et vérifiez les étapes de réversion. Les environnements partagés nécessitent des limites de fréquence d’accès, des contrôles de location, ainsi qu’un responsable clair pour la rotation des secrets. Préférez une fiabilité solide à des démonstrations brillantes mais ponctuelles.

Note de batch pour 8d172e929623 : gardez les clés du fournisseur en dehors du répertoire, fixez un plafond pour les tokens par session, et stockez les transcriptions à côté des fichiers de configuration d’évaluation afin que les remplacements ultérieurs de modèles restent comparables.