(17-6-02)检索增强生成(RAG):多查询检索器+上下文压缩

5.6.3  多查询检索器

在LangChain中,MultiQueryRetriever是一种高级检索器,旨在通过生成多个查询来提高检索的全面性和准确性。MultiQueryRetriever检索器的核心思想是,通过从不同的角度和用不同的措辞来探索同一个问题,可以克服单一查询可能带来的局限性,从而获得更丰富和多样化的检索结果。

多角度探索:通过生成多个查询,MultiQueryRetriever可以从不同的角度探索问题,这有助于捕捉问题的不同方面和细微差别。

在实际应用中,多查询检索器的优势如下所示。

  1. 提高覆盖率:由于考虑了多个查询,MultiQueryRetriever能够提高检索结果的覆盖率,减少遗漏重要信息的风险。
  2. 灵活性:用户可以自定义LLM生成查询的方式,包括提供自定义的提示模板和输出解析器,以满足特定的需求。
  3. 去重:自动去除重复的文档,确保结果集中的文档是多样化的。

MultiQueryRetriever 的工作原理是,对于每个生成的查询,它都会检索一组相关文档,然后取所有查询得到的结果的并集,以获得更大范围的潜在相关文档。通过这种方式,检索器可以从多个角度探索同一个问题,从而获得更丰富的结果集。请看下面的实例,演示了使用多查询检索器(MultiQueryRetriever)从向量数据库中检索信息的过程。

实例5-1使用多查询检索器从向量数据库中检索信息(源码路径:codes\5\jian02.py

实例文件jian02.py的具体实现代码如下所示。

from langchain_chroma import Chroma
from langchain_community.document_loaders import WebBaseLoader
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter

# 加载博客文章
loader = WebBaseLoader("https://lilianweng.github.io/posts/2023-06-23-agent/")
data = loader.load()

# 分割文本
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=0)
splits = text_splitter.split_documents(data)

# 向量数据库
embedding = OpenAIEmbeddings()
vectordb = Chroma.from_documents(documents=splits, embedding=embedding)

from langchain.retrievers.multi_query import MultiQueryRetriever
from langchain_openai import ChatOpenAI

question = "What are the approaches to Task Decomposition?"
llm = ChatOpenAI(temperature=0)
retriever_from_llm = MultiQueryRetriever.from_llm(
    retriever=vectordb.as_retriever(), llm=llm
)

# 设置日志记录
import logging
logging.basicConfig()
logging.getLogger("langchain.retrievers.multi_query").setLevel(logging.INFO)

unique_docs = retriever_from_llm.get_relevant_documents(query=question)
print(len(unique_docs) )

上述代码的实现流程如下所示:

  1. 首先,通过WebBaseLoader从指定的网址加载博客文章数据。加载完文章数据后,使用RecursiveCharacterTextSplitter对文章进行分割,将文章内容按照指定的字符数量和重叠度进行切片,生成多个文本片段。
  2. 接下来,使用OpenAIEmbeddings创建一个嵌入器对象,这个对象将会把文本片段转换成嵌入向量。嵌入向量是一种将文本信息映射到高维空间的数学表示。
  3. 使用Chroma.from_documents方法构建一个向量数据库,将嵌入向量存储到数据库中。这个向量数据库可以理解为一个存储了文本信息的高效检索系统,它能够根据输入的查询快速地检索出与之相似的文档。
  4. 导入MultiQueryRetriever和ChatOpenAI类,准备使用它们来构建一个多查询检索器。
  5. 创建一个ChatOpenAI对象作为语言模型,这个语言模型将用于生成多个查询。在这个例子中,语言模型负责生成多个与用户提供的查询相关的问题。
  6. 使用MultiQueryRetriever.from_llm方法创建一个基于语言模型的多查询检索器。这个检索器将结合向量数据库和语言模型,以生成多个查询并检索相关文档。
  7. 设置日志记录级别为INFO,以便在控制台输出查询生成的信息。
  8. 最后,使用创建的多查询检索器来检索关于任务分解方法的相关文档,并输出检索到的文档数量。

