RAG en production : pipeline complet du chunking à la réponse
Plongez dans les étapes concrètes d'un pipeline Retrieval-Augmented Generation : découpage, embedding, stockage vectoriel, re-ranking et génération finale avec un LLM.
Le Retrieval-Augmented Generation (RAG) est aujourd’hui l’architecture de référence pour construire des applications IA qui répondent sur la base d’une base de connaissance privée. Plutôt que de fine-tuner un LLM (coûteux, lent, vite obsolète), on lui donne accès dynamiquement aux documents pertinents au moment de la requête.
Vue d’ensemble du pipeline
Un pipeline RAG complet comprend deux phases distinctes : l’indexation (préparation des données, effectuée une fois) et la requête (retrieval + génération, effectuée à chaque appel).
── INDEXATION (offline) ──────────────────────────────────
Documents bruts
│
▼
┌──────────┐ ┌───────────┐ ┌─────────────┐
│ Chunking │───▶│ Embedding │───▶│ Vector DB │
└──────────┘ └───────────┘ └─────────────┘
Découpe en Conversion Pinecone,
segments en vecteurs pgvector, Qdrant
── REQUÊTE (online, ~400ms) ──────────────────────────────
Question utilisateur
│
▼
┌───────────┐ ┌────────────┐ ┌──────────┐ ┌──────────┐
│ Embedding │───▶│ Retrieval │───▶│ Re-rank │───▶│ LLM │
└───────────┘ └────────────┘ └──────────┘ └──────────┘
La question Top-K chunks Reorder par Génère la
→ vecteur similaires pertinence réponse
Étape 1 : Chunking — l’art de découper
La qualité du RAG dépend énormément de la stratégie de découpe. Un chunk trop grand dilue l’information, un chunk trop petit perd le contexte.
Stratégies courantes :
- Fixed-size : découpe à N tokens avec chevauchement (overlap). Simple, efficace pour texte homogène.
- Semantic : découpe aux frontières de paragraphes ou sections. Préserve mieux le sens.
- Hierarchical : combine les deux — chunks fins pour la précision, chunks larges pour le contexte.
// src/lib/chunker.ts (exemple)
function chunkText(text: string, maxTokens = 512, overlap = 50) {
const sentences = text.split(/(?<=[.!?])\s+/);
const chunks: string[] = [];
let current = '';
for (const sentence of sentences) {
if (tokenCount(current + sentence) > maxTokens) {
chunks.push(current.trim());
// Garder les dernières phrases pour le contexte
current = getLastNTokens(current, overlap) + sentence;
} else {
current += ' ' + sentence;
}
}
if (current) chunks.push(current.trim());
return chunks;
}
Étape 2 : Embedding — transformer du texte en géométrie
L’embedding convertit chaque chunk en un vecteur dense (1536 dimensions avec text-embedding-3-small d’OpenAI). Des chunks sémantiquement proches auront des vecteurs proches dans cet espace.
// src/lib/embeddings.ts
import OpenAI from 'openai';
const openai = new OpenAI({ apiKey: OPENAI_API_KEY });
export async function embedText(text: string): Promise<number[]> {
const response = await openai.embeddings.create({
model: 'text-embedding-3-small',
input: text,
});
return response.data[0].embedding;
}
Alternatives à OpenAI : Cohere embed-multilingual-v3.0 (excellent pour le français), nomic-embed-text (open-source, auto-hébergeable).
Étape 3 : Vector DB — stocker et chercher par similarité
La base vectorielle indexe les embeddings et permet de retrouver les K chunks les plus similaires à une requête en quelques millisecondes, via la similarité cosinus.
// src/lib/pinecone.ts
import { Pinecone } from '@pinecone-database/pinecone';
const pc = new Pinecone({ apiKey: PINECONE_API_KEY });
const index = pc.index(PINECONE_INDEX_NAME);
export async function retrieve(queryEmbedding: number[], topK = 5) {
const results = await index.query({
vector: queryEmbedding,
topK,
includeMetadata: true,
});
return results.matches.map(m => ({
text: m.metadata?.text as string,
score: m.score ?? 0,
source: m.metadata?.source as string,
}));
}
Comparatif bases vectorielles :
| Solution | Hosting | Idéal pour |
|---|---|---|
| Pinecone | SaaS managé | Production, scalabilité |
| pgvector | PostgreSQL | Stack existante Postgres |
| Qdrant | Self-hosted / cloud | Open-source, contrôle |
| Chroma | Local | Développement, prototypage |
Étape 4 : Re-ranking — affiner la pertinence
Le retrieval vectoriel optimise la similarité sémantique, pas la pertinence exacte. Un re-ranker cross-encoder lit la paire (question, chunk) ensemble et produit un score de pertinence plus précis.
// Avec Cohere Rerank
const reranked = await cohere.rerank({
model: 'rerank-multilingual-v3.0',
query: userQuestion,
documents: retrievedChunks.map(c => c.text),
topN: 3,
});
Le re-ranking améliore typiquement la précision de 10 à 20% pour un coût marginal.
Étape 5 : Génération — le LLM avec contexte injecté
La dernière étape assemble la question et les chunks récupérés dans un prompt structuré, puis appelle le LLM.
// src/lib/anthropic.ts
import Anthropic from '@anthropic-ai/sdk';
export async function generateAnswer(
question: string,
context: string[]
) {
const client = new Anthropic({ apiKey: ANTHROPIC_API_KEY });
const prompt = `Tu es un assistant expert. Réponds uniquement en te basant sur le contexte fourni.
CONTEXTE :
${context.map((c, i) => `[${i + 1}] ${c}`).join('\n\n')}
QUESTION : ${question}
RÉPONSE :`;
const response = await client.messages.create({
model: 'claude-sonnet-4-6',
max_tokens: 1024,
messages: [{ role: 'user', content: prompt }],
});
return response.content[0].type === 'text'
? response.content[0].text
: '';
}
Métriques de production à surveiller
Un pipeline RAG en production nécessite un monitoring continu :
- Retrieval recall — les chunks pertinents sont-ils bien récupérés ?
- Answer faithfulness — la réponse est-elle fidèle aux chunks (pas d’hallucination) ?
- Latence p95 — viser < 2s pour une bonne UX
- Coût par requête — embedding + LLM, typiquement $0.001–$0.01 selon le modèle
Ce boilerplate expose l’endpoint /api/search qui simule ce pipeline complet. Connectez vos propres services dans src/lib/ pour passer en mode production.