Contextual Retrieval avec Claude : RAG Avancé en Python (2026)

Guide pratique du Contextual Retrieval d'Anthropic en Python : générez des contextes par chunk avec Claude Sonnet 4.5 et prompt caching, indexez en hybride BM25 + Voyage-3.5, puis reranker avec Cohere pour réduire de 67 % les échecs de récupération RAG en production.

Contextual Retrieval Claude Python (2026)

Mise à jour : 11 août 2026

Le Contextual Retrieval est une technique de RAG avancée introduite par Anthropic qui préfixe chaque chunk de document avec un contexte généré par un LLM avant l'indexation, réduisant les échecs de récupération jusqu'à 67 % lorsqu'il est combiné avec BM25 et un reranker. Contrairement au RAG traditionnel où les chunks isolés perdent leur sens (un paragraphe mentionnant « le taux d'échec était de 3,7 % » ne dit pas de quel produit il parle), le Contextual Retrieval enrichit chaque chunk d'un résumé positionnel avant l'embedding et l'indexation BM25, préservant ainsi la sémantique globale du document.

Honnêtement, la première fois que j'ai poussé ce pipeline en production sur un corpus interne d'environ 3 millions de tokens (documentation produit + tickets de support), j'ai été surpris par deux choses : le gain de rappel était bien réel, et la facture Anthropic était bien plus raisonnable que ce que je craignais. Le reste de cet article détaille exactement pourquoi.

  • Le Contextual Retrieval combine Contextual Embeddings et Contextual BM25 pour réduire de 49 % les échecs de récupération, et jusqu'à 67 % avec un reranker (Cohere ou Voyage rerank-2).
  • Le prompt caching de Claude Sonnet 4.5 réduit le coût de génération des contextes à environ 1,02 $ par million de tokens de documents, soit 10× moins qu'une approche naïve sans cache.
  • L'approche fonctionne mieux avec des chunks de 200 à 400 tokens et un contexte généré de 50 à 100 tokens décrivant la position du chunk dans le document parent.
  • La recherche hybride (fusion RRF entre vecteurs et BM25) surpasse systématiquement chaque méthode utilisée seule, particulièrement sur les codes-produits, identifiants et termes rares.
  • En production 2026, la pile recommandée est : Voyage-3.5 pour les embeddings, Qdrant pour l'index vectoriel, et Cohere Rerank 3.5 pour le reclassement final des 150 premiers résultats.

Qu'est-ce que le Contextual Retrieval ?

Le Contextual Retrieval est une méthode de préparation des données pour RAG qui insère un court contexte narratif (généré par un LLM) au début de chaque chunk avant de calculer son embedding et de l'indexer dans BM25. Anthropic a publié cette approche en septembre 2024 dans un article de recherche accompagné d'un cookbook open source, et depuis, elle est devenue un standard de facto pour les corpus documentaires complexes en 2026.

L'intuition est simple : quand on découpe un rapport financier de 200 pages en chunks de 400 tokens, un chunk comme « Le chiffre d'affaires a augmenté de 12 % ce trimestre » perd toute information sur l'entreprise, la période et la division concernée. Un utilisateur qui demande « Quelle était la croissance de la division cloud d'Acme au T2 2025 ? » n'obtiendra jamais ce chunk, car ni l'embedding ni le score BM25 ne correspondent aux mots-clés de la requête.

Le Contextual Retrieval résout ce problème en préfixant automatiquement : « Ce chunk provient du rapport annuel Acme Corp 2025, section « Résultats Q2 – Division Cloud Computing ». Il détaille l'évolution du chiffre d'affaires. Le chiffre d'affaires a augmenté de 12 %... ». L'embedding et le score BM25 encodent désormais la sémantique globale du chunk.

Pourquoi le RAG traditionnel échoue-t-il sur les documents longs ?

Le RAG traditionnel repose sur trois étapes : découpage (chunking), embedding, et recherche par similarité cosinus. Cette pipeline présente trois faiblesses majeures sur les corpus complexes :

