💻 Développement

dev-rag-pipeline-designer

Conception de pipelines RAG (Retrieval-Augmented Generation) — architecture, chunking, embeddings, vector stores, retrieval hybride, re-ranking, évaluation RAGAS.

⚡ Installation & lancement en 1 commande

Copiez-collez dans votre terminal : le skill s'installe dans ~/.claude/skills et Claude Code se lance directement dessus.

macOS / Linux
curl -fsSL https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.sh | sh -s -- dev-rag-pipeline-designer --launch
Windows (PowerShell)
iex "& { $(iwr -useb https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.ps1) } dev-rag-pipeline-designer -Launch"

🚀 Déjà installé ?

claude "/dev-rag-pipeline-designer"

Ou tapez /dev-rag-pipeline-designer dans une session Claude Code, ou décrivez simplement votre besoin — le skill se déclenche automatiquement via le skill-router.

🔑 Déclencheurs automatiques

Le skill s'active automatiquement quand votre demande contient :

RAGretrieval augmentedvector databaseembeddingsknowledge basePineconeChromaDBWeaviatechercher dans mes documents

📦 Installation manuelle

git clone https://github.com/khalilbenaz/claude-skills-collection.git cp -r claude-skills-collection/skills/dev-rag-pipeline-designer ~/.claude/skills/

Payload du plugin : skills/dev-rag-pipeline-designer · source éditable : dev-skills/rag-pipeline-designer

📖 Manuel

RAG Pipeline Designer

Workflow

1. Analyse des données sources

Avant toute ligne de code, inventorier :

CritèreImpact
Volume (< 10K / < 1M / > 1M docs)FAISS local → Qdrant → architecture distribuée
Fréquence de mise à jourBatch indexing statique vs incremental avec détection de changements
LanguesModèle d'embedding multilingue obligatoire si > 1 langue
StructureHomogène (1 splitter) vs hétérogène (splitters par type)
ConfidentialitéCloud embeddings ou modèle local (Ollama / llama.cpp)

Identifier les relations inter-documents (références croisées, hiérarchies) : elles orientent vers un RAG Graph ou une stratégie parent-child chunking.


2. Pipeline d'ingestion

Chaîne : chargement → parsing → nettoyage → enrichissement métadonnées → stockage brut

from langchain_community.document_loaders import PyMuPDFLoader, DirectoryLoader
from datetime import datetime

loader = DirectoryLoader("./docs", glob="**/*.pdf", loader_cls=PyMuPDFLoader)
documents = loader.load()

for doc in documents:
    doc.metadata.update({
        "indexed_at": datetime.now().isoformat(),
        "source_type": "pdf",
        # ajouter : title, department, version, expiry_date si dispo
    })

Loaders recommandés par type :


3. Chunking strategy

Règle de base : chunk size ≈ taille de la réponse attendue. Toujours tester ≥ 3 tailles.

StratégieQuand l'utiliserchunk_size conseillé
RecursiveCharacterTextSplitterDéfaut universel600–1000 tokens, overlap 10–15%
MarkdownHeaderTextSplitterMarkdown structuréPar section H2/H3
SemanticChunker (LangChain)Texte dense, sujets variésVariable
Parent-child chunkingContexte large + précision fineParent 2000 / enfant 300
Sentence-levelFAQ, contenu court1–3 phrases
from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=800,
    chunk_overlap=120,           # ~15% overlap
    separators=["\n\n", "\n", ". ", "! ", "? ", " "],
    length_function=len,
    add_start_index=True,        # métadonnée de position dans le doc
)
chunks = splitter.split_documents(documents)
print(f"{len(documents)} docs → {len(chunks)} chunks")

# Inspecter manuellement 10 chunks aléatoires avant d'indexer
import random
for c in random.sample(chunks, 10):
    print("---", c.metadata.get("source"), c.page_content[:200])

4. Embedding model selection

Une fois choisi, changer = ré-indexer tout le corpus. Bien choisir dès le départ.

ModèleDimsCas d'usageCoût
text-embedding-3-small (OpenAI)1536Bon compromis qualité/prix$0.02/1M tokens
text-embedding-3-large (OpenAI)3072Meilleure qualité en$0.13/1M tokens
intfloat/multilingual-e5-large1024Multilingue localGratuit (GPU)
BAAI/bge-m31024Multilingue, dense+sparseGratuit (GPU)
Cohere embed-multilingual-v3.01024Multilingue SaaS$0.10/1M tokens
# OpenAI
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")

# Local GPU/CPU
from langchain_community.embeddings import HuggingFaceEmbeddings
embeddings = HuggingFaceEmbeddings(
    model_name="BAAI/bge-m3",
    model_kwargs={"device": "cuda"},   # "cpu" si pas de GPU
    encode_kwargs={"normalize_embeddings": True},
)

5. Vector store setup

