Contextual Retrieval With Llama Index
This notebook covers contextual retrieval with llama_index DocumentContextExtractor
Based on an Anthropic blost post, the concept is to:
- Use an LLM to generate a ‘context’ for each chunk based on the entire document
- embed the chunk + context together
- reap the benefits of higher RAG accuracy
While you can also do this manually, the DocumentContextExtractor offers a lot of convenience and error handling, plus you can integrate it into your llama_index pipelines! Let’s get started.
NOTE: This notebook costs about $0.02 everytime you run it.
Install Packages
Section titled “Install Packages”%pip install llama-index%pip install llama-index-readers-file%pip install llama-index-embeddings-huggingface%pip install llama-index-llms-openai
Setup an LLM
Section titled “Setup an LLM”You can use the MockLLM or you can use a real LLM of your choice here. flash 2 and gpt-4o-mini work well.
from llama_index.llms.openai import OpenAIfrom llama_index.core import Settings
OPENAI_API_KEY = "sk-..."llm = OpenAI(model="gpt-4o-mini", api_key=OPENAI_API_KEY)Settings.llm = llm
Setup a data pipeline
Section titled “Setup a data pipeline”we’ll need an embedding model, an index store, a vectore store, and a way to split tokens.
Build Pipeline & Index
Section titled “Build Pipeline & Index”from llama_index.core import VectorStoreIndex, StorageContextfrom llama_index.core.node_parser import TokenTextSplitterfrom llama_index.core.storage.docstore.simple_docstore import ( SimpleDocumentStore,)from llama_index.embeddings.huggingface import HuggingFaceEmbedding
# Initialize document store and embedding modeldocstore = SimpleDocumentStore()embed_model = HuggingFaceEmbedding(model_name="baai/bge-small-en-v1.5")
# Create storage contextsstorage_context = StorageContext.from_defaults(docstore=docstore)storage_context_no_extra_context = StorageContext.from_defaults()text_splitter = TokenTextSplitter( separator=" ", chunk_size=256, chunk_overlap=10)
/Users/loganmarkewich/Library/Caches/pypoetry/virtualenvs/llama-index-caVs7DDe-py3.10/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html from .autonotebook import tqdm as notebook_tqdm
DocumentContextExtractor
Section titled “DocumentContextExtractor”# This is the new part!
from llama_index.core.extractors import DocumentContextExtractor
context_extractor = DocumentContextExtractor( # these 2 are mandatory docstore=docstore, max_context_length=128000, # below are optional llm=llm, # default to Settings.llm oversized_document_strategy="warn", max_output_tokens=100, key="context", prompt=DocumentContextExtractor.SUCCINCT_CONTEXT_PROMPT,)
Load Data
Section titled “Load Data”!wget "https://raw.githubusercontent.com/run-llama/llama_index/main/docs/docs/examples/data/paul_graham/paul_graham_essay_ambiguated.txt" -O "paul_graham_essay_ambiguated.txt"
from llama_index.core import SimpleDirectoryReader
reader = SimpleDirectoryReader( input_files=["./paul_graham_essay_ambiguated.txt"])documents = reader.load_data()
Run the pipeline, then search
Section titled “Run the pipeline, then search”import nest_asyncio
nest_asyncio.apply()
# need to add documents directly for the DocumentContextExtractor to workstorage_context.docstore.add_documents(documents)index = VectorStoreIndex.from_documents( documents=documents, storage_context=storage_context, embed_model=embed_model, transformations=[text_splitter, context_extractor],)
index_nocontext = VectorStoreIndex.from_documents( documents=documents, storage_context=storage_context_no_extra_context, embed_model=embed_model, transformations=[text_splitter],)
100%|██████████| 15/15 [00:07<00:00, 2.10it/s]
test_question = "Which chunks of text discuss the IBM 704?"retriever = index.as_retriever(similarity_top_k=2)nodes_fromcontext = retriever.retrieve(test_question)
retriever_nocontext = index_nocontext.as_retriever(similarity_top_k=2)nodes_nocontext = retriever_nocontext.retrieve(test_question)
# Print each node's contentprint("==========")print("NO CONTEXT")for i, node in enumerate(nodes_nocontext, 1): print(f"\nChunk {i}:") print(f"Score: {node.score}") # Similarity score print(f"Content: {node.node.text}") # The actual text content
# Print each node's contentprint("==========")print("WITH CONTEXT")for i, node in enumerate(nodes_fromcontext, 1): print(f"\nChunk {i}:") print(f"Score: {node.score}") # Similarity score print(f"Content: {node.node.text}") # The actual text content
==========NO CONTEXT
Chunk 1:Score: 0.5710870309825231Content: it. The result would ordinarily be to print something on the spectacularly loud device. I was puzzled by the machine. I couldn't figure out what to do with it. And in retrospect there's not much I could have done with it. The only form of input to programs was data stored on cards, and I didn't have any information stored on them. The only other option was to do things that didn't rely on any input, like calculate approximations of pi, but I didn't know enough math to do anything interesting of that type. So I'm not surprised I can't remember any code I wrote, because it can't have done much. My clearest memory is of the moment I learned it was possible for programs not to terminate, when one of mine didn't. On a machine without time-sharing, this was a social as well as a technical error, as the manager's expression made clear. With microcomputers, everything changed. Now you could have one sitting right in front of you, on a desk, that could respond to your keystrokes as it was running instead of just churning through a stack of punched inputs
Chunk 2:Score: 0.567587387219806Content: McCarthy's 1960 paper.But if so there's no reason to suppose that this is the limit of the language that might be known to them. Presumably aliens need numbers and errors and I/O too. So it seems likely there exists at least one path out of McCarthy's Lisp along which discoveredness is preserved.Thanks to Trevor Blackwell, John Collison, Patrick Collison, Daniel Gackle, Ralph Hazell, Jessica Livingston, Robert Morris, and Harj Taggar for reading drafts of this.==========WITH CONTEXT
Chunk 1:Score: 0.6776241992281743Content: it. The result would ordinarily be to print something on the spectacularly loud device. I was puzzled by the machine. I couldn't figure out what to do with it. And in retrospect there's not much I could have done with it. The only form of input to programs was data stored on cards, and I didn't have any information stored on them. The only other option was to do things that didn't rely on any input, like calculate approximations of pi, but I didn't know enough math to do anything interesting of that type. So I'm not surprised I can't remember any code I wrote, because it can't have done much. My clearest memory is of the moment I learned it was possible for programs not to terminate, when one of mine didn't. On a machine without time-sharing, this was a social as well as a technical error, as the manager's expression made clear. With microcomputers, everything changed. Now you could have one sitting right in front of you, on a desk, that could respond to your keystrokes as it was running instead of just churning through a stack of punched inputs
Chunk 2:Score: 0.6200645958839048Content: Before college the two main things I worked on, outside of school, were writing and programming. I didn't write essays. I wrote what beginning writers were supposed to write then, and probably still are: short stories. They were awful. They had hardly any plot, just characters with strong feelings, which I imagined made them deep. The first programs I tried writing were on the IBM 1401 that our school district used for what was then called "data processing." This was in 9th grade, so I was 13 or 14. The district's machine happened to be in the basement of our junior high school, and my friend Rich Draves and I got permission to use it. The space was like a mini Bond villain's lair down there, with all these alien-looking machines — CPU, disk drives, printer, card reader — sitting up on a raised floor under bright fluorescent lights. The language we used was an early version of Fortran. You had to type programs on punch cards, then stack them in the reader and press a button to load the code into memory and run it. The result would ordinarily be to print something