A settembre 2024, Anthropic ha proposto la Contextual Retrieval, una tecnica per migliorare la qualità del retrieval nei sistemi RAG. L'idea alla base è affrontare un problema frequente: i chunk estratti da un documento spesso perdono informazioni contestuali importanti quando vengono isolati dal loro contesto originale.
Prendiamo un esempio pratico dal nostro dataset D\&D. Una tabella di armi da guerra potrebbe essere divisa in più chunk, e uno di questi potrebbe contenere:
```
| Battleaxe | 1d8 Slashing | Versatile (1d10) | Topple | 4 lb. | 10 GP |
| Flail | 1d8 Bludgeoning | — | Sap | 2 lb. | 10 GP |
| Longsword | 1d8 Slashing | Versatile (1d10) | Sap | 3 lb. | 15 GP |
| Maul | 2d6 Bludgeoning | Heavy, Two-Handed | Topple | 10 lb. | 10 GP |
```
Questo chunk contiene le informazioni sulla maul, ma manca l’header che indica che stiamo parlando di Martial Melee Weapons (Armi da Guerra). Un sistema di retrieval basato su similarità semantica potrebbe non recuperare questo chunk quando un utente chiede “Quale arma da guerra infligge il maggior numero di danni?”.
La Contextual Retrieval risolve questo problema arricchendo ogni chunk con un breve contesto che lo colloca all'interno del documento di origine. Questo contesto è generato da un LLM che, avendo accesso sia al singolo chunk sia all'intero documento, è in grado di produrre una descrizione contestuale accurata. Il contesto viene quindi anteposto al chunk, e l'unità così formata viene trasformata in embedding. Approfondiremo questo processo più avanti nel blogpost.
La contextual retrieval è particolarmente utile quando:
Anthropic riporta miglioramenti significativi nelle metriche di retrieval, specialmente quando combinata con altre tecniche come il reranking.
È passato più di un anno dalla pubblicazione di quel post, e ci siamo chiesti: la Contextual Retrieval è ancora una tecnica valida nel 2026?
Nel frattempo, l'ecosistema di modelli e tecniche si è evoluto significativamente. In particolare, i reranker hanno fatto passi da gigante: modelli come Cohere Rerank v4 Pro e i nuovi reranker open-source come quelli della famiglia Qwen 3 offrono performance notevolmente superiori rispetto a un anno fa.
Questo ci ha portato a una domanda interessante: come interagisce la Contextual Retrieval con i reranker di nuova generazione e con dei dataset sfidanti? L'arricchimento contestuale dei chunk offre ancora un vantaggio significativo? Abbiamo deciso di scoprirlo sperimentalmente.
Per i nostri esperimenti abbiamo utilizzato:
Come spiegato nel blogpost precedente, la struttura char-based del nostro dataset ci permette di calcolare con precisione quali chunk sono necessari per rispondere a ogni domanda, indipendentemente dalla strategia di chunking adottata.
La Contextual Retrieval richiede un intervento nella fase di ingestion: prima di inserire i chunk nel vector store, è necessario arricchirli con informazioni contestuali che li situano all'interno del documento originale. Solo così l'embedding può catturare sia il contenuto del chunk che il suo contesto più ampio.
Per implementare questa ingestion abbiamo sfruttato l’IngestionPipeline di Datapizza AI. Questa architettura modulare ci permette di comporre diversi step di processing come componenti indipendenti: chunking, contestualizzazione, embedding e upsert su vector store. Abbiamo esteso la pipeline con componenti custom per la generazione del contesto tramite LLM.
La pipeline del nostro esperimento dà per scontato che i documenti di input siano già stati convertiti in markdown. Per la conversione da PDF a markdown, rimandiamo al preprocessing del nostro rag-dataset-builder.
Per il nostro dataset D\&D SRD, per mantenere l’esperimento il più semplice possibile, abbiamo adottato un chunking by character con:
Lo step di contestualizzazione è il cuore della Contextual Retrieval. Per ogni chunk, forniamo a un LLM:
L’LLM genera un breve contesto che viene poi anteposto al chunk nel formato:
```
CONTEXT: [contesto generato]
CONTENT: [testo originale del chunk]
```
Per generare il contesto, il prompt di Anthropic (dal blog post originale) è:
```
<document>
{{WHOLE_DOCUMENT}}
</document>
Here is the chunk we want to situate within the whole document
<chunk>
{{CHUNK_CONTENT}}
</chunk>
Please give a short succinct context to situate this chunk within
the overall document for the purposes of improving search retrieval
of the chunk. Answer only with the succinct context and nothing else.
```
Il nostro setup differisce in alcuni aspetti:
```
<document>
{{ whole_document }}
</document>
You are an expert on the Dungeons & Dragons 5.2.1 System Reference Document.
Your task is to contextualize text chunks for improved semantic search retrieval.
For each chunk below:
{% for chunk in chunks %}
<chunk id="{{ chunk.id }}">
{{ chunk.text }}
</chunk>
{% endfor %}
```
Questi prompt domain-specific permettono all’LLM di generare contesti più accurati e rilevanti per il dominio.
Applicando la contestualizzazione all'intero dataset D\&D SRD, e sfruttando il batching insieme a Gemini Flash 2.5, il costo totale si è attestato intorno ai 0,63$.
I chunk (contestualizzati o meno) vengono poi embeddati usando Cohere embed-v4.0 (1536 dimensioni).
Una volta embeddati, i chunk vengono caricati su un vector database, nel nostro caso abbiamo scelto Qdrant. Ogni chunk viene salvato con:
Per i nostri esperimenti, abbiamo messo in piedi due pipeline parallele:


L’unica differenza è lo step di contestualizzazione, così da poter confrontare i due approcci e isolare il suo impatto sulle performance.
Per valutare la qualità del retrieval abbiamo utilizzato la metrica di recall.
La recall, in questo caso, corrisponde alla frazione dei chunk necessari per rispondere alla query effettivamente recuperati. Se una domanda necessita di 4 chunk e ne recuperiamo 3, la recall è 0.75.
Successivamente, per definire una singola metrica per un intero dataset, calcoliamo la recall media su tutte le domande del dataset fissando uno specifico valore di k, cioè il numero di chunk che il sistema recupera per ogni query. Ad esempio, con k=10 il sistema restituisce i 10 chunk più rilevanti secondo il modello di retrieval, e valutiamo quanti di questi sono effettivamente necessari per rispondere alla domanda. In questo modo otteniamo una metrica generale riassuntiva della performance del sistema.
Il primo esperimento confronta direttamente la retrieval base con la contextual retrieval, senza reranker. Abbiamo valutato con k = 5, 10, 20.
L’embedding è stato generato usando Cohere embed-v4.0 (1536 dimensioni), e il retrieval avviene tramite similarity search su Qdrant.
I grafici mostrano la recall (asse y) al variare di k = 5, 10, 20 (asse x), confrontando la base retrieval con la contextual retrieval.
Per le domande easy, abbiamo risultati su due dataset: D\&D SRD e Private Dataset 2. Il Private Dataset 1 non include un set di domande easy.
Per le domande medium, abbiamo risultati su tutti e tre i dataset: D\&D SRD, Private Dataset 1, e Private Dataset 2.
Ogni grafico mostra un subplot per dataset, permettendo un confronto diretto tra i due metodi di retrieval.


La tabella seguente riporta il guadagno percentuale in recall della contextual retrieval rispetto all'approccio base (calcolato come recall_contextual − recall_base) per diversi valori di k:

Un dato interessante emerge da questi risultati: il miglioramento maggiore si registra per k=5, mentre per k=10 il guadagno è minimo o addirittura leggermente negativo.
Una possibile spiegazione è che la contextual retrieval aumenti il numero complessivo di chunk rilevanti per ciascuna query. Con un valore di k limitato a 10, alcuni di questi chunk utili potrebbero essere esclusi dal cutoff, riducendo così il vantaggio rispetto all'approccio base.
Per verificare questa ipotesi, abbiamo calcolato il guadagno medio di recall per ogni valore di k nell'intervallo \[1, 20]:

Il grafico conferma l'osservazione iniziale: sia per il tier Easy che per Medium, il guadagno raggiunge il minimo intorno a k=10, mentre risulta massimo per valori bassi di k. Questo comportamento suggerisce che la contextual retrieval sia particolarmente efficace nel posizionare i chunk più rilevanti nelle prime posizioni del ranking.
Abbiamo deciso di aggiungere un reranker alla pipeline per due motivi:
Il flusso con reranker è:
A differenza dell’embedder che opera su rappresentazioni vettoriali, il reranker analizza direttamente il testo, permettendo un matching più fine.
Abbiamo testato due reranker:
A seguito dei nostri esperimenti, qui sotto indichiamo tutti i risultati ottenuti di recall sulla difficoltà medium, con k fissato 20. Tutti i risultati dettagliati sono disponibili

Come ulteriore approfondimento, applichiamo la stessa metodologia dello step precedente (in cui non veniva utilizzato alcun reranker) per analizzare il miglioramento della recall introdotto dalla contextual retrieval rispetto all'approccio base (calcolato come recall_contextual − recall_base), esplorando l'intero intervallo k ∈ \[1, 20].

Con l'introduzione di un reranker, il minimo di guadagno osservato intorno a k=10 scompare. Si nota invece un picco di miglioramento intorno a k=5, seguito da un calo progressivo per valori superiori. Questo andamento suggerisce che, all'aumentare di k, lo spazio di miglioramento offerto dalla contextual retrieval si riduce gradualmente
Dopo questi esperimenti, le nostre conclusioni sono:
Consigliamo la Contextual Retrieval quando:
Potrebbe non valere la pena se:
Ci sono diverse direzioni interessanti per esperimenti futuri:
Il codice completo con cui abbiamo condotto questi esperimenti è disponibile nella nostra repository GitHub.
***
*Raul Singh -* GitHub - LinkedIn - AI R\&D Engineer - Datapizza
*Ling Xuan “Emma” Chen -* GitHub - LinkedIn - AI R\&D Engineer - Datapizza
*Francesco Foresi -* GitHub - LinkedIn - GenAI R\&D Team Lead - Datapizza