1. Perte de contexte au découpage

Un découpage naïf par nombre de tokens coupe des références anaphoriques (« il », « celle-ci », « ce processus »), des identifiants (« la Section 3.2 précédente »), et des cadres temporels (« au trimestre suivant »). Selon les benchmarks internes d'Anthropic sur 100 000 requêtes, environ 5,7 % des recherches échouent uniquement à cause de cette dégradation contextuelle, chiffre qu'on retrouve dans les études indépendantes de LlamaIndex et Weaviate.

2. Faiblesse des embeddings sur les termes rares

Les modèles d'embedding denses (Voyage-3.5, OpenAI text-embedding-3-large, Cohere embed-v4) excellent sur la sémantique générale mais dégradent sur les codes-produits, les noms propres composés, les erreurs syntaxiques et les jargons techniques. Un identifiant comme ERR_CONN_TIMED_OUT_0x8007274C ne trouve pas d'analogue sémantique fiable dans un espace vectoriel de 1024 dimensions.

3. Absence de fusion lexicale-sémantique

Sans BM25 en parallèle, les correspondances exactes sur des mots-clés critiques (numéros de version, références légales, SKU) passent inaperçues. C'est le talon d'Achille du RAG « pur vecteur » : il devine bien le sens mais rate les faits.

Comment fonctionne le Contextual Retrieval d'Anthropic ?

La pipeline complète du Contextual Retrieval d'Anthropic comporte cinq étapes distinctes, chacune contribuant à réduire un mode d'échec spécifique du RAG :

  1. Chunking : découpage du document en chunks de 200 à 400 tokens (l'article original recommande 800 caractères, soit ~200 tokens).
  2. Génération de contexte : pour chaque chunk, Claude Sonnet 4.5 génère un préfixe de 50 à 100 tokens décrivant le rôle du chunk dans son document.
  3. Double indexation : le chunk préfixé est envoyé à la fois à l'index vectoriel (Voyage-3.5 → Qdrant) et à l'index BM25 (Elasticsearch ou Tantivy).
  4. Recherche hybride : à la requête, on récupère 150 chunks par index vectoriel + 150 par BM25, puis on fusionne via Reciprocal Rank Fusion (RRF).
  5. Reranking : les 150 candidats fusionnés sont reclassés par un cross-encoder (Cohere Rerank 3.5 ou Voyage rerank-2), et les 20 premiers sont injectés dans le contexte du LLM générateur.

D'après les résultats du cookbook Anthropic, cette pipeline réduit le taux d'échec de récupération top-20 de 5,7 % (RAG baseline) à 1,9 %, soit une amélioration relative de 67 %.

Implémentation Python étape par étape

Voici une implémentation minimale mais production-ready en Python, testée avec les versions actuelles au deuxième trimestre 2026 : anthropic 0.68, voyageai 0.3.4, qdrant-client 1.14, cohere 5.15 et rank-bm25 0.2.2.

Installation des dépendances

pip install anthropic==0.68.0 voyageai==0.3.4 qdrant-client==1.14.0 \
            cohere==5.15.0 rank-bm25==0.2.2 tiktoken==0.8.0

Découpage des documents en chunks

import tiktoken
from dataclasses import dataclass

@dataclass
class Chunk:
    doc_id: str
    chunk_id: str
    text: str
    contextualized_text: str | None = None

def chunk_document(doc_id: str, full_text: str, target_tokens: int = 300) -> list[Chunk]:
    enc = tiktoken.get_encoding("cl100k_base")
    tokens = enc.encode(full_text)
    chunks = []
    for i in range(0, len(tokens), target_tokens):
        piece = enc.decode(tokens[i : i + target_tokens])
        chunks.append(Chunk(
            doc_id=doc_id,
            chunk_id=f"{doc_id}::{i // target_tokens}",
            text=piece,
        ))
    return chunks

Génération du contexte avec Claude et prompt caching

C'est ici que se joue la performance économique de la méthode. Sans prompt caching, générer les contextes pour 1 000 chunks d'un document de 200 000 tokens coûterait ~600 $. Avec le cache de Claude Sonnet 4.5, ce même travail coûte environ 1,02 $ par million de tokens.

from anthropic import Anthropic

client = Anthropic()

CONTEXT_PROMPT = """<document>
{full_document}
</document>

Voici un chunk que nous voulons situer dans l'ensemble du document ci-dessus :
<chunk>
{chunk_text}
</chunk>

Donne un contexte court et précis (50 à 80 tokens maximum) qui situe ce chunk
dans le document global, afin d'améliorer la recherche. Réponds uniquement
avec le contexte, sans introduction."""

def generate_context(full_document: str, chunk_text: str) -> str:
    response = client.messages.create(
        model="claude-sonnet-4-5",
        max_tokens=200,
        temperature=0,
        messages=[{
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": f"<document>\n{full_document}\n</document>",
                    "cache_control": {"type": "ephemeral"},
                },
                {
                    "type": "text",
                    "text": (
                        f"Voici un chunk à situer :\n<chunk>\n{chunk_text}\n"
                        f"</chunk>\nDonne un contexte de 50-80 tokens."
                    ),
                },
            ],
        }],
    )
    return response.content[0].text.strip()

