← Writing
AI & Generative AI

Continue Exploring Retrievers with LangChain

Data Mastery Series — Episode 39: LangChain Website (Part 14 )

29 Dec 202421 min readLangChainDashboard
LangChain Series · Part 9 of 19

Continue Exploring Retrievers with LangChain

Data Mastery Series — Episode 39: LangChain Website (Part 14)

Connect with me and follow our journey: Linkedin, Facebook


Welcome to Episode 39 of the Data Mastery Series! Today, we continue from our last episode about retrievers, diving into more techniques for enhancing document retrieval in LangChain. Before we dive in, let’s recap our journey so far:

Note:This post is inspired by insights from the official LangChain documentation and represents my practical learning journey.

In the previous episode, we covered Vectorstore Retrievers, ParentDocument Retrievers, Multi-Vector Retrievers, Self-Query Retrievers, Contextual Compression, and Time-Weighted Vectorstore Retrievers. In this episode, we’ll delve into Multi-Query Retriever, Ensemble, and Long-Context Reorder techniques.

7. Multi-Query Retriever: Expanding Your Search Perspective

This retriever uses an LLM to generate multiple queries from the original input. It’s useful when the original query needs to gather information on multiple topics. By generating multiple perspectives, it retrieves a broader and richer set of documents.

  • Index Type: Any
  • Uses an LLM: Yes
  • Use Cases: When user queries are complex and require information from multiple distinct perspectives to be answered thoroughly.

7.1) Simple usage

Let’s start with a simple example where the retriever does all the query generation work for us.

Example - Multi-Query Retriever: Simple usage

Load and split documents

loaders = [
TextLoader("../../Tortoise and the Hare.txt"),
TextLoader("../../ลูกหมูสามตัว.txt"),
]
data = []
for loader in loaders:
data.extend(loader.load())
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
splits = text_splitter.split_documents(data)

VectorDB

embedding = OpenAIEmbeddings(api_key=OPENAI_API_KEY)
vectordb = Chroma.from_documents(documents=splits, embedding=embedding)

Simple usage: Specify the LLM to use for query generation, and the retriever will do the rest.

question = "What did the tortoise do in the race"
llm = ChatOpenAI(temperature=0, api_key=OPENAI_API_KEY)
retriever_from_llm = MultiQueryRetriever.from_llm(
retriever=vectordb.as_retriever(), llm=llm
)

Set logging for the queries

logging.basicConfig()
logging.getLogger("langchain.retrievers.multi_query").setLevel(logging.INFO)
unique_docs = retriever_from_llm.invoke(question)

Example of output of Generated queries and Retriever Result