StoreHébergementVolume max conseilléPoint fort
FAISSLocal / fichier< 5M vecteursZéro infra, rapide
ChromaDBLocal / Docker< 10MDX simple, filtrage OK
QdrantDocker / Cloud100M+Filtrage avancé, payload
pgvectorPostgreSQL10M+Si Postgres déjà en place
PineconeSaaSIllimitéScalabilité, serverless
WeaviateDocker / Cloud100M+GraphQL, modules intégrés
# ChromaDB (dev / staging)
from langchain_chroma import Chroma
vectorstore = Chroma.from_documents(
    chunks, embeddings, persist_directory="./chroma_db"
)

# Qdrant (production self-hosted)
from langchain_qdrant import QdrantVectorStore
from qdrant_client import QdrantClient
client = QdrantClient(url="http://localhost:6333")
vectorstore = QdrantVectorStore.from_documents(
    chunks, embeddings, client=client, collection_name="my_kb"
)

# pgvector (stack PostgreSQL existante)
from langchain_postgres import PGVector
vectorstore = PGVector(
    embeddings=embeddings,
    connection="postgresql+psycopg://user:pass@localhost/mydb",
    collection_name="documents",
)

Indexer par batch de 500–1000 chunks pour éviter les timeouts.


6. Retrieval optimization

La recherche vectorielle seule est insuffisante. Pipeline recommandé :

Query → [BM25 top-20] + [Dense top-20] → RRF fusion → Cross-encoder re-rank top-5 → LLM
from langchain.retrievers import EnsembleRetriever, BM25Retriever
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import CrossEncoderReranker

# Hybrid search : BM25 + dense, pondéré
bm25 = BM25Retriever.from_documents(chunks, k=15)
dense = vectorstore.as_retriever(search_kwargs={"k": 15})
ensemble = EnsembleRetriever(
    retrievers=[bm25, dense],
    weights=[0.35, 0.65],   # ajuster selon corpus
)

# Re-ranking cross-encoder
reranker = CrossEncoderReranker(
    model_name="cross-encoder/ms-marco-MiniLM-L-6-v2",
    top_n=5,
)
retriever = ContextualCompressionRetriever(
    base_compressor=reranker,
    base_retriever=ensemble,
)

# Filtrage par métadonnées avant la recherche vectorielle (Qdrant/Chroma)
retriever_filtered = vectorstore.as_retriever(
    search_kwargs={"k": 10, "filter": {"department": "finance", "year": 2025}}
)

Techniques avancées 2026 :


7. Generation avec contexte

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import ChatOpenAI

RAG_PROMPT = """\
Tu es un assistant expert. Réponds à la question en te basant UNIQUEMENT
sur les documents fournis ci-dessous. Si l'information n'est pas présente,
réponds : "Je n'ai pas trouvé cette information dans la base de connaissances."
Ne complète jamais avec tes connaissances générales.

Documents :
{context}

Question : {question}

Réponse (cite les sources entre [brackets]) :"""

def format_docs(docs):
    return "\n\n".join(
        f"[{d.metadata.get('source','?')} p.{d.metadata.get('page','?')}]\n{d.page_content}"
        for d in docs
    )

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
prompt = ChatPromptTemplate.from_template(RAG_PROMPT)

chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | prompt
    | llm
)
response = chain.invoke("Quelle est la politique de remboursement ?")

Limiter à 4–6 chunks dans le contexte : au-delà, la qualité baisse (lost-in-the-middle).


8. Évaluation (RAGAS)

Construire un dataset de 50–100 paires question / réponse attendue manuellement validé avant d'optimiser quoi que ce soit.

from ragas import evaluate
from ragas.metrics import (
    faithfulness,           # hallucinations : réponse ⊂ documents ?
    answer_relevancy,       # la réponse répond-elle à la question ?
    context_precision,      # documents récupérés pertinents ?
    context_recall,         # tous les docs nécessaires récupérés ?
)
from datasets import Dataset

test_data = Dataset.from_dict({
    "question":     questions,
    "answer":       generated_answers,
    "contexts":     retrieved_contexts,   # liste de listes de strings
    "ground_truth": ground_truths,
})
results = evaluate(test_data, metrics=[faithfulness, answer_relevancy,
                                        context_precision, context_recall])
print(results.to_pandas())
# Seuils minimaux acceptables : faithfulness > 0.85, context_precision > 0.75

Garde-fous et anti-patterns

Anti-patternSymptômeCorrection
Chunks trop grands (> 1500 tokens)Retrieval dilué, mauvaises réponsesRéduire à 600–900 + overlap
Chunks trop petits (< 100 tokens)Manque de contexte dans la réponseMonter à 400–600 + parent-child
Pas de re-rankingTop-k peu pertinents malgré bon recallAjouter cross-encoder
Métadonnées non stockéesImpossible de filtrer ou citer les sourcesEnrichir dès l'ingestion
Dense retrieval seulMauvais sur termes exacts (codes, noms propres)Hybrid BM25 + dense
Contexte > 8 chunks"Lost in the middle", réponse dégradéeLimiter à 5–6 chunks
Aucun dataset d'évalOptimisation aveugleRAGAS dataset en priorité
Modèle d'embedding changé sans ré-indexationScores incohérents, retrieval casséToujours ré-indexer intégralement
Pas de stratégie d'updateIndex périmé silencieusementDétection de changements + ré-indexation incrémentale

Bonnes pratiques 2026