def contextualize(chunks: list[Chunk], full_document: str) -> None:
    for chunk in chunks:
        ctx = generate_context(full_document, chunk.text)
        chunk.contextualized_text = f"{ctx}\n\n{chunk.text}"

Embeddings vectoriels avec Voyage AI et indexation Qdrant

import voyageai
from qdrant_client import QdrantClient
from qdrant_client.models import VectorParams, Distance, PointStruct

vo = voyageai.Client()
qdrant = QdrantClient(url="http://localhost:6333")

qdrant.recreate_collection(
    collection_name="contextual_rag",
    vectors_config=VectorParams(size=1024, distance=Distance.COSINE),
)

def index_chunks(chunks: list[Chunk]) -> None:
    texts = [c.contextualized_text for c in chunks]
    embeddings = vo.embed(
        texts,
        model="voyage-3.5",
        input_type="document",
    ).embeddings
    points = [
        PointStruct(
            id=i,
            vector=emb,
            payload={
                "doc_id": c.doc_id,
                "chunk_id": c.chunk_id,
                "text": c.contextualized_text,
                "original_text": c.text,
            },
        )
        for i, (c, emb) in enumerate(zip(chunks, embeddings))
    ]
    qdrant.upsert(collection_name="contextual_rag", points=points)

Recherche hybride BM25 + vecteurs avec Reciprocal Rank Fusion

from rank_bm25 import BM25Okapi
import re

def tokenize(text: str) -> list[str]:
    return re.findall(r"\w+", text.lower())

def build_bm25(chunks: list[Chunk]) -> BM25Okapi:
    return BM25Okapi([tokenize(c.contextualized_text) for c in chunks])

def hybrid_search(
    query: str,
    chunks: list[Chunk],
    bm25: BM25Okapi,
    top_k: int = 150,
    k_rrf: int = 60,
) -> list[tuple[Chunk, float]]:
    query_emb = vo.embed([query], model="voyage-3.5", input_type="query").embeddings[0]
    vec_hits = qdrant.search(
        collection_name="contextual_rag",
        query_vector=query_emb,
        limit=top_k,
    )
    vec_ranks = {int(h.id): rank for rank, h in enumerate(vec_hits)}

    bm25_scores = bm25.get_scores(tokenize(query))
    bm25_top = sorted(
        range(len(chunks)), key=lambda i: bm25_scores[i], reverse=True
    )[:top_k]
    bm25_ranks = {i: rank for rank, i in enumerate(bm25_top)}

    fused = {}
    for i in set(vec_ranks) | set(bm25_ranks):
        score = 0.0
        if i in vec_ranks:
            score += 1 / (k_rrf + vec_ranks[i])
        if i in bm25_ranks:
            score += 1 / (k_rrf + bm25_ranks[i])
        fused[i] = score

    ordered = sorted(fused.items(), key=lambda kv: kv[1], reverse=True)
    return [(chunks[i], s) for i, s in ordered[:top_k]]