"""
Generated queries: [
'1. How did the tortoise participate in the competition?',
'2. What actions did the tortoise take during the race?',
'3. In what way did the tortoise engage in the race?']
"""
"""
Retriever Result:
Document 1:
Tortoise and the Hare
Deep within a sun-dappled clearing of the DC Forest, lived a hare named Piti, famed for his lightning speed. He would streak past the other animals, a blur of brown fur that left them breathless in his wake. One crisp morning, Piti was bragging about his agility, puffing out his chest and flicking his tail with unconcealed pride.
"There's no creature in this entire forest faster than me!" he declared, his voice echoing through the trees.
A slow, rumbling voice came from behind a nearby thicket. It was Donato, a tortoise known for his steady pace and unwavering determination.
"Speed isn't everything, Piti," Donato rumbled. "Even the slowest can achieve victory, if they set their mind to it."
Piti burst into laughter. "You? Win a race against me? Donato, that's the most ludicrous notion I've ever heard!"
To everyone's surprise, Donato challenged Piti to a race. The other animals gathered around, buzzing with excitement at the prospect of such an unequal competition. Even the wise old owl hooted in amusement, his amber eyes twinkling with anticipation.
The race began. Piti shot off like a furry bullet, leaving Donato in a cloud of dust. The animals cheered for the hare, certain of his victory. But Piti, brimming with overconfidence, spotted a patch of wildflowers bursting with color. He darted off the track, unable to resist the temptation of a tasty treat.
Meanwhile, Donato plodded on steadily, never stopping, never wavering. He may have been slow, but his determination burned bright.
Back on the track, Piti, feeling sluggish from his snack, decided to take a nap under the shade of a towering oak. "Old Donato won't catch up to me anyway," he thought arrogantly.
He drifted off to sleep, picturing himself crossing the finish line first to a chorus of cheers. But time crawled by for the sleeping hare, while for Donato, it marched on relentlessly.
Donato, inch by inch, made his way towards the finish line. The animals, who had initially mocked him, now cheered him on, their voices echoing through the forest. They were impressed by his unwavering perseverance.
Finally, Donato, with a triumphant plod, crossed the finish line. Piti woke up with a start, his ears twitching in disbelief. He saw, to his utter humiliation, the crowd celebrating Donato's victory.
The hare had lost the race, not to speed, but to slow and steady determination. The cheers of the animals resonated through the DC Forest, a testament to the fact that slow and steady truly does win the race.
Document 2:
Tortoise and the Hare
Deep within a sun-dappled clearing of the DC Forest, lived a hare named Piti, famed for his lightning speed. He would streak past the other animals, a blur of brown fur that left them breathless in his wake. One crisp morning, Piti was bragging about his agility, puffing out his chest and flicking his tail with unconcealed pride.
"There's no creature in this entire forest faster than me!" he declared, his voice echoing through the trees.
A slow, rumbling voice came from behind a nearby thicket. It was Donato, a tortoise known for his steady pace and unwavering determination.
"Speed isn't everything, Piti," Donato rumbled. "Even the slowest can achieve victory, if they set their mind to it."
Piti burst into laughter. "You? Win a race against me? Donato, that's the most ludicrous notion I've ever heard!"
Document 3:
To everyone's surprise, Donato challenged Piti to a race. The other animals gathered around, buzzing with excitement at the prospect of such an unequal competition. Even the wise old owl hooted in amusement, his amber eyes twinkling with anticipation.
The race began. Piti shot off like a furry bullet, leaving Donato in a cloud of dust. The animals cheered for the hare, certain of his victory. But Piti, brimming with overconfidence, spotted a patch of wildflowers bursting with color. He darted off the track, unable to resist the temptation of a tasty treat.
Meanwhile, Donato plodded on steadily, never stopping, never wavering. He may have been slow, but his determination burned bright.
Back on the track, Piti, feeling sluggish from his snack, decided to take a nap under the shade of a towering oak. "Old Donato won't catch up to me anyway," he thought arrogantly.
Document 4:
He drifted off to sleep, picturing himself crossing the finish line first to a chorus of cheers. But time crawled by for the sleeping hare, while for Donato, it marched on relentlessly.
Donato, inch by inch, made his way towards the finish line. The animals, who had initially mocked him, now cheered him on, their voices echoing through the forest. They were impressed by his unwavering perseverance.
Finally, Donato, with a triumphant plod, crossed the finish line. Piti woke up with a start, his ears twitching in disbelief. He saw, to his utter humiliation, the crowd celebrating Donato's victory.
The hare had lost the race, not to speed, but to slow and steady determination. The cheers of the animals resonated through the DC Forest, a testament to the fact that slow and steady truly does win the race.
"""

7.2) supply your own prompt

You can customize the MultiQueryRetriever further by supplying your own prompt template.

Example - Multi-Query Retriever: supply your own prompt

Load and split documents

loaders = [
TextLoader("../../Tortoise and the Hare.txt"),
TextLoader("../../ลูกหมูสามตัว.txt"),
]
documents = []
for loader in loaders:
documents.extend(loader.load())
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
splits = text_splitter.split_documents(documents)

Create VectorDB using FAISS and OpenAIEmbeddings

embedding = OpenAIEmbeddings(api_key=OPENAI_API_KEY)
vectordb = FAISS.from_documents(splits, embedding)

Define Output Parser using Pydantic

class LineList(BaseModel):
lines: List[str] = Field(description="Lines of text")

class LineListOutputParser(PydanticOutputParser):
def init(self) -> None:
super().init(pydantic_object=LineList)

def parse(self, text: str) -> LineList:  
    lines = text.strip().split("\n")  
    return LineList(lines=lines)  

output_parser = LineListOutputParser()

Define Prompt Template

QUERY_PROMPT = PromptTemplate(
input_variables=["question"],
template="""You are an AI language model assistant. Your task is to generate five
different versions of the given user question to retrieve relevant documents from a vector
database. By generating multiple perspectives on the user question, your goal is to help
the user overcome some of the limitations of the distance-based similarity search.
Provide these alternative questions separated by newlines.
Original question: {question}""",
)

Define LLM and Create LLMChain

llm = ChatOpenAI(temperature=0, api_key=OPENAI_API_KEY)
llm_chain = LLMChain(llm=llm, prompt=QUERY_PROMPT)

Create MultiQueryRetriever with custom prompt

