Retrievers and RAG
Retrieval-Augmented Generation (RAG): Connecting the Vector Store to the LLM.
🧑🏫 Sabse pehle — simple mein samjho#
Pichhle section mein humne Vector Store banaya aur dekha ki similarity_search se kaise context milta hai. Par user ko raw paragraphs thodi dikhane hain! Humein wo paragraphs LLM ko dene hain aur bolna hai: "Ye lo documents, inko padho, aur fir user ke sawal ka ek accha sa jawab do". Is poori process ko RAG (Retrieval-Augmented Generation) kehte hain. LangChain mein hum Vector Store ko ek Retriever mein convert karte hain, aur fir LCEL (pipe syntax) use karke Retriever, Prompt, aur LLM ko aapas mein jod dete hain.
What is a Retriever?#
A Retriever is simply an interface that returns documents given a query. While a Vector Store is a database, a Retriever is the LangChain component that actually plugs into LCEL chains.
# Convert our existing Chroma vectorstore into a retriever
# search_kwargs={"k": 4} tells it to fetch the top 4 matching documents
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
# Retrievers implement the .invoke() method just like models!
retrieved_docs = retriever.invoke("How do agents use memory?")
Building the RAG Chain#
Let's put everything we've learned together: Prompts, Models, Output Parsers, and Retrievers, all connected using LCEL.
1. The Prompt Template#
We need a prompt that explicitly tells the LLM to use the provided context to answer the question.
from langchain_core.prompts import ChatPromptTemplate
template = """You are an assistant for question-answering tasks.
Use the following pieces of retrieved context to answer the question.
If you don't know the answer, just say that you don't know.
Use three sentences maximum and keep the answer concise.
Question: {question}
Context: {context}
Answer:"""
prompt = ChatPromptTemplate.from_template(template)
2. Formatting the Context#
The retriever returns a list of Document objects. We need a tiny helper function to extract the text from those objects and join them into one massive string so we can inject it into the {context} variable of the prompt.
def format_docs(docs):
# Joins the page_content of all retrieved documents with a double newline
return "\n\n".join(doc.page_content for doc in docs)
3. Assembling the LCEL Chain#
This is where LangChain's true power shines. We use RunnablePassthrough to pass the user's question down the chain, while simultaneously using the retriever to fetch the context.
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
# The RAG Chain
rag_chain = (
# Step 1: Create the inputs for the prompt
{
# Retrieve docs based on the question, then format them into a string
"context": retriever | format_docs,
# Pass the original question through untouched
"question": RunnablePassthrough()
}
# Step 2: Inject the context and question into the Prompt Template
| prompt
# Step 3: Pass the formatted prompt to the LLM
| llm
# Step 4: Extract the string answer from the AIMessage object
| StrOutputParser()
)
4. Invoking the Chain#
Now, to use this massive system, you just call .invoke() with a simple string question!
question = "What is Task Decomposition?"
# Behind the scenes:
# 1. The question is embedded and searches ChromaDB.
# 2. Top 4 chunks are extracted and merged into a string.
# 3. The string is injected into the prompt alongside the question.
# 4. The prompt goes to GPT-3.5.
# 5. GPT-3.5 returns the answer.
answer = rag_chain.invoke(question)
print(answer)
Congratulations! You have just built a complete AI system that can read private documents and answer questions about them.