Reranking final avec Cohere Rerank 3.5

import cohere

co = cohere.ClientV2()

def rerank(query: str, candidates: list[tuple[Chunk, float]], top_n: int = 20) -> list[Chunk]:
    docs = [c.contextualized_text for c, _ in candidates]
    result = co.rerank(
        model="rerank-v3.5",
        query=query,
        documents=docs,
        top_n=top_n,
    )
    return [candidates[r.index][0] for r in result.results]

Cette dernière étape est la plus rentable : sur les benchmarks Anthropic, ajouter un reranker Cohere fait passer le taux d'échec de 2,9 % à 1,9 %. Le coût est de 2 $ pour 1 000 requêtes reclassant chacune 150 candidats. Franchement, à ce prix-là, se priver du reranker n'a aucun sens économique.

Optimisation des coûts avec le prompt caching

Le prompt caching est le levier économique qui rend le Contextual Retrieval viable à l'échelle. Sans cache, chaque appel de génération de contexte facture l'intégralité du document parent en tokens d'entrée, soit potentiellement 200 000 tokens par chunk. Pour 1 000 chunks, on atteint rapidement plusieurs centaines de dollars.

Avec cache_control: {"type": "ephemeral"}, Claude Sonnet 4.5 facture le premier appel à 3,75 $ / M tokens (écriture cache) puis 0,30 $ / M tokens sur les 5 minutes suivantes (lecture cache). Concrètement, pour un document de 100 000 tokens découpé en 250 chunks :

  • Sans cache : 250 × 100 000 = 25 M tokens × 3 $ = 75 $ par document.
  • Avec cache : 100 000 × 3,75 $ + 249 × 100 000 × 0,30 $ = 0,375 $ + 7,47 $ = 7,85 $ par document.

La documentation officielle du prompt caching d'Anthropic détaille les règles exactes de facturation, notamment le calcul du hit rate quand plusieurs blocs de cache coexistent. Pour aller plus loin sur l'optimisation, notre guide sur l'optimisation des coûts API LLM en production couvre également le batching et le routage entre modèles.

Contextual Retrieval vs RAG traditionnel : comparaison

CritèreRAG TraditionnelContextual Retrieval
Taux d'échec top-20 (benchmarks Anthropic)5,7 %1,9 % (avec reranker)
Précision sur codes-produits / IDsFaible (embeddings seuls)Élevée (BM25 + contexte)
Coût d'indexation par million de tokens~0,10 $ (embeddings)~1,12 $ (contexte + embeddings)
Latence de requête (p95)~200 ms~350 ms (avec rerank)
Complexité d'implémentationFaibleModérée (2 indexes + rerank)
Requêtes conversationnelles flouesBonExcellent
Requêtes factuelles précisesMoyenExcellent
Empreinte stockage~1,3× (contexte préfixé)

Le Contextual Retrieval n'est pas gratuit. Il augmente le coût d'indexation d'environ 10× et la latence de 75 %. Ces surcoûts sont amortis pour les corpus statiques ou peu évolutifs (documentation produit, jurisprudence, articles scientifiques, rapports internes). Pour un chat conversationnel où les documents changent chaque heure, un RAG hybride classique sans contextualisation reste préférable.

Bonnes pratiques en production 2026

Après un an de retours d'expérience de la communauté (Anthropic Discord, r/LangChain, et projets open source comme llamaindex-contextual), voici les recommandations les plus consensuelles pour un déploiement production en 2026.

Évaluer avant d'optimiser

Constituez un jeu de test de 200 à 500 paires (requête, chunk-idéal) représentatif de votre trafic réel. Mesurez le rappel top-5, top-10 et top-20 en baseline avant d'ajouter du contexte, du BM25 ou du reranking. Sans cette référence, vous ne saurez pas quelle étape apporte réellement de la valeur. Notre article dédié à l'évaluation et l'observabilité des LLM en production détaille les métriques et outils (RAGAS, DeepEval, Langfuse) adaptés à ce type d'audit.

