Accueil / Articles / RAG sans la mystique : récupération puis génération

RAG sans la mystique : récupération puis génération

Un chemin simple, allant du découpage et des index aux réponses fondées pouvant faire l’objet d’un contrôle grâce à des opérateurs de citation.

2179 mots

Utilisez ceci comme une version révisée destinée aux opérateurs des idées présentées dans « RAG, Explained the Way I Wish Someone Had Explained It to Me » : étapes claires, emplacements de code ordonnés, ainsi que des notes de récupération qui survivent au transfert de responsabilités. L’aperçu fonctionne le mieux lorsqu’il est considéré comme une surface mesurable. Capturez un enregistrement exemplaire, un cas d’échec et la note de réversion avant d’élargir le périmètre. 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 livrés font partie du produit, et non d’une mise en forme ultérieure.

1. L’analogie qui rend RAG compréhensible

Pour 1. L’analogie qui rend RAG efficace, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier du 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 aux 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.

2. Les deux composantes de tout système RAG

Pour 2. Les deux parties de tout système RAG, 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.

3. Étape 1 — Indexation : Transformer les documents en éléments recherchables

Pour le 3. Étape 1 — Indexation : Transformer les documents en éléments recherchables, 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é. Enregistrez les temps d’exécution ainsi que le coût des jetons ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce du coût é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.

Étape 1 : Charger vos documents

Pour Étape 1 : Charger vos documents, 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é. 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 opérateurs peuvent auditer sans avoir à lire l’ensemble du système. Citez les passages qui servent de base à la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation.

from langchain_community.document_loaders import PyPDFLoader

loader = PyPDFLoader("company_policy.pdf")
documents = loader.load()

print(f"Loaded {len(documents)} pages")

Étape 2 : Le diviser en blocs — et cette étape est plus importante que ce que l’on pense

Pour la étape 2 : Le découpage — et cette étape est plus importante que ce que l’on pense, définissez les entrées, le responsable de l’étape ainsi que les critères d’arrêt avant de modifier du 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 les scénarios 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’une mise en forme ultérieure. 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.

from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,      # characters per chunk
    chunk_overlap=80,    # overlap between consecutive chunks
    separators=["\n\n", "\n", ". ", " "]
)

chunks = splitter.split_documents(documents)
print(f"Split into {len(chunks)} chunks")

Étape 3 : Intégrer les fragments

Pour Étape 3 : Intégrer les chunks, 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 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.

from langchain_openai import OpenAIEmbeddings

embedding_model = OpenAIEmbeddings(model="text-embedding-3-small")

# under the hood, this is what happens per chunk:
vector = embedding_model.embed_query("30-day refund window for annual plans")
print(len(vector))   # e.g. 1536 numbers representing this sentence's meaning
        "refund policy" •
                          \
                           • "money-back guarantee"
   "vacation days" •
                     \
                      • "paid time off"

Étape 4 : Stocker les vecteurs dans une base de données vectorielle

Pour Étape 4 : Stocker les vecteurs dans une base de données vectorielle, définissez les entrées, le responsable de l’étape et les critères d’achèvement 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 terminations 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.

from langchain_community.vectorstores import FAISS

vectorstore = FAISS.from_documents(chunks, embedding_model)
vectorstore.save_local("faiss_index")

4. Étape 2 — Récupération + Génération : Répondre à une vraie question

Pour 4. Étape 2 — Récupération + Génération : Répondre à une vraie question, 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 en tokens ou en requêtes à côté des résultats fonctionnels. Une visibilité précoce du coût é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 dans l’indexation. Pour 4. Étape 2 — Récupération + Génération : Répondre à une vraie question, 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. Retr

Les portes humaines et le traitement des lettres mortes font partie du produit, et non d’une amélioration ultérieure.

Étape 1 : Intégrer la requête — avec le même modèle

Lorsque vous travaillez sur l’Étape 1 : Intégrer la requête — avec le même modèle, notez d’abord les exigences : 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. Préférez des unités petites et testables plutôt que des scripts complexes. 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 source fréquente de gaspillage.

query = "Can I get a refund on my annual subscription?"

Étape 2 : Récupérer les fragments les plus pertinents

Lorsque vous travaillez sur Étape 2 : Récupérer les fragments les plus pertinents, 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. 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. Évaluez 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.

retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
relevant_chunks = retriever.invoke(query)

for chunk in relevant_chunks:
    print(chunk.page_content[:100], "...")

Étape 3 : Insérer les fragments dans un prompt

Lorsque vous travaillez sur Étape 3 : Insérer les fragments dans un prompt, notez d’abord le contrat : 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. Enregistrez les temps d’exécution ainsi que le coût en tokens ou 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. Mémorisez les instructions stables du système et les schémas des outils. Envoyer à nouveau un préambule identique est une cause fréquente de gaspillage. Lorsque vous travaillez sur Étape 3 : Insérer les fragments dans un prompt, notez d’abord le contrat : 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. Documentez ensemble le parcours normal et les procédures 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 ultérieures.

from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_template("""
Answer the question using ONLY the context below.
If the answer isn't in the context, say "I don't have that information."

Context:
{context}

Question: {question}
""")

Étape 4 : Assembler tout le système

L’Étape 4 : Assembler tout le système fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Recueillez un transcript exemplaire, 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’erreur 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.

from langchain_openai import ChatOpenAI
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser

llm = ChatOpenAI(model="gpt-4o-mini")

def format_docs(docs):
    return "\n\n".join(doc.page_content for doc in docs)

rag_chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | prompt
    | llm
    | StrOutputParser()
)

answer = rag_chain.invoke("Can I get a refund on my annual subscription?")
print(answer)

5. Pourquoi c’est mieux que de coller tout le document directement dans la demande

5. Pourquoi cette méthode est supérieure à la simple copie du document complet dans le prompt fonctionne 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 é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 les complétions partielles silencieuses. Fixez un budget de tokens par tour et par session. Les outils agents élargissent lourdement le contexte ; des plafonds stricts empêchent que les démonstrations ne se transforment en factures inattendues.

6. Pièges courants (appris à mes dépens)

6. Pièges courants (appris à la dure) fonctionne le mieux lorsqu’il est considéré comme une surface mesurable. Recueillez 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. 6. Pièges courants (appris à la dure) fonctionne le mieux lorsqu’il est considéré comme une surface mesurable. Recueillez 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.

7. Que faire ensuite

Pour 7. Où aller ensuite, 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 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.

Liste de contrôle opérationnelle

Lorsque vous travaillez sur la liste de contrôle opérationnelle, écrivez 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 garantit que les modifications ultérieures du code restent transparentes.

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.

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 inefficace.

Fixez les versions des dépendances et enregistrez le digest de l’image utilisée pour la démonstration. La reproductibilité vaut mieux que les connaissances internes au groupe.

Dokumentez à la fois le parcours normal et celui 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.

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 inefficace.

Au préalable de promouvoir l’ensemble technologique, 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 débit, des contrôles d’attribution et un responsable clair pour la rotation des secrets. Préférez une fiabilité solide à des démonstrations brillantes mais ponctuelles.

Note pour le lot 372916c7a432 : 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 d’évaluation afin que les remplacements ultérieurs de modèles restent comparables.