Contextual Retrieval in 80 Lines: Anthropic's Chunk-Prefix Trick, Measured on Your Docs A developer published an ~80-line Python script that reproduces Anthropic's Contextual Retrieval technique, prepending an LLM-generated context note to each chunk before embedding and BM25 indexing, and measuring recall@5 before and after on user-labeled questions. The script uses the Anthropic and OpenAI SDKs, rank-bm25 and NumPy, and relies on prompt caching so each document is paid for once. It cites Anthropic's September 2024 write-up, which reported a 49% reduction in top-20 retrieval failure (5.7% to 2.9%) with contextual embeddings plus contextual BM25, and 67% with a reranker added. Here is a chunk from a quarterly filing: "The company's revenue grew by 3% over the previous quarter." Which company? Which quarter? Grew from what? Inside the filing, the sentence made sense. Cut out, embedded and indexed on its own, it carries none of that, and a query about ACME's Q2 2023 revenue has almost nothing to match against. That example comes from Anthropic's Contextual Retrieval write-up https://www.anthropic.com/news/contextual-retrieval , published September 19, 2024. The fix they proposed is small. Before you embed a chunk or index it for BM25, ask a model to write a short context for it using the whole document, and glue that context to the front of the chunk. Their chunk becomes: "This chunk is from an SEC filing on ACME corp's performance in Q2 2023; the previous quarter's revenue was $314 million. The company's revenue grew by 3% over the previous quarter." On their test sets, contextual embeddings plus contextual BM25 cut the top-20 retrieval failure rate by 49% https://www.anthropic.com/news/contextual-retrieval , from 5.7% to 2.9%. Adding a reranker on top took the reduction to 67%, down to 1.9%. Those are their numbers on their data. This post gets you yours: about 80 lines of Python that index your docs both ways, contextualize every chunk with prompt caching so each document is paid for once, and print recall@5 before and after on 30 questions you label yourself. The metric in the write-up is 1 minus recall@20: the share of relevant chunks that did not show up in the top 20 results. The test data covered codebases, fiction, ArXiv papers and science papers. The headline figures used Gemini Text 004 embeddings and Claude 3 Haiku to write the contexts, which usually came out at 50 to 100 tokens same source https://www.anthropic.com/news/contextual-retrieval . The companion cookbook notebook https://github.com/anthropics/claude-cookbooks/blob/main/capabilities/contextual-embeddings/guide.ipynb reports at k=5, closer to what this post measures. Its printed run shows Pass@5 going from 80.92% to 88.12% with contextual embeddings alone. The same notebook's summary table and prose don't fully agree with its printed output, which is one more reason to treat any published number as a hint about direction. None of this tells you whether your docs have the problem. Chunks that lean on their surroundings "the endpoint above", "this setting", a table whose header lives two chunks back have the most to gain. A folder of self-contained FAQ entries may barely move. You find out by measuring. Anthropic's 2024 write-up also suggests https://www.anthropic.com/news/contextual-retrieval that if your whole knowledge base is under 200,000 tokens, you put all of it in the prompt and skip retrieval. The whole trick: the chunk keeps a short note about where it came from. Install the four dependencies and set ANTHROPIC API KEY and OPENAI API KEY : pip install anthropic openai rank-bm25 numpy The script uses the Anthropic Python SDK https://github.com/anthropics/anthropic-sdk-python for the contexts, the OpenAI Python SDK https://github.com/openai/openai-python for embeddings, rank-bm25 https://pypi.org/project/rank-bm25/ for keyword search and NumPy https://numpy.org for the vector math. Save it as ctx eval.py : python import json, re, sys import numpy as np from anthropic import Anthropic from openai import OpenAI from rank bm25 import BM25Okapi claude, oai = Anthropic , OpenAI usage = {"written": 0, "read": 0, "uncached": 0} PROMPT = "Here is the chunk we want to situate within the whole " "document\n