执行后会输出生成的查询和检索到的文档数量:

INFO:langchain.retrievers.multi_query:Generated queries: ['1. How can Task Decomposition be approached?', '2. What are the different methods for Task Decomposition?', '3. What are the various approaches to decomposing tasks?']
5

请看下面的实例,实现了基于多重查询的文档检索功能,利用LLM模型自动生成多个查询以获取更丰富的检索结果。

实例5-3利用LLM模型自动生成多个查询(源码路径:codes\5\jian03.py

实例文件jian03.py的具体实现代码如下所示。

# 加载博客文章
loader = WebBaseLoader("https://lilianweng.github.io/posts/2023-06-23-agent/")
data = loader.load()

# 分割文本
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=0)
splits = text_splitter.split_documents(data)

# 向量数据库
embedding = OpenAIEmbeddings()
vectordb = Chroma.from_documents(documents=splits, embedding=embedding)

# 输出解析器将 LLM 结果分割成查询列表
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()

# 自定义提示模板
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}""",
)
llm = ChatOpenAI(temperature=0)
# 构建 LLM 链
llm_chain = LLMChain(llm=llm, prompt=QUERY_PROMPT, output_parser=output_parser)
# 其他输入
question = "What are the approaches to Task Decomposition?"

# 运行检索器
retriever = MultiQueryRetriever(
    retriever=vectordb.as_retriever(), llm_chain=llm_chain, parser_key="lines"
)
# 结果
unique_docs = retriever.get_relevant_documents(
    query="What does the course say about regression?"
)
print(len(unique_docs))

上述代码的实现流程如下所示:

  1. 通过WebBaseLoader加载指定URL的博客文章。
  2. 使用RecursiveCharacterTextSplitter将加载的博客文章分割成适当大小的文本块。
  3. 使用OpenAIEmbeddings将文本块嵌入向量空间,并使用Chroma将嵌入的文本块存储到向量数据库中。
  4. 定义一个自定义的输出解析器LineListOutputParser,用于将LLM模型的输出结果分割成一个查询列表。
  5. 创建一个LLM链,使用ChatOpenAI作为LLM模型,并设置了一个自定义的提示模板QUERY_PROMPT,用于生成多个查询。
  6. 设置一个查询问题,并使用MultiQueryRetriever从向量数据库中检索相关文档,检索器会根据给定的查询问题生成多个查询,并返回检索到的相关文档数量。

执行结果取决于生成的查询和检索到的文档数量,例如执行后会输出:

11

5.6.4  上下文压缩

在LangChain中,上下文压缩(Contextual Compression)是一种用于改善检索系统性能的技术。在传统的检索系统中,检索到的文档可能包含大量无关信息,这会增加处理成本并降低系统的响应速度。上下文压缩技术的目标是通过使用查询的上下文信息,将检索到的文档进行压缩,以过滤掉无关信息,只保留与查询相关的信息,从而提高系统的效率和性能。LangChain主要通过如下两个组件来实现上下文压缩功能。

  1. 基础检索器(Base Retriever):这是用于执行原始文档检索的组件。它接收用户查询并返回一组初始文档。
  2. 文档压缩器(Document Compressor):这是用于对初始文档进行压缩的组件。文档压缩器可以根据查询的上下文信息,过滤文档内容或者整体性地减少文档数量,从而提取出与查询相关的信息。

在LangChain中,可以通过不同的压缩器实现上下文压缩,例如:

  1. LLMChainExtractor:使用语言模型链(LLM)从文档中提取与查询相关的内容。
  2. LLMChainFilter:使用LLM决定是否过滤掉初始文档中的某些内容。
  3. EmbeddingsFilter: 使用嵌入向量相似度过滤掉与查询不相关的文档。

通过这些压缩器,LangChain可以根据具体需求,定制不同的上下文压缩检索器,从而提高检索系统的效率和性能,减少无关信息的干扰,提供更准确和有针对性的搜索结果。请看下面的实例,演示了使用LangChain上下文压缩(Contextual Compression)器改进文档检索系统的过程。首先,使用基本的向量存储检索器获取相关文档的信息。然后,通过引入不同的文档压缩器,如LLMChainExtractor、LLMChainFilter和EmbeddingsFilter,以及文档压缩管道,演示了在保留相关信息的同时,减少文档内容并过滤无关文档从而提高检索效率和结果质量的方法。

实例5-1使用上下文压缩器改进文档检索系统(源码路径:codes\5\jian04.py

实例文件jian04.py的具体实现流程如下所示。

(1)定义函数pretty_print_docs(docs),功能是将文档打印为易读的格式。它接受一个文档列表作为参数,然后将每个文档的内容以带有标识的形式打印出来,以便用户更容易地阅读和理解文档内容。

from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor
from langchain_openai import OpenAI

# 初始化OpenAI语言模型
llm = OpenAI(temperature=0)
# 从LLM构建LLMChainExtractor文档压缩器
compressor = LLMChainExtractor.from_llm(llm)
# 构建上下文压缩检索器
compression_retriever = ContextualCompressionRetriever(
    base_compressor=compressor, base_retriever=retriever
)

# 获取压缩后的相关文档
compressed_docs = compression_retriever.get_relevant_documents(
    "What did the president say about Ketanji Jackson Brown"
)
# 打印压缩后的相关文档
pretty_print_docs(compressed_docs)

(2)使用普通向量存储检索器

初始化一个简单的向量存储检索器,加载文本文档state_of_the_union.txt,并使用字符文本分割器将文档分割为片段。然后,构建了一个向量存储并将其作为检索器。接着,它使用检索器来获取与给定查询相关的文档,并通过pretty_print_docs函数打印出这些相关文档。

from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor
from langchain_openai import OpenAI

# 初始化OpenAI语言模型
llm = OpenAI(temperature=0)
# 从LLM构建LLMChainExtractor文档压缩器
compressor = LLMChainExtractor.from_llm(llm)
# 构建上下文压缩检索器
compression_retriever = ContextualCompressionRetriever(
    base_compressor=compressor, base_retriever=retriever
)

# 获取压缩后的相关文档
compressed_docs = compression_retriever.get_relevant_documents(
    "What did the president say about Ketanji Jackson Brown"
)
# 打印压缩后的相关文档
pretty_print_docs(compressed_docs)

(3)添加上下文压缩器(LLMChainExtractor)

现在使用上下文压缩检索器包装基本检索器,将添加一个LLMChainExtractor,将迭代初始返回的文档,并从每个文档中提取与查询相关的内容。

from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor
from langchain_openai import OpenAI

# 初始化OpenAI语言模型
llm = OpenAI(temperature=0)
# 从LLM构建LLMChainExtractor文档压缩器
compressor = LLMChainExtractor.from_llm(llm)
# 构建上下文压缩检索器
compression_retriever = ContextualCompressionRetriever(
    base_compressor=compressor, base_retriever=retriever
)

# 获取压缩后的相关文档
compressed_docs = compression_retriever.get_relevant_documents(
    "What did the president say about Ketanji Jackson Brown"
)
# 打印压缩后的相关文档
pretty_print_docs(compressed_docs)

在上述代码中,首先始化了一个 OpenAI 语言模型,并使用该模型构建了一个 LLMChainExtractor 文档压缩器。然后,构建了一个上下文压缩检索器,该检索器使用了前面创建的压缩器和之前构建的基本检索器。接着,使用压缩后的检索器获取与给定查询相关的文档,并通过 pretty_print_docs 函数打印出这些压缩后的相关文档。

(4)更多压缩器

除了LLMChainExtractor之外,在LangChain中还有其他一些压缩器,如LLMChainFilter和EmbeddingsFilter。

  1. 实现LLMChainFilter压缩器:编写下面的代码,首先从 OpenAI 语言模型构建了一个 LLMChainFilter 文档压缩器。然后,它使用该压缩器构建了一个上下文压缩检索器,该检索器与之前的基本检索器一起使用。接着,它使用压缩后的检索器获取了与给定查询相关的文档,并通过 pretty_print_docs 函数打印出这些压缩后的相关文档。
from langchain.retrievers.document_compressors import LLMChainFilter

# 从LLM构建LLMChainFilter文档压缩器
_filter = LLMChainFilter.from_llm(llm)
# 构建上下文压缩检索器
compression_retriever = ContextualCompressionRetriever(
    base_compressor=_filter, base_retriever=retriever
)

# 获取压缩后的相关文档
compressed_docs = compression_retriever.get_relevant_documents(
    "What did the president say about Ketanji Jackson Brown"
)
# 打印压缩后的相关文档
pretty_print_docs(compressed_docs)
  1. 实现EmbeddingsFilter压缩器:下面代码使用了 EmbeddingsFilter 来构建文档压缩器,该压缩器基于嵌入模型的相似度阈值进行过滤。然后,它将压缩器与之前的基本检索器一起使用,构建了一个上下文压缩检索器。接着,它使用压缩后的检索器获取了与给定查询相关的文档,并通过 pretty_print_docs 函数打印出这些压缩后的相关文档。
from langchain.retrievers.document_compressors import DocumentCompressorPipeline
from langchain_community.document_transformers import EmbeddingsRedundantFilter
from langchain_text_splitters import CharacterTextSplitter

# 初始化字符文本分割器
splitter = CharacterTextSplitter(chunk_size=300, chunk_overlap=0, separator=". ")
# 初始化EmbeddingsRedundantFilter文档转换器
redundant_filter = EmbeddingsRedundantFilter(embeddings=embeddings)
# 使用EmbeddingsFilter构建文档压缩器
relevant_filter = EmbeddingsFilter(embeddings=embeddings, similarity_threshold=0.76)
# 构建文档压缩管道
pipeline_compressor = DocumentCompressorPipeline(
    transformers=[splitter, redundant_filter, relevant_filter]
)
# 构建上下文压缩检索器
compression_retriever = ContextualCompressionRetriever(
    base_compressor=pipeline_compressor, base_retriever=retriever
)
# 获取压缩后的相关文档
compressed_docs = compression_retriever.get_relevant_documents(
    "What did the president say about Ketanji Jackson Brown"
)
# 打印压缩后的相关文档
pretty_print_docs(compressed_docs)

(5)文档压缩管道和文档转换器

编写如下所示的代码,首先初始化了字符文本分割器 CharacterTextSplitter,然后初始化了 EmbeddingsRedundantFilter 文档转换器,并使用 EmbeddingsFilter 构建了文档压缩器。接着,构建了一个包含多个文档转换器的文档压缩管道 DocumentCompressorPipeline,其中包括字符文本分割器、冗余过滤器和相关性过滤器。然后,使用压缩器与之前的基本检索器一起构建了一个上下文压缩检索器。最后,使用压缩后的检索器获取了与给定查询相关的文档,并通过 pretty_print_docs 函数打印出这些压缩后的相关文档。

from langchain.retrievers.document_compressors import DocumentCompressorPipeline
from langchain_community.document_transformers import EmbeddingsRedundantFilter
from langchain_text_splitters import CharacterTextSplitter

# 初始化字符文本分割器
splitter = CharacterTextSplitter(chunk_size=300, chunk_overlap=0, separator=". ")
# 初始化EmbeddingsRedundantFilter文档转换器
redundant_filter = EmbeddingsRedundantFilter(embeddings=embeddings)
# 使用EmbeddingsFilter构建文档压缩器
relevant_filter = EmbeddingsFilter(embeddings=embeddings, similarity_threshold=0.76)
# 构建文档压缩管道
pipeline_compressor = DocumentCompressorPipeline(
    transformers=[splitter, redundant_filter, relevant_filter]
)
# 构建上下文压缩检索器
compression_retriever = ContextualCompressionRetriever(
    base_compressor=pipeline_compressor, base_retriever=retriever
)
# 获取压缩后的相关文档
compressed_docs = compression_retriever.get_relevant_documents(
    "What did the president say about Ketanji Jackson Brown"
)
# 打印压缩后的相关文档
pretty_print_docs(compressed_docs)

未完待续

  • 26
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农三叔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值