Lecture : 16 minutes · Niveau : développeur intermédiaire · Mise à jour : avril 2026
Le RAG (Retrieval-Augmented Generation) est devenu en 2025-2026 le pattern dominant pour faire répondre un LLM à partir des données spécifiques d’une entreprise au lieu de sa mémoire pré-entraînée — éliminant l’écrasante majorité des hallucinations sur faits métiers. Ce tutoriel pratique couvre toute la chaîne : choix du vector database (pgvector, ChromaDB, Qdrant, Pinecone), modèles d’embedding 2026 (text-embedding-3-large, Cohere embed-v4, bge-m3), découpage de documents, ingestion, recherche, reranking, et intégration avec Claude ou OpenAI. Tous les exemples sont en Python exécutables.
Pour le contexte stratégique global, voir le pillar IA générative avancée PME.
Sommaire
- Comprendre RAG en 5 minutes
- Choisir le vector database
- Modèles d’embedding 2026
- Découpage de documents (chunking)
- Ingestion : pipeline complet pgvector + OpenAI
- Recherche : top-K, hybrid, reranking
- Intégration LLM (Claude / OpenAI)
- Patterns avancés
- Évaluation qualité RAG
- Optimisations production
- Pièges fréquents
- FAQ
1. Comprendre RAG en 5 minutes
Problème. Les LLMs hallucinent sur faits précis (chiffres, dates, processus internes). Et leur knowledge cutoff date d’avant l’entraînement → pas d’info récente.
Solution RAG. À chaque question, on récupère depuis une base les documents pertinents et on les fournit en contexte au LLM. Il répond à partir des documents, pas de sa mémoire.
Architecture.
User Question
↓
Embedding (vector 1536 dim)
↓
Vector DB Search (top-K similar docs)
↓
Prompt: [system] + [retrieved docs] + [question]
↓
LLM → Answer (avec citations sources)
Cas d’usage type PME.
– Chatbot service client sur base FAQ + politique entreprise.
– Assistant interne sur documentation Notion / wiki.
– Recherche sémantique catalogue produits.
– Support technique multilingue.
– Analyse contrats / dossiers juridiques.
Différence vs fine-tuning.
– RAG : ajoute connaissance externe à chaque requête, mise à jour temps réel facile.
– Fine-tuning : modifie le modèle, fige connaissance, coûteux à mettre à jour.
– En 2026 : RAG est le défaut pour 90 % des cas. Fine-tuning réservé à styles spécifiques ou volumes très répétitifs.
2. Choisir le vector database
pgvector (PostgreSQL extension).
– Recommandé démarrage si PostgreSQL déjà en place.
– Performance correcte jusqu’à plusieurs millions de vecteurs.
– Index HNSW pour recherche rapide.
– Coût zéro additionnel.
– Limites : scaling massif (>10M vecteurs) demande tuning.
ChromaDB.
– Open-source Python-native, ultra-simple.
– Persistance locale ou serveur.
– Idéal prototypage, MVP, dev local.
– Limites : moins mature en production scaled.
Qdrant.
– Open-source Rust, performant, scaling fort.
– Cloud managed disponible (Qdrant Cloud).
– Bon choix moyens à gros volumes.
– API REST + gRPC.
Pinecone.
– SaaS spécialisé.
– Performance et scaling excellents.
– Pricing : 70-200+ USD/mo selon usage.
– Pas d’option self-hosted.
Weaviate.
– Open-source, multi-modal (texte, images), GraphQL natif.
– Cloud option.
Milvus / Zilliz.
– Enterprise scale, très gros volumes (10M+).
– Plus complexe à opérer.
Redis 8 vector sets.
– Si Redis déjà en place + petits volumes (<100K vecteurs).
– Latence sub-ms.
Tableau de décision.
| Volume vecteurs | Stack existant | Recommandation |
|---|---|---|
| <100K | PostgreSQL | pgvector |
| <100K | Redis | Redis vector sets |
| <1M | PostgreSQL | pgvector + tuning |
| <1M | Pas d’infra | ChromaDB local ou Qdrant Cloud |
| 1-10M | Tout | Qdrant ou Pinecone |
| 10M+ | Enterprise | Pinecone ou Milvus |
3. Modèles d’embedding 2026
Un modèle d’embedding transforme du texte en vecteur (typiquement 768-3072 dimensions). La similarité entre vecteurs reflète la similarité sémantique.
OpenAI text-embedding-3-large.
– Standard 2026, très performant.
– 3072 dimensions par défaut, réductible à 1536 ou 768 (Matryoshka).
– ~0,13 USD/M tokens.
– API simple.
OpenAI text-embedding-3-small.
– 1536 dimensions, ~0,02 USD/M tokens.
– Excellent rapport qualité-prix pour la majorité des cas.
Cohere embed-v4.
– Multilingue (100+ langues), excellent en français.
– ~0,10 USD/M tokens.
– Fort en multilingue / cross-language.
bge-m3 (BAAI, open-source).
– Multilingue, performant.
– Auto-hébergeable (gratuit après infra).
– Taille modèle : ~2 GB, RAM CPU OK ou GPU pour vitesse.
voyage-3 (Voyage AI).
– Haut de gamme, performance benchmarks excellente.
– Plus cher.
Choix recommandé PME 2026.
– Démarrage simple : OpenAI text-embedding-3-small (1536 dim, peu cher, qualité solide).
– Multilingue critique (FR + EN + Wolof / autres langues) : Cohere embed-v4.
– Souveraineté / coût zéro après infra : bge-m3 self-hosted.
Cohérence importante. Le modèle d’embedding doit être le même pour ingestion ET pour query. Si on change de modèle, il faut ré-embedder toute la base.
4. Découpage de documents (chunking)
Pourquoi chunker. Les LLMs ont des limites de contexte (200K tokens Claude Sonnet 4.6, 128K GPT-5). Et les embeddings d’un document trop long perdent en précision sémantique.
Tailles recommandées.
– 300-800 tokens par chunk : sweet spot 2026.
– 10-20 % d’overlap entre chunks : évite de couper une idée en deux.
Stratégies de chunking.
Fixed-size.
– Découpage par nombre de caractères/tokens fixe.
– Simple mais peut couper en plein milieu d’une phrase.
Recursive.
– Découpe d’abord par paragraphes, sinon phrases, sinon caractères.
– Préserve mieux la structure.
– Standard via LangChain RecursiveCharacterTextSplitter.
Semantic.
– Découpage selon similarité sémantique entre phrases.
– Plus coûteux mais meilleurs résultats sur certains contenus.
Document-aware.
– Adapté au format (Markdown, code, HTML).
– Respecte sections, listes, tables.
Exemple Python avec LangChain.
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=600,
chunk_overlap=100,
separators=["\n\n", "\n", ". ", " ", ""]
)
with open("docs/policy.md") as f:
text = f.read()
chunks = splitter.split_text(text)
print(f"Created {len(chunks)} chunks")
Bonnes pratiques.
– Préserver métadonnées (titre document, section, date, auteur).
– Inclure contexte dans chunk : nom doc en début pour aider retrieval.
– Tester différentes tailles, mesurer impact sur qualité retrieval.
5. Ingestion : pipeline complet pgvector + OpenAI
Setup pgvector.
-- PostgreSQL
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE documents (
id BIGSERIAL PRIMARY KEY,
title TEXT,
source TEXT,
chunk_index INT,
content TEXT,
embedding VECTOR(1536),
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Index HNSW pour recherche rapide
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops);
Pipeline ingestion Python.
import os
import psycopg2
from openai import OpenAI
from langchain.text_splitter import RecursiveCharacterTextSplitter
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
conn = psycopg2.connect(os.environ["DATABASE_URL"])
def embed(texts: list[str]) -> list[list[float]]:
response = client.embeddings.create(
model="text-embedding-3-small",
input=texts
)
return [item.embedding for item in response.data]
def ingest_document(file_path: str, title: str):
with open(file_path) as f:
text = f.read()
splitter = RecursiveCharacterTextSplitter(
chunk_size=600, chunk_overlap=100
)
chunks = splitter.split_text(text)
# Embedding par batch (économie API)
embeddings = embed(chunks)
# Insertion DB
cur = conn.cursor()
for i, (chunk, emb) in enumerate(zip(chunks, embeddings)):
cur.execute(
"""INSERT INTO documents (title, source, chunk_index, content, embedding)
VALUES (%s, %s, %s, %s, %s)""",
(title, file_path, i, chunk, emb)
)
conn.commit()
cur.close()
print(f"Ingested {len(chunks)} chunks from {title}")
# Usage
ingest_document("docs/politique-confidentialite.md", "Politique confidentialité")
ingest_document("docs/faq-livraison.md", "FAQ Livraison")
Coûts ingestion.
– 1 000 documents × 600 tokens moyens × text-embedding-3-small = 600K tokens = ~0,012 USD.
– Très économique pour PME.
Mise à jour incrémentale.
– Hash du contenu pour détecter changements.
– Re-embedder seulement chunks modifiés.
6. Recherche : top-K, hybrid, reranking
Recherche similarité simple.
def search(query: str, top_k: int = 5):
query_embedding = embed([query])[0]
cur = conn.cursor()
cur.execute(
"""SELECT title, content, 1 - (embedding <=> %s::vector) AS similarity
FROM documents
ORDER BY embedding <=> %s::vector
LIMIT %s""",
(query_embedding, query_embedding, top_k)
)
results = cur.fetchall()
cur.close()
return results
Hybrid search (vector + full-text).
– Vector seul : capture sémantique mais peut rater termes exacts.
– Full-text seul : termes exacts mais pas synonymes.
– Hybrid combine les deux pour meilleure recall.
-- PostgreSQL avec pg_trgm + pgvector
SELECT
title, content,
0.6 * (1 - (embedding <=> %s::vector))
+ 0.4 * similarity(content, %s) AS score
FROM documents
WHERE content ILIKE '%' || %s || '%' OR embedding <=> %s::vector < 0.5
ORDER BY score DESC
LIMIT 10;
Reranking.
– Top-K initial large (20-50 chunks).
– Reranker (Cohere Rerank, voyage-rerank) → reordonne par pertinence fine.
– Garde top 5-10 pour LLM.
– Améliore qualité de 10-30 % en pratique.
Cohere Rerank exemple.
import cohere
co = cohere.Client(os.environ["COHERE_API_KEY"])
def rerank(query, candidates, top_n=5):
response = co.rerank(
query=query,
documents=[c["content"] for c in candidates],
model="rerank-multilingual-v3.0",
top_n=top_n
)
return [candidates[r.index] for r in response.results]
Pipeline RAG complet.
1. Embed query.
2. Vector search top-K=20.
3. Optionally hybrid avec full-text.
4. Rerank pour top-N=5.
5. Construire prompt avec docs + question.
6. LLM génère réponse.
7. Citer sources (chunk IDs) pour traçabilité.
7. Intégration LLM (Claude / OpenAI)
Avec Claude (Anthropic).
import anthropic
client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
def rag_answer(question: str):
docs = search(question, top_k=5)
context = "\n\n".join([
f"[Source: {d[0]}]\n{d[1]}" for d in docs
])
message = client.messages.create(
model="claude-sonnet-4-6-20250101",
max_tokens=1000,
system="Tu es un assistant qui répond uniquement à partir des documents fournis. Cite tes sources entre crochets.",
messages=[{
"role": "user",
"content": f"Documents:\n\n{context}\n\nQuestion: {question}"
}]
)
return message.content[0].text
# Usage
answer = rag_answer("Quelle est la politique de retour produit ?")
print(answer)
Avec OpenAI.
from openai import OpenAI
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
def rag_answer_openai(question: str):
docs = search(question, top_k=5)
context = "\n\n".join([f"[Source: {d[0]}]\n{d[1]}" for d in docs])
response = client.chat.completions.create(
model="gpt-5-mini",
messages=[
{"role": "system", "content": "Réponds uniquement à partir des documents fournis. Cite sources."},
{"role": "user", "content": f"Documents:\n\n{context}\n\nQuestion: {question}"}
]
)
return response.choices[0].message.content
Prompt caching Anthropic (économie -90 %).
message = client.messages.create(
model="claude-sonnet-4-6-20250101",
max_tokens=1000,
system=[
{"type": "text", "text": "Tu es un assistant..."},
{
"type": "text",
"text": stable_context, # contexte stable
"cache_control": {"type": "ephemeral"}
}
],
messages=[{"role": "user", "content": question}]
)
Streaming pour UX.
with client.messages.stream(...) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
8. Patterns avancés
Multi-query.
– Générer 3-5 reformulations de la question.
– Recherche pour chacune.
– Union des résultats.
– Améliore recall sur questions ambiguës.
Hypothetical Document Embeddings (HyDE).
– LLM génère réponse hypothétique à la question.
– Embedde la réponse hypothétique au lieu de la question.
– Recherche similarité avec docs réels.
Parent-Child chunks.
– Embedde des petits chunks (précis pour search).
– Retourne les chunks parents (plus larges, contexte LLM).
Metadata filtering.
– Filtrer avant search par tags, dates, types.
– Réduit espace recherche, améliore pertinence.
Self-query.
– LLM extrait filtres de la question.
– Construit query DB structurée.
– Idéal pour catalogues avec attributs (prix, catégorie, etc.).
Conversation history.
– Pour chatbot : injecter messages précédents en contexte.
– Compresser historique long via summary.
9. Évaluation qualité RAG
Pourquoi évaluer. Sans dataset de test, on ne sait pas si une modification améliore ou dégrade.
Métriques.
– Retrieval recall : le bon document est-il dans top-K ?
– Retrieval precision : combien de docs retournés sont pertinents ?
– Faithfulness : la réponse est-elle fidèle aux docs ?
– Answer relevance : la réponse répond-elle à la question ?
– Context precision : les docs sont-ils pertinents pour la question ?
Outils.
– Ragas : framework éval RAG open-source Python.
– TruLens : observabilité + eval.
– DeepEval : framework eval LLM.
– LLM-as-judge : utiliser GPT-5 ou Claude pour scorer outputs.
Workflow.
1. Construire dataset de 50-100 questions + réponses idéales.
2. Lancer système RAG sur dataset.
3. Calculer métriques automatisées + LLM-as-judge.
4. Comparer versions (baseline vs amélioration).
Eval continue en production.
– Sample 1-5 % des requêtes prod pour eval.
– Alertes si métriques dégradent.
10. Optimisations production
Coûts.
– Cache embedding queries fréquentes.
– Modèle embedding adapté (small vs large).
– Prompt caching Anthropic sur system prompt + few-shot.
– Batch processing pour ingestion.
Latence.
– Index HNSW bien tuné (paramètres m, ef_construction).
– Vector DB proche du LLM API (région).
– Reranking optionnel (saute si latence critique).
– Streaming output LLM.
Qualité.
– Reranker activé.
– Hybrid search (vector + full-text).
– Métadonnées filtering.
– Prompt instructions strict (« cite sources, dis je ne sais pas si pas dans docs »).
Robustesse.
– Timeout sur LLM calls.
– Fallback model si timeout.
– Logging requêtes pour audit.
– Monitoring qualité (LangSmith, Helicone).
11. Pièges fréquents
Chunks trop grands. 2000+ tokens = embedding flou, retrieval médiocre. 300-800 tokens optimal.
Chunks trop petits. <100 tokens = perte contexte. 300+ tokens minimum.
Pas d’overlap. Coupures milieu de phrase = info perdue. 10-20 % overlap.
Modèle embedding incohérent. Ingestion avec model A, query avec model B = chaos. Toujours même modèle.
Pas de citations sources. Réponse non vérifiable, hallucinations indétectables. Toujours citer.
Top-K trop bas (1-2). Manque de contexte. 5-10 typique.
Top-K trop haut (50+). Bruit, dépasse contexte LLM, coût. 5-10 suffit après reranking.
Pas de reranking. Qualité 20 % inférieure à possible. Cohere Rerank simple à intégrer.
Pas de filtres metadata. Recherche sur tout = perte temps, baisse pertinence. Filtrer par date, type, source.
Index non optimisé. HNSW avec params par défaut = perf modeste sur gros volumes. Tuning m=16-32, ef_search=40-100.
Pas d’eval. Régression silencieuse à chaque changement. Dataset test minimum 50 questions.
FAQ
pgvector suffit-il pour la production ?
Oui jusqu’à plusieurs millions de vecteurs avec index HNSW correctement tuné. Au-delà ou si latence p95 < 50 ms requise sur volume massif, migrer vers Qdrant ou Pinecone. Pour PME : pgvector couvre 90 % des cas.
Quel modèle d’embedding choisir ?
text-embedding-3-small d’OpenAI : qualité solide, prix imbattable (0,02 USD/M tokens), dimension 1536 manageable. Pour multilingue critique : Cohere embed-v4. Pour souveraineté : bge-m3 self-hosted.
Combien coûte un système RAG en production ?
Petit usage (1 000 requêtes/mo) : 10-30 USD/mo (embeddings + LLM). Usage moyen (10 000 req/mo) : 50-150 USD/mo. Gros usage (100 000 req/mo) : 300-1500 USD/mo selon optimisations (caching, modèle, reranking).
Comment gérer mises à jour des documents ?
Hash du contenu détecte changements. Re-embedder seulement chunks modifiés. Tagger metadata avec timestamp. Pour suppressions : DELETE chunks correspondants. Job batch quotidien ou trigger event.
LangChain ou LlamaIndex ?
LangChain : framework plus large, agents + RAG + autre. LlamaIndex : focus RAG, optimisations spécifiques. En 2026 : LangChain pour stack global, LlamaIndex si RAG seul. Possible de combiner.
Faut-il du fine-tuning par-dessus le RAG ?
Rarement. RAG bien fait + prompt engineering couvre 90 % des cas. Fine-tuning utile pour adapter un small model open-source à un domaine très spécifique (juridique, médical) avec corpus annoté.
Comment éviter que le LLM hallucine sur des docs absents ?
Prompt strict : « Si l’information n’est pas dans les documents fournis, réponds ‘Je ne dispose pas de cette information’ ». LLM-as-judge en post pour valider faithfulness. Citer sources systématiquement.
Vector DB managed ou self-hosted ?
Managed (Pinecone, Qdrant Cloud) : zéro ops, payant à l’usage, idéal démarrage et scaling automatique. Self-hosted (pgvector, Qdrant on VPS) : moins cher à volume stable, contrôle total, mais ops à gérer. Pour PME démarrage : managed ou pgvector.
Articles liés (cluster IA générative avancée)
- 👉 IA générative avancée pour PME : guide complet
- 👉 Agents IA Claude OpenAI LangGraph
- 👉 Prompt et context engineering avancé
Voir aussi : Bases de données PME guide complet, PostgreSQL tutoriel production, Python pour PME guide complet, MCP servers tutoriel développeur.
Article mis à jour le 26 avril 2026. Pour signaler une erreur ou suggérer une amélioration, écrivez-nous.