Chunking, embeddings, vector DB (Qdrant, Pinecone, Weaviate), hybrid search BM25 + dense.
Un LLM brut a deux faiblesses : knowledge cutoff (ne connaît pas vos données ni les événements récents) et hallucinations. Le RAG y répond en récupérant les passages pertinents d'une base documentaire et en les injectant dans le prompt.
Lewis P. et al., « Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks », NeurIPS 2020, arXiv:2005.11401 — papier fondateur RAG par Facebook AI.
1. INGESTION (offline)
Documents → Parse (PDF/HTML/MD) → Chunk → Embed → Vector DB
2. QUERY (online)
User question → Embed → Vector search (top-k)
↓
Pass(es) → LLM with context → Answer
| Format | Outil recommandé |
|---|---|
| PDF natif | PyMuPDF (fitz), Unstructured.io |
| PDF scanné | OCR (Tesseract, AWS Textract, Azure Document Intelligence, Mistral OCR) |
| HTML | BeautifulSoup, Readability, Trafilatura |
| DOCX | python-docx |
| Code | tree-sitter, langchain-text-splitters Language |
| Tableaux | Camelot, Tabula, Unstructured |
| Stratégie | Principe | Quand l'utiliser |
|---|---|---|
| Fixed-size | N tokens avec chevauchement | Texte brut homogène |
| Recursive character | Split par séparateurs hiérarchiques | Mixte (Markdown, légal, manuels) |
| Sentence-aware | Découpe en phrases (spaCy, NLTK) | Texte conversationnel |
| Semantic chunking | Coupe sur la dissimilarité embedding voisine | Documents thématiquement variés |
| Document layout-aware | Préserve titres, sections (Unstructured) | Documents structurés (juridique, scientifique) |
| Parent-child | Index sur petits chunks, retourne le parent | RAG avancé (voir leçon 3) |
from langchain_text_splitters import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=512,
chunk_overlap=64,
separators=["\n\n", "\n", ". ", " ", ""]
)
chunks = splitter.split_text(long_text)
| Modèle | Dim | API/Open | Particularité |
|---|---|---|---|
| OpenAI text-embedding-3-large | 3072 (réductible) | API | Excellent défaut, multilingue |
| Cohere embed-v3 | 1024 | API | Multilingue 100 langues, compression INT8 |
| Voyage AI voyage-3 | 1024 | API | Très bon RAG, multilingue |
| BGE-large-en/multilingual | 1024 | Open | Top open-source (BAAI, MTEB leader) |
| E5-Mistral 7B | 4096 | Open | État de l'art mais coûteux |
| nomic-embed-text-v1.5 | 768 (Matryoshka) | Open | Compact, multilingue |
| all-MiniLM-L6-v2 | 384 | Open | Léger CPU, baseline |
Référence comparative : MTEB Leaderboard (Massive Text Embedding Benchmark)
| Vector DB | Type | Hybrid search | Particularité |
|---|---|---|---|
| Qdrant | Open-source + cloud | Oui | Rust, payload filters puissants |
| Pinecone | SaaS | Oui | Premier acteur, managed pur |
| Weaviate | Open + cloud | Oui (BM25 natif) | GraphQL, modules ML intégrés |
| Milvus / Zilliz | Open + cloud | Oui | Scale énorme (milliards) |
| Chroma | Open + cloud | Limité | Embeddable, dev local |
| pgvector | Postgres extension | Via FTS | Simple, SQL, ACID |
| Elasticsearch / OpenSearch | Search engine | Excellent (BM25 + dense) | Stack existante |
| LanceDB | Embeddable | Oui | Format columnar, scale efficient |
from qdrant_client import QdrantClient, models
from openai import OpenAI
client = QdrantClient(url="http://localhost:6333")
oai = OpenAI()
# Création collection
client.recreate_collection(
collection_name="docs",
vectors_config=models.VectorParams(size=3072, distance=models.Distance.COSINE)
)
# Ingestion
points = []
for i, chunk in enumerate(chunks):
emb = oai.embeddings.create(model="text-embedding-3-large", input=chunk).data[0].embedding
points.append(models.PointStruct(id=i, vector=emb, payload={"text": chunk, "source": doc_id}))
client.upsert(collection_name="docs", points=points)
# Query
q = "Comment fonctionne Spark ?"
q_emb = oai.embeddings.create(model="text-embedding-3-large", input=q).data[0].embedding
results = client.search(collection_name="docs", query_vector=q_emb, limit=5)
for r in results:
print(r.score, r.payload["text"][:100])
Le dense (embeddings) capture la sémantique mais peut louper les termes rares (noms propres, identifiants techniques). BM25 (Okapi 1995) capture les termes exacts. La combinaison RRF (Reciprocal Rank Fusion) est nettement supérieure.
def reciprocal_rank_fusion(rankings, k=60):
scores = {}
for ranking in rankings:
for rank, doc_id in enumerate(ranking):
scores[doc_id] = scores.get(doc_id, 0) + 1.0 / (k + rank)
return sorted(scores.items(), key=lambda x: -x[1])
Cormack G., Clarke C., Buettcher S., « Reciprocal Rank Fusion outperforms Condorcet and individual Rank Learning Methods », SIGIR 2009.
Voir leçon 5 pour les frameworks Ragas et LangSmith.
Inscrivez-vous pour accéder aux 5 autres leçons + le quiz final.
Créer mon compteChoisis quels cookies tu acceptes — modifiable à tout moment.