retriever = MultiQueryRetriever(
retriever=vectordb.as_retriever(), llm_chain=llm_chain, parser_key="lines"
)

Set question to retriever

question = "What did the tortoise do in the race?"
unique_docs = retriever.invoke(input=question)

Example of output of Generated queries and Retriever Result

"""
Generated queries:

  1. How did the tortoise participate in the race?
  2. What actions did the tortoise take during the race?
  3. In the race, what was the tortoise's role or behavior?
  4. Can you describe the tortoise's involvement in the race?
  5. What were the specific activities of the tortoise during the race?
    """
    """
    Retriever Result:
    Document 1:
    He drifted off to sleep, picturing himself crossing the finish line first to a chorus of cheers. But time crawled by for the sleeping hare, while for Donato, it marched on relentlessly.
    Donato, inch by inch, made his way towards the finish line. The animals, who had initially mocked him, now cheered him on, their voices echoing through the forest. They were impressed by his unwavering perseverance.
    Finally, Donato, with a triumphant plod, crossed the finish line. Piti woke up with a start, his ears twitching in disbelief. He saw, to his utter humiliation, the crowd celebrating Donato's victory.
    The hare had lost the race, not to speed, but to slow and steady determination. The cheers of the animals resonated through the DC Forest, a testament to the fact that slow and steady truly does win the race.
    Document 2:
    To everyone's surprise, Donato challenged Piti to a race. The other animals gathered around, buzzing with excitement at the prospect of such an unequal competition. Even the wise old owl hooted in amusement, his amber eyes twinkling with anticipation.
    The race began. Piti shot off like a furry bullet, leaving Donato in a cloud of dust. The animals cheered for the hare, certain of his victory. But Piti, brimming with overconfidence, spotted a patch of wildflowers bursting with color. He darted off the track, unable to resist the temptation of a tasty treat.
    Meanwhile, Donato plodded on steadily, never stopping, never wavering. He may have been slow, but his determination burned bright.
    Back on the track, Piti, feeling sluggish from his snack, decided to take a nap under the shade of a towering oak. "Old Donato won't catch up to me anyway," he thought arrogantly.
    Document 3:
    Tortoise and the Hare
    Deep within a sun-dappled clearing of the DC Forest, lived a hare named Piti, famed for his lightning speed. He would streak past the other animals, a blur of brown fur that left them breathless in his wake. One crisp morning, Piti was bragging about his agility, puffing out his chest and flicking his tail with unconcealed pride.
    "There's no creature in this entire forest faster than me!" he declared, his voice echoing through the trees.
    A slow, rumbling voice came from behind a nearby thicket. It was Donato, a tortoise known for his steady pace and unwavering determination.
    "Speed isn't everything, Piti," Donato rumbled. "Even the slowest can achieve victory, if they set their mind to it."
    Piti burst into laughter. "You? Win a race against me? Donato, that's the most ludicrous notion I've ever heard!"
    Document 4:
    ลูกหมูทั้งสามตัว ปลอดภัยอยู่ในบ้านอิฐ พวกเขาได้เรียนบทเรียนอันมีค่าในวันนั้น ความขยันหมั่นเพียรและการวางแผนนั้นสำคัญ และรากฐานที่แข็งแกร่งสามารถช่วยให้คุณปลอดภัยจากอันตราย พวกเขาอยู่ด้วยกันอย่างมีความสุขตลอดไปในบ้านอิฐที่แข็งแรง ไม่เคยลืมความสำคัญของการเตรียมตัว
    """

8. Ensemble: Combining Strengths

This retriever combines results from different retrievers, such as sparse (BM25) and dense (embedding-based) retrievers, and reranks them using algorithms like Reciprocal Rank Fusion. This hybrid approach leverages the strengths of each method for better retrieval performance.

  • Index Type: Any
  • Uses an LLM: No
  • Use Cases: When you have multiple retrieval methods and want to try combining them to leverage each method’s unique strengths.

Example - Self Query Retrievers

Load and split documents

loaders = [
TextLoader("../../Tortoise and the Hare.txt"),
TextLoader("../../ลูกหมูสามตัว.txt"),
]
documents = []
for loader in loaders:
documents.extend(loader.load())
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=0)
splits = text_splitter.split_documents(documents)

Create BM25 retriever

bm25_retriever = BM25Retriever.from_texts(
[doc.page_content for doc in splits], metadatas=[doc.metadata for doc in splits]
)
bm25_retriever.k = 3

Create FAISS retriever

embedding = OpenAIEmbeddings(api_key=OPENAI_API_KEY)
faiss_vectorstore = FAISS.from_texts(
[doc.page_content for doc in splits], embedding, metadatas=[doc.metadata for doc in splits]
)
faiss_retriever = faiss_vectorstore.as_retriever(search_kwargs={"k": 3})

Create Ensemble retriever

ensemble_retriever = EnsembleRetriever(
retrievers=[bm25_retriever, faiss_retriever], weights=[0.5, 0.5]
)

Set the question

question = "What did the tortoise do in the race?"
docs = ensemble_retriever.invoke(question)
for i, doc in enumerate(docs):
print(f"Document {i+1}:\n{doc.page_content}\n")

Example of output

"""
Document 1:
Tortoise and the Hare
Deep within a sun-dappled clearing of the DC Forest, lived a hare named Piti, famed for his lightning speed. He would streak past the other animals, a blur of brown fur that left them breathless in his wake. One crisp morning, Piti was bragging about his agility, puffing out his chest and flicking his tail with unconcealed pride.
"There's no creature in this entire forest faster than me!" he declared, his voice echoing through the trees.
Document 2:
A slow, rumbling voice came from behind a nearby thicket. It was Donato, a tortoise known for his steady pace and unwavering determination.
"Speed isn't everything, Piti," Donato rumbled. "Even the slowest can achieve victory, if they set their mind to it."
Piti burst into laughter. "You? Win a race against me? Donato, that's the most ludicrous notion I've ever heard!"
Document 3:
The hare had lost the race, not to speed, but to slow and steady determination. The cheers of the animals resonated through the DC Forest, a testament to the fact that slow and steady truly does win the race.
Document 4:
To everyone's surprise, Donato challenged Piti to a race. The other animals gathered around, buzzing with excitement at the prospect of such an unequal competition. Even the wise old owl hooted in amusement, his amber eyes twinkling with anticipation.
"""

9. Long-Context Reorder: Optimizing for Long Documents

This technique fetches documents from an underlying retriever and reorders them so that the most relevant are near the beginning and end. This is especially useful for models with long contexts that tend to ignore middle information.

  • Index Type: Any
  • Uses an LLM: No
  • Use Cases: If you are working with a long-context model and noticing that it’s not paying attention to information in the middle of retrieved documents.

Example - Self Query Retrievers

Load and split documents

loaders = [
TextLoader("../../Tortoise and the Hare.txt"),
TextLoader("../../ลูกหมูสามตัว.txt"),
]

documents = []
for loader in loaders:
documents.extend(loader.load())

text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=0)
splits = text_splitter.split_documents(documents)

Create retriever

embeddings = OpenAIEmbeddings(api_key=OPENAI_API_KEY)
retriever = Chroma.from_texts([doc.page_content for doc in splits], embedding=embeddings).as_retriever(search_kwargs={"k": 10})

set the question to retriever

question = "What did the tortoise do in the race?"
docs = retriever.invoke(question)

Reorder documents with Long-Context Reorder

reordering = LongContextReorder()
reordered_docs = reordering.transform_documents(docs)

Show reordered documents

print("Reordered Documents:")
for i, doc in enumerate(reordered_docs):
print(f"Document {i+1}:\n{doc.page_content}\n")

Example of output

"""
Document 5:
To everyone's surprise, Donato challenged Piti to a race. The other animals gathered around, buzzing with excitement at the prospect of such an unequal competition. Even the wise old owl hooted in amusement, his amber eyes twinkling with anticipation.
Document 6:
Back on the track, Piti, feeling sluggish from his snack, decided to take a nap under the shade of a towering oak. "Old Donato won't catch up to me anyway," he thought arrogantly.
He drifted off to sleep, picturing himself crossing the finish line first to a chorus of cheers. But time crawled by for the sleeping hare, while for Donato, it marched on relentlessly.
Document 7:
The race began. Piti shot off like a furry bullet, leaving Donato in a cloud of dust. The animals cheered for the hare, certain of his victory. But Piti, brimming with overconfidence, spotted a patch of wildflowers bursting with color. He darted off the track, unable to resist the temptation of a tasty treat.
Meanwhile, Donato plodded on steadily, never stopping, never wavering. He may have been slow, but his determination burned bright.
Document 8:
A slow, rumbling voice came from behind a nearby thicket. It was Donato, a tortoise known for his steady pace and unwavering determination.
"Speed isn't everything, Piti," Donato rumbled. "Even the slowest can achieve victory, if they set their mind to it."
Piti burst into laughter. "You? Win a race against me? Donato, that's the most ludicrous notion I've ever heard!"
Document 9:
Tortoise and the Hare
Deep within a sun-dappled clearing of the DC Forest, lived a hare named Piti, famed for his lightning speed. He would streak past the other animals, a blur of brown fur that left them breathless in his wake. One crisp morning, Piti was bragging about his agility, puffing out his chest and flicking his tail with unconcealed pride.
"There's no creature in this entire forest faster than me!" he declared, his voice echoing through the trees.
Document 10:
Tortoise and the Hare
Deep within a sun-dappled clearing of the DC Forest, lived a hare named Piti, famed for his lightning speed. He would streak past the other animals, a blur of brown fur that left them breathless in his wake. One crisp morning, Piti was bragging about his agility, puffing out his chest and flicking his tail with unconcealed pride.
"There's no creature in this entire forest faster than me!" he declared, his voice echoing through the trees.
A slow, rumbling voice came from behind a nearby thicket. It was Donato, a tortoise known for his steady pace and unwavering determination.
"Speed isn't everything, Piti," Donato rumbled. "Even the slowest can achieve victory, if they set their mind to it."
Piti burst into laughter. "You? Win a race against me? Donato, that's the most ludicrous notion I've ever heard!"
To everyone's surprise, Donato challenged Piti to a race. The other animals gathered around, buzzing with excitement at the prospect of such an unequal competition. Even the wise old owl hooted in amusement, his amber eyes twinkling with anticipation.
The race began. Piti shot off like a furry bullet, leaving Donato in a cloud of dust. The animals cheered for the hare, certain of his victory. But Piti, brimming with overconfidence, spotted a patch of wildflowers bursting with color. He darted off the track, unable to resist the temptation of a tasty treat.
Meanwhile, Donato plodded on steadily, never stopping, never wavering. He may have been slow, but his determination burned bright.
Back on the track, Piti, feeling sluggish from his snack, decided to take a nap under the shade of a towering oak. "Old Donato won't catch up to me anyway," he thought arrogantly.
He drifted off to sleep, picturing himself crossing the finish line first to a chorus of cheers. But time crawled by for the sleeping hare, while for Donato, it marched on relentlessly.
Donato, inch by inch, made his way towards the finish line. The animals, who had initially mocked him, now cheered him on, their voices echoing through the forest. They were impressed by his unwavering perseverance.
Finally, Donato, with a triumphant plod, crossed the finish line. Piti woke up with a start, his ears twitching in disbelief. He saw, to his utter humiliation, the crowd celebrating Donato's victory.
The hare had lost the race, not to speed, but to slow and steady determination. The cheers of the animals resonated through the DC Forest, a testament to the fact that slow and steady truly does win the race.
"""

Summary of Retriever Techniques

Here’s a quick recap of all the retriever techniques we’ve explored:

  1. Vectorstore Retrievers: A simple and efficient method for basic semantic similarity searches.
  2. ParentDocument Retrievers: Maintains context by retrieving larger parent documents while indexing smaller chunks.
  3. Multi-Vector Retrievers: Enables more precise searches by creating multiple vector embeddings for each document.
  4. Self-Query Retrievers: Enhances search accuracy by using LLMs to generate structured queries based on metadata.
  5. Contextual Compression: Optimizes retrievers by removing irrelevant information using LLMs or embeddings.
  6. Time-Weighted Vectorstore Retrievers: Prioritizes documents based on recency, ensuring results are timely and relevant.
  7. MultiQueryRetriever: Uses an LLM to generate multiple queries from the original, useful when the original query needs pieces of information about multiple topics to be properly answered.
  8. Ensemble Retriever: Combines multiple retrieval methods to get more robust results by leveraging the benefits of each method.
  9. Long-Context Reorder: Reorders retrieved documents to ensure long-context models pay attention to important information at the beginning and end of the document.

Key Takeaways

We’ve completed our exploration of retrieval techniques. By now you should have a robust set of techniques to use in your LangChain applications.

In Episode 41, we’ll explore “agents and concept”. Stay tuned to unlock the next level of intelligent data retrieval! 🚀


Data Science Explore the world of data science with Donato_Story

Dashboard Discover the power of data visualization with Donato_Story

Donato_Journey Join me on my journey (Thai version)

Course_Review Discover the training courses with Donato_Story (Thai version)

Let’s Connect!

Your thoughts and feedback are invaluable. Feel free to share them in the comments or connect with me on

Originally published on Medium

Related