Votre
LLM
vient
de citer
avec
assurance
un
article
de
recherche
qui
n’existe
pas.
Vous
l’avez\interrogé
sur
la
documentation
API
de
votre
entreprise,
et
il
a
décrit
des
points
d’accès
qui
ont
été
supprimés
en
2019.
Cela
se
produit
car
les
modèles
de
langage
génèrent
du
texte
basé
sur
les
modèles
des
données
d’entraînement,
et
non
en
interrogeant
vos
informations
réelles.
La
Génération
Augmentée
par
Récupération
(RAG)
règle
ce
problème.
Pas
en
rendant
les
modèles
plus
intelligents,
mais
en
leur
donnant
accès
à
des
données
réelles
avant
qu’ils
ne
génèrent
une
réponse.
Cette
technique
est
devenue
essentielle
pour
les
systèmes
de
production,
mais
la
plupart
des
implémentations
échouent
silencieusement
—
soit
en
retournant
des
documents
irrelevant,
soit
en\améliorant
la
récupération
au
point
que
le
modèle
est\confus
par
un
contexte
trop
important.
Ce
guide
explique
comment
le
RAG
fonctionne
réellement,
pourquoi
les
configurations
de
base
échouent,
et
les
modèles
spécifiques
qui
fonctionnent
en
production.
Ce
que
le
RAG
fait
réellement
Le
RAG
comprend
trois
étapes :
- Récupérer\xa0:
Recherchez
dans
votre
base
de
données
de
documents
du
contenu
pertinent
pour
la
question
de
l’utilisateur - Augmenter\xa0:
Injectez
les
documents
récupérés
dans
le
prompt
avec
la
question
originale - Générer\xa0:
Le
LLM
répond
en
utilisant
à
la
fois
la
question
et
le
contexte
récupéré
L’idée
critique\xa0: le
modèle
ne
hallucine
jamais
à
propos
de
vos
données
car
il
lit
vos
données
réelles
pendant
la
génération
de
la
réponse.
Il
a
l’information
sous
les\//yeux.
Mais
c’est
icy
que
les
implémentations
divergent.
Un
système
RAG
naïf
récupère
tous
les
documents
vaguement
pertinents,
inonde
la
fenêtre
de
contexte,
et
le
modèle
se
perd
dans
le
bruit.
Un
système
RAG
bien
calibré
récupère
exactement
ce
qui
compte,
le
classe
par
pertinence,
et
le
modèle
reçoit
des\//instructions
claires.
Pourquoi
le
RAG
de
base
échoue\xa0:
Les
trois
ruptures
communes
Avant
de
réparer
quoi
que
ce
soit,
comprenez
où
ça
casse.
Rupture\xa01\xa0: La
récupération
renvoie
du
bruit.
Vous
interrogez
votre
base
de
données
de
documents
en
utilisant
une
recherche
par
mots-clés
de
base
ou
des
embeddings
simples,
et
vous
obtenez
10
résultats
où
seuls
2
sont
pertinents.
Le
LLM
voit
le
bruit
et
est
soit
confus,
soit
ignore
complètement
les
bonnes
informations.
C’est
le
mode
d’échec
le
plus
courant.
Rupture\xa02\xa0: Les
documents
récupérés
sont
trop
longs.
Même
si
la
récupération
fonctionne,
vous
récupérez
des
documents
complets
(800+
tokens
chacun).
Votre
fenêtre
de
contexte
de
4K
est
épuisée.
Claude
ou
GPT-4o
gaspille
des
tokens
à\//analyser
des
sections
irrelevant
et
n’a
plus
de
place
pour
la
nuance
dans
la
réponse.
Rupture\xa03\xa0: Le
décalage
entre
la
récupération
et
la
génération.
Votre
système
de
récupération
trouve
des
documents
optimisés
pour
un
type
de
requête
(recherche
factuelle),
mais
l’étape
de
génération
a
besoin
de
documents
structurés
différemment
(pour
le
raisonnement,
pour
des
exemples,
pour
des
cas
limites).
Vous
résolvez
deux
problèmes
différents
avec
un
seul
récupérateur.
Construire
un
Pipeline
RAG
de
Production\xa0:
Étape
par
Étape
Un
système
RAG
fonctionnel
nécessite
quatre
composants\xa0:
une
base
de
données
de
documents,
un
récupérateur,
un
classeur,
et
une
structure
de
prompt.
Construisons-en
un.
Étape\xa01\xa0: Ingestion
et
Découpage
des
Documents
Les
documents
bruts
sont
trop
grands
pour
être
récupérés
efficacement.
Vous
devez
les
diviser
en\morceaux
—
typiquement
300-500
tokens
chacun,
avec
10-20%
de
surchevauchement
entre
les
morceaux.
#
Exemple\xa0:
découpage
d'un
document
pour
RAG
documents
=
load_documents("company_docs/")
chunks
=
[]
for
doc
in
documents:
text
=
doc.content
#
Approche
naïve\xa0:
couper
tous
les
300
tokens
for
i
in
range(0,
len(text),
300):
chunk
=
text[i:i+400]
chunks.append({
"id":
f"{doc.name}_chunk_{len(chunks)}",
"content":
chunk,
"source":
doc.name,
"metadata":
doc.metadata
})
#
Meilleure
approche\xa0:
couper
aux
limites
sémantiques
de
langchain.text_splitter
import
RecursiveCharacterTextSplitter
splitter
=
RecursiveCharacterTextSplitter(
chunk_size=400,
chunk_overlap=50,
separators=[\"
\",
\"
\",
\"
\",
\"
\"]
)
chunks
=
[]
for
doc
in
documents:
splits
=
splitter.split_text(doc.content)
for
split
in
splits:
chunks.append({
"id":
f"{doc.name}_chunk_{len(chunks)}",
"content":
split,
"source":
doc.name
})
La
clé
icy\xa0:\ ne
coupez
pas
au
hasard.
Utilisez
RecursiveCharacterTextSplitter
pour
couper
d’abord
aux
limites
de
paragraphe,
puis
de
sentence,
puis
de
mot.
Cela
garde
intactes
les
unités
sémantiques.
Étape\xa02\xa0: Intégration
et
Stockage
Convertissez
chaque
morceau
en
un
vecteur
d’embedding.
C’est
ce
qui
rend
la
recherche
sémantique
possible\xa0—
les
significations
similaires
produisent
des
vecteurs
similaires,
indépendamment
des
correspondances
de
mots
exactes.
#
Embedding
des
morceaux
et
leur
stockage
de
openai
import
OpenAI
de
pinecone
import
Pinecone
client
=
OpenAI()
pc
=
Pinecone(api_key="your-key")
index
=
pc.Index("rag-index")
#
Intégrer
chaque
morceau
embedded_chunks
=
[]
for
chunk
in
chunks:
response
=
client.embeddings.create(
input=chunk["content"],
model="text-embedding-3-small"
)
embedding
=
response.data[0].embedding
embedded_chunks.append({
"id":
chunk["id"],
"values":
embedding,
"metadata":
{
"text":
chunk["content"],
"source":
chunk["source"]
}
})
#
Upsert
dans
la
base
de
données
vectorielle
index.upsert(vectors=embedded_chunks)
Utilisez
text-embedding-3-small
(ou
similar,
comme
Mistral
Embed)\xa0: il
est
moins
cher
que
les
grands
modèles,
assez
rapide
pour
la
récupération
en
temps
réel,
et
assez
puissant
pour
la
recherche
sémantique.
Pour
la
plupart
des
cas
d’utilisation
en
production,
vous
n’avez
pas
besoin
d’embeddings
grands.
Étape\xa03\xa0: Récupération
avec
Classement
C’est
icy
que
la
plupart
des
systèmes
se
trompent.
La
récupération
trouve
des
candidats\xa0;
le
classement
les
ordonne
par
pertinence.
Vous
avez
besoin
des
deux.
#
Pipeline
de
récupération
+
classement
def
retrieve_and_rank(query,
top_k=10,
final_k=3):
#
Étape\xa01\xa0:
Recherche
vectorielle
(axée
sur
le
rappel)
query_embedding
=
client.embeddings.create(
input=query,
model="text-embedding-3-small"
).data[0].embedding
candidates
=
index.query(
vector=query_embedding,
top_k=top_k,
include_metadata=True
)
#
Étape\xa02\xa0:
Re-classement
(axé
sur
la
précision)
#
Utilisez
un
petit
modèle
de
classement
ou
un
LLM
pour
re-noter
documents
=
[
{
"id":
match["id"],
"text":
match["metadata"]["text"],
"source":
match["metadata"]["source"]
}
for
match
in
candidates["matches"]
]
#
Noter
chaque
candidat
par
rapport
à
la
requête
scores
=
[]
for
doc
in
documents:
#
Utilisez
Claude
pour
vérifier
la
pertinence
response
=
client.client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=10,
system="Notez
la
pertinence
de
ce
document
par
rapport
à
la
requête
(1-10,
chiffre
uniquement).",
messages=[
{
"role":
"user",
"content":
f"Requête\xa0:
{query}
Document\xa0:
{doc['text']}"
}
]
)
score
=
int(response.content[0].text)
scores.append((doc,
score))
#
Retourner
les
documents
les
mieux
classés
ranked
=
sorted(scores,
key=lambda
x:
x[1],
reverse=True)
return
[doc
for
doc,
score
in
ranked[:final_k]]
Cette
approche
en
deux
étapes
fonctionne
car
la
recherche
vectorielle
est
rapide
mais
imparfaite.
Une
deuxième
étape
de
classement
—
même
simple
—
améliore
dramatiquement
ce
qui
arrive
réellement
au
LLM.
En
production
chez
AlgoVesta,
ajouter
un
re-classement
a
réduit
notre
récupération
irrelevant
de
~60%.
Étape\xa04\xa0: Construction
du
Prompt
Maintenant
que
vous
avez
des
documents
pertinents,
comment
les
injecter
dans
le
prompt\xa0?
Le
contexte
compte\énormément.
#
Mauvais
prompt\xa0:
trop
vague
sur
le
contexte
bad_prompt
=
f"""
Répondez
à
cette
question\xa0:
{user_question}
Voici
un
peu
de
contexte\xa0:
{retrieved_documents}
"""
#
Meilleur
prompt\xa0:
instructions
explicites
pour
l'utilisation
du
contexte
better_prompt
=
f"""
Vous
avez
accès
aux
documents
suivants\xa0:
---
{retrieved_documents}
---
Répondez
à
la
question
de
l'utilisateur
en
utilisant
UNIQUEMENT
les
informations
des
documents
ci-dessus.
Si
les
documents
ne
contiennent
pas
suffisamment
d'informations
pour
répondre,
indiquez-le
explicitement.
Citez
toujours
le
document
source
lorsque
vous
utilisez
des
informations
provenant
de
celui-ci.
Question\xa0:
{user_question}
"""
L’amélioration\xa0:\ instruction
explicite
sur
ce
qu’il
faut
faire
avec
le
contexte.
Dites
au
modèle
d’utiliser
uniquement
les
documents
fournis,
de
dire
lorsque
l’information
est
manquante,
et
de
citer
les
sources.
Sans
ces
instructions,
les
modèles
reviendront
parfois
aux
données
d’entraînement
lorsque
les
documents
sont
ambigus.
Modèles
RAG
communs
et
quand
ils
fonctionnent
Trois
modèles
dominent
les
systèmes
RAG
de
production.
Chacun
résout
des
problèmes
différents.
Modèle\xa01\xa0: Génération
Augmentée
par
Récupération
(RAG
Standard)
Comment
ça
marche\xa0:
Récupérez
les
documents,
ajoutez-les
au
prompt,
générez
la
réponse
en
un
seul
passage.
Quand
ça
marche\xa0:
Recherche
factuelle,
Questions/Réponses
sur
la
documentation,
questions/réponses
simples.
Le
temps
de
réponse
est
important.
Vous
avez
besoin
d’une
réponse
cohérente
unique.
Latence\xa0:
~500ms-1s
(embedding
+
récupération
+
appel
LLM)
Exemple
cas
d’utilisation\xa0:
Extrait\xa0: RAG ancre les LLM avec vos données réelles, éliminant les hallucinations. Ce guide explique comment le RAG fonctionne en production, pourquoi les configurations de base échouent et les modèles spécifiques qui fonctionnent — avec des exemples de code et des compromis. Points clés\xa0: [« Le RAG comporte trois étapes\xa0: récupérer les documents pertinents, les injecter dans le prompt, générer la réponse. Le modèle ne peut pas halluciner sur vos données s’il les lit. », « La plupart des échecs du RAG proviennent d’une mauvaise récupération (renvoi de bruit), de documents trop longs (perte de contexte) ou d’un décalage récupération-génération. Une récupération en deux étapes avec re-classement résout ce problème. », « La recherche vectorielle seule ne suffit pas. La récupération hybride (vectorielle + mots-clés) capte la dérive sémantique et les différences de vocabulaire. Utilisez Pinecone ou Weaviate, pas la complexité auto-hébergée. », « Découpez les documents aux limites sémantiques (paragraphes d’abord), pas au hasard. Des morceaux de 300 à 500 tokens avec 10% de chevauchement conviennent à la plupart des cas. Ajustez en fonction de vos modèles de requête. », « Choisissez votre LLM pour la taille de la fenêtre de contexte RAG et le suivi des instructions. Claude Sonnet 4 est le défaut de production\xa0: rapide, bon marché, gère 200K de contexte. GPT-4o pour un raisonnement plus difficile. », « Évaluez le RAG avec la précision de récupération, l’exactitude de la réponse et les métriques de latence. Commencez avec le RAG standard\xa0; ajoutez l’expansion de requête ou les approches basées sur des graphes uniquement lorsque le RAG simple atteint ses limites. »]
«