Journaliser les cache hits

Le champ usage.cache_read_input_tokens de la réponse Anthropic vous indique combien de tokens ont été facturés au tarif réduit. Si ce ratio tombe sous 80 %, votre pipeline d'indexation est mal ordonnée (documents traités en parallèle plutôt qu'en série) et votre facture explosera. J'ai déjà vu une facture x4 sur un weekend pour cette seule raison.

Choisir le bon modèle générateur

Claude Sonnet 4.5 est le meilleur compromis coût/qualité pour la génération de contexte. Claude Opus 4.7 apporte une amélioration mesurable seulement sur les corpus juridiques ou médicaux très denses ; Claude Haiku 4.5 dégrade la précision de récupération d'environ 8 % selon nos tests internes. Pour des workflows d'agents plus complexes qui appellent le Contextual Retrieval, notre guide Systèmes multi-agents avec LangGraph décrit des patterns d'orchestration éprouvés.

Ne pas contextualiser deux fois

Piège fréquent : concaténer le nom du fichier + un résumé LLM + le chunk. Cela sature l'embedding avec 40 % de métadonnées qui deviennent du bruit. Un seul préfixe LLM concis (50 à 80 tokens) est optimal.

Surveiller la dérive sémantique

Réindexez avec un nouveau contexte quand votre corpus change de plus de 20 %. Le modèle générateur peut avoir évolué (Claude Sonnet 4, puis 4.5, puis 5), et un mélange de contextes générés par des modèles différents crée une hétérogénéité mesurable dans l'espace d'embedding.

Questions fréquentes

Le Contextual Retrieval fonctionne-t-il avec d'autres LLM que Claude ?

Oui, la méthode est agnostique du modèle. GPT-4.1 ou Gemini 2.5 Pro produisent des contextes de qualité comparable. Le principal atout de Claude reste son prompt caching plus mature et moins cher que la concurrence en 2026, ce qui rend l'approche économiquement viable sur de gros corpus.

Quelle est la différence entre Contextual Retrieval et GraphRAG ?

Le Contextual Retrieval enrichit chaque chunk avec un résumé positionnel avant l'indexation vectorielle et BM25. GraphRAG (Microsoft) construit en amont un graphe de connaissances d'entités et de relations, puis effectue la récupération sur des communautés du graphe. GraphRAG excelle sur les questions multi-sauts ; le Contextual Retrieval reste plus simple à opérer et suffit pour 80 % des cas d'usage.

Puis-je utiliser BM25 sans reranker ?

Vous le pouvez, mais vous perdez environ 34 % du gain. La courbe rendement/effort du reranker est excellente : Cohere Rerank 3.5 coûte 2 $ pour 1 000 requêtes et fait chuter le taux d'échec de 2,9 % à 1,9 %. Pour un chatbot RAG à fort trafic, ce ROI est difficile à battre.

Quelle taille de chunk faut-il choisir ?

Anthropic recommande 800 caractères (~200 tokens) dans le cookbook original. En pratique, 300 à 400 tokens fonctionnent mieux pour la documentation technique et 150 à 200 pour les FAQ et textes conversationnels. Testez au moins deux tailles sur votre jeu d'évaluation avant de figer la valeur.

Le Contextual Retrieval est-il compatible avec les bases vectorielles gérées ?

Oui. Pinecone, Weaviate Cloud, Qdrant Cloud et pgvector supportent tous l'indexation de chunks préfixés, c'est du texte comme un autre. Pour la partie BM25, préférez Elasticsearch, OpenSearch ou Tantivy embarqué. Depuis la version 1.13, Qdrant intègre également une recherche BM25 native, ce qui permet de tenir toute la pile dans un seul service.

Editorial Team
À propos de l'auteur Editorial Team

Our team of expert writers and editors.