Advertisement

(17-6-04)检索增强生成(RAG):长文本检索器+多向量检索器

阅读量:

5.6.7 长文本检索器

在LangChain框架中,LongContextReorder类被设计用于应对长文本下知识库性能降低的问题。这种性能退化现象通常源于模型在处理长段落内容时难以高效地整理和利用全部信息,并尤其是在段落中间位置的信息存储上存在不足。通过将检索到的相关文档按照新的顺序重新排列组合,在此过程中LongContextReorder类旨在优化模型对上下文数据的整体利用效率。

LongContextReorder的主要作用是对已检索得到的文档列表进行重新排列,在这种操作中,系统会将与查询最为相关的文档放置在列表的两端位置,并将与查询关联性较低的内容移至中间区域,在此过程中旨在使模型能够更加关注那些最重要的信息来源。通过优化文档顺序安排这一目标导向原则,在提升该方法在处理长文本场景中的性能方面发挥了显著作用;尤其是在需要从大量文档中提取特定信息的情境下表现更为突出

在实际应用场景中,LongContextReorder能够与LangChain框架中的多种检索工具(例如Chroma)进行整合应用。通过这种方式,在提升知识图谱查询效率的同时也显著提升了搜索结果的质量。例如,请考虑以下利用LangChain库实现的具体场景:通过将多个检索模块协同工作,在优化长文本信息提取效率的同时实现了对复杂问题的精准解答能力。

实例5-7优化了长上下文中的检索信息(源码路径:codes*5*jian07.py******)****

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

复制代码
 from langchain.chains import LLMChain, StuffDocumentsChain

    
 from langchain.prompts import PromptTemplate
    
 from langchain_chroma import Chroma
    
 from langchain_community.document_transformers import (
    
     LongContextReorder,
    
 )
    
 from langchain_community.embeddings import HuggingFaceEmbeddings
    
 from langchain_openai import OpenAI
    
  
    
 # 获取嵌入模型
    
 embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
    
  
    
 # 文本列表
    
 texts = [
    
     "篮球是一项伟大的运动。",
    
     "《飞往月球》是我最喜欢的歌曲之一。",
    
     "凯尔特人是我最喜欢的球队。",
    
     "这是关于波士顿凯尔特人的文档。",
    
     "我简直喜欢去电影院。",
    
     "波士顿凯尔特人队以20分的优势赢得了比赛。",
    
     "这只是一段随机文本。",
    
     "《埃尔登之环》是过去15年中最棒的游戏之一。",
    
     "L·科尔奈特是最好的凯尔特人球员之一。",
    
     "拉里·伯德是一位标志性的NBA球员。",
    
 ]
    
  
    
 # 创建检索器
    
 retriever = Chroma.from_texts(texts, embedding=embeddings).as_retriever(
    
     search_kwargs={"k": 10}
    
 )
    
 query = "关于凯尔特人队你能告诉我什么?"
    
  
    
 # 获取相关文档,按相关性得分排序
    
 docs = retriever.get_relevant_documents(query)
    
 print(docs)
    
  
    
 # 对文档进行重新排序:
    
 # 相关度较低的文档将位于列表中间,相关度较高的文档位于开头和结尾。
    
 reordering = LongContextReorder()
    
 reordered_docs = reordering.transform_documents(docs)
    
 # 确认四个相关文档位于列表的开头和结尾。
    
 print(reordered_docs)
    
  
    
 # 我们准备并运行一个自定义Stuff链,并使用重新排序的文档作为上下文。
    
  
    
 # 覆盖提示
    
 document_prompt = PromptTemplate(
    
     input_variables=["page_content"], template="{page_content}"
    
 )
    
 document_variable_name = "context"
    
 llm = OpenAI()
    
 stuff_prompt_override = """给定这些文本摘录:
    
 -----
    
 {context}
    
 -----
    
 请回答以下问题:
    
 {query}"""
    
 prompt = PromptTemplate(
    
     template=stuff_prompt_override, input_variables=["context", "query"]
    
 )
    
  
    
 # 实例化链
    
 llm_chain = LLMChain(llm=llm, prompt=prompt)
    
 chain = StuffDocumentsChain(
    
     llm_chain=llm_chain,
    
     document_prompt=document_prompt,
    
     document_variable_name=document_variable_name,
    
 )
    
 chain.run(input_documents=reordered_docs, query=query)

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

该类采用HuggingFaceEmbeddings工具来获得预训练text embedding model,并且其中采用了all-MiniLM-L6-v2这个特定版本

(2)定义文本列表:创建一个涵盖多样化的主题领域的文本片段列表,并列举以下几种:篮球、音乐、电影以及电子游戏等。

(3)创建检索器:通过Chroma类与文本嵌入技术构建一个检索系统,该系统能够基于用户的查询请求从文本库中提取相关信息。将search_kwargs参数配置为{'k': 10},表示每次搜索最多返回十个匹配项。

(4)执行检索操作:调用...函数以获取基于用户的查询"关于凯尔特人队你能告诉我什么?"的相关文档列表。

(5)重新排序文档:通过类LongContextReorder对检索到的文档进行排序,在列表中将相关性较高的文档放置于前端与末尾位置。此操作旨在提升模型处理长段落内容的能力,并因模型通常在处理较长文本时,在内容的起始与结束部分表现出较强的表现力而得以实施。

(6)搭建提示与处理流程:生成PromptTemplate对象并用于构建处理流程中的提示信息。其中包含一个覆盖范围内的提示机制,并接受文档内容与查询作为输入参数

(7)创建LLMChain和StuffDocumentsChain这两个实例,并将它们用于重新排列文档以输出相应的回答内容。

通过运行该StuffDocumentsChain中的run方法,并以重新排序的文档和查询为输入参数来生成最终的回答。

执行后会输出:

复制代码
 # 获取相关文档的原始顺序

    
 [
    
   Document(page_content='这是关于波士顿凯尔特人的文档'),
    
   Document(page_content='L. Kornet 是最好的凯尔特人球员之一。'),
    
   Document(page_content='拉里·伯德是一位标志性的 NBA 球员。'),
    
   Document(page_content='波士顿凯尔特人队以 20 分的优势赢得了比赛。'),
    
   Document(page_content='我简直喜欢去电影院。'),
    
   Document(page_content='《飞往月球》是我最喜欢的歌曲之一。'),
    
   Document(page_content='这只是一段随机文本。'),
    
   Document(page_content='《埃尔登之环》是过去 15 年中最棒的游戏之一。'),
    
   Document(page_content='篮球是一项伟大的运动。')
    
 ]
    
  
    
 # 重新排序文档后的顺序
    
 [
    
   Document(page_content='凯尔特人是我最喜欢的球队。'),
    
   Document(page_content='波士顿凯尔特人队以 20 分的优势赢得了比赛。'),
    
   Document(page_content='这只是一段随机文本。'),
    
   Document(page_content='我简直喜欢去电影院。'),
    
   Document(page_content='《飞往月球》是我最喜欢的歌曲之一。'),
    
   Document(page_content='《埃尔登之环》是过去 15 年中最棒的游戏之一。'),
    
   Document(page_content='篮球是一项伟大的运动。'),
    
   Document(page_content='L. Kornet 是最好的凯尔特人球员之一。'),
    
   Document(page_content='这是关于波士顿凯尔特人的文档。')
    
 ]
    
  
    
 # 最终生成的回答
    
 '''
    
 给定这些文本摘录:
    
 -----
    
 The Celtics are my favorite team.
    
 The Boston Celtics won the game by 20 points.
    
 This is just a random text.
    
 I simply love going to the movies.
    
 Fly me to the moon is one of my favorite songs.
    
 Elden Ring is one of the best games in the last 15 years.
    
 Basketball is a great sport.
    
 L. Kornet is one of the best Celtics players.
    
 This is a document about the Boston Celtics.
    
 -----
    
 请回答以下问题:
    
 关于凯尔特人队你能告诉我什么?
    
 '''
    
  
    
 凯尔特人队是NBA中的一支非常知名且历史悠久的篮球队。根据提供的文本摘录,凯尔特人队赢得了一场比赛,优势明显。此外,L. Kornet被提及为队中最优秀的球员之一,而拉里·伯德则被尊称为标志性的NBA球员。这些信息表明,凯尔特人队在篮球领域有着丰富的成功经历和杰出的球员阵容。

本实例旨在确保,在长上下文中,模型能够更为专注地聚焦以及妥善处理相关信息,并从而提升信息检索与回答的准确率。通过重新排列文档内容,有助于缓解模型在处理长文本过程中可能遇到的性能下降的问题。

5.6.8 多向量检索器

在LangChain中属于一个多向量检索器类。该类支持每个文档存储与检索多个向量。这种策略特别适合于在多种角度或不同表示下进行信息检索的情景。提供了增强系统性能与准确性的灵活机制。

多向量检索器的主要特点如下所示。

  1. 多个向量存储:与传统的检索器不同,MultiVectorRetriever可以为每个文档存储多个向量。这些向量可以代表文档的不同部分、摘要、相关问题或其他与文档相关的信息。
  2. 灵活的检索策略:MultiVectorRetriever支持多种检索策略,包括相似性搜索(基于向量之间的相似度)和最大边际相关性(MMR)搜索(选择与查询最相关且彼此之间相关性较低的文档集)。
  3. 自定义向量生成:用户可以自定义文档向量的生成方式,例如通过分割文档、生成摘要或创建假设性问题等。
  4. 精确控制:MultiVectorRetriever允许用户精确控制哪些向量用于检索,以及如何组合这些向量以获得最佳结果。

在实际应用环境中,MultiVectorRetriever常见地与诸如Chroma这样的向量存储系统协同工作。其功能依赖于向量存储系统来完成文档向量的存取操作。其实现涉及以下几个核心组件:

  1. 矢量化处理系统:旨在将文本数据转换为数值形式以供模型进行分析与处理。
  2. 字节数组层级:负责组织并保存原始信息包括元数据与具体内容。
  3. 唯一标识码:负责建立不同层次之间的对应关系以确保数据的一致性与完整性。

在类MultiVectorRetriever中提供了如下所示的成员方法:

  1. 将文档及其向量加入到向量存储库中。
  2. 根据用户查询返回相关文档列表。
  3. 执行基于向量相似度的搜索操作。

在实际应用场景中,该系统的主要应用场景具体来说如下所示。

1. 分割文档

处理长文档时将其划分为更小的段落,并对每个段落创建向量表示。例如下面展示了使用MultiVectorRetriever进行文档划分并为各个部分生成向量的具体步骤

实例5-1****:分割文档并为每个部分生成向量(源码路径:codes*5**jian08*.py********)****

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

复制代码
 loaders = [

    
     TextLoader("example1.txt"),
    
 ]
    
 docs = []
    
 for loader in loaders:
    
     docs.extend(loader.load())
    
  
    
 # 使用文本分割器分割文档
    
 text_splitter = RecursiveCharacterTextSplitter(chunk_size=10000)
    
 docs = text_splitter.split_documents(docs)
    
  
    
 # 创建向量存储和字节存储
    
 vectorstore = Chroma(collection_name="full_documents", embedding_function=OpenAIEmbeddings())
    
 store = InMemoryByteStore()
    
 id_key = "doc_id"
    
  
    
 # 初始化MultiVectorRetriever
    
 retriever = MultiVectorRetriever(
    
     vectorstore=vectorstore,
    
     byte_store=store,
    
     id_key=id_key,
    
 )
    
  
    
 # 为每个文档生成唯一的ID
    
 doc_ids = [str(uuid.uuid4()) for _ in docs]
    
  
    
 # 分割文档为更小的块
    
 child_text_splitter = RecursiveCharacterTextSplitter(chunk_size=400)
    
 sub_docs = []
    
 for i, doc in enumerate(docs):
    
     _id = doc_ids[i]
    
     _sub_docs = child_text_splitter.split_documents([doc])
    
     for _doc in _sub_docs:
    
     _doc.metadata[id_key] = _id
    
     sub_docs.extend(_sub_docs)
    
  
    
 # 将子文档及其向量添加到向量存储
    
 retriever.vectorstore.add_documents(sub_docs)
    
 retriever.docstore.mset(list(zip(doc_ids, docs)))
    
  
    
 # 现在可以使用MultiVectorRetriever来检索与查询相关的子文档
    
 # 例如,搜索包含"justice breyer"的子文档
    
 retriever.vectorstore.similarity_search("justice breyer")

eCharacterTextSplitter被用来划分文本为较小的部分。我们将每个子文档分配一个独特的标识符,并将它们及其对应的向量存入Chroma向量存储系统中。最后一步是利用MultiVectorRetriever系统来进行基于相似度的搜索任务,在结果中找到包含查询关键词如'justice breyer'的相关文件。运行后将返回:

复制代码
 loaders = [

    
     TextLoader("paul_graham_essay.txt"),
    
     TextLoader("state_of_the_union.txt"),
    
 ]
    
 docs = []
    
 for loader in loaders:
    
     docs.extend(loader.load())
    
 text_splitter = RecursiveCharacterTextSplitter(chunk_size=10000)
    
 docs = text_splitter.split_documents(docs)
    
  
    
 # 使用LangChain的链来生成摘要
    
 chain = (
    
     {"doc": lambda x: x.page_content}
    
     | ChatPromptTemplate.from_template("Summarize the following document:\n\n{doc}")
    
     | ChatOpenAI(max_retries=0)
    
     | StrOutputParser()
    
 )
    
  
    
 summaries = chain.batch(docs, {"max_concurrency": 5}) # 并行生成摘要以提高效率
    
  
    
 # 创建向量存储以索引子块
    
 vectorstore = Chroma(collection_name="summaries", embedding_function=OpenAIEmbeddings())
    
 # 创建存储层以存储父文档
    
 store = InMemoryByteStore()
    
 id_key = "doc_id"  # 用于在向量存储和字节存储之间关联文档的唯一标识符
    
  
    
 # 初始化MultiVectorRetriever
    
 retriever = MultiVectorRetriever(
    
     vectorstore=vectorstore,
    
     byte_store=store,
    
     id_key=id_key,
    
 )
    
  
    
 # 为每个摘要生成唯一ID并创建Document对象
    
 doc_ids = [str(uuid.uuid4()) for _ in range(len(summaries))]
    
 summary_docs = [
    
     Document(page_content=summary, metadata={id_key: doc_id})
    
     for summary, doc_id in zip(summaries, doc_ids)
    
 ]
    
  
    
 # 将摘要文档及其向量添加到向量存储
    
 retriever.vectorstore.add_documents(summary_docs)
    
 retriever.docstore.mset(list(zip(doc_ids, docs)))
    
 # 现在可以使用MultiVectorRetriever来检索与查询相关的摘要
    
 # 例如,搜索包含"justice breyer"的摘要
    
 retriever.get_relevant_documents("justice breyer")

2. 生成摘要

根据每个文档创建摘要后,在对摘要内容进行向量化处理时会采用以下步骤:首先基于特定算法自动生成文件总结;接着将总结文字转化为数值形式;最后通过预设模型完成整个编码过程。例如,在实际操作中可利用MultiVectorRetriever工具对文件进行自动提取,并完成相应向量的生成和编码工作

实例5-1:基于MultiVectorRetriever提取文件摘要与向量(源码路径:codes/5/jian09.py)

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

复制代码
 loaders = [

    
     TextLoader("paul_graham_essay.txt"),
    
     TextLoader("state_of_the_union.txt"),
    
 ]
    
 docs = []
    
 for loader in loaders:
    
     docs.extend(loader.load())
    
 text_splitter = RecursiveCharacterTextSplitter(chunk_size=10000)
    
 docs = text_splitter.split_documents(docs)
    
  
    
 # 使用LangChain的链来生成摘要
    
 chain = (
    
     {"doc": lambda x: x.page_content}
    
     | ChatPromptTemplate.from_template("Summarize the following document:\n\n{doc}")
    
     | ChatOpenAI(max_retries=0)
    
     | StrOutputParser()
    
 )
    
  
    
 summaries = chain.batch(docs, {"max_concurrency": 5}) # 并行生成摘要以提高效率
    
  
    
 # 创建向量存储以索引子块
    
 vectorstore = Chroma(collection_name="summaries", embedding_function=OpenAIEmbeddings())
    
 # 创建存储层以存储父文档
    
 store = InMemoryByteStore()
    
 id_key = "doc_id"  # 用于在向量存储和字节存储之间关联文档的唯一标识符
    
  
    
 # 初始化MultiVectorRetriever
    
 retriever = MultiVectorRetriever(
    
     vectorstore=vectorstore,
    
     byte_store=store,
    
     id_key=id_key,
    
 )
    
  
    
 # 为每个摘要生成唯一ID并创建Document对象
    
 doc_ids = [str(uuid.uuid4()) for _ in range(len(summaries))]
    
 summary_docs = [
    
     Document(page_content=summary, metadata={id_key: doc_id})
    
     for summary, doc_id in zip(summaries, doc_ids)
    
 ]
    
  
    
 # 将摘要文档及其向量添加到向量存储
    
 retriever.vectorstore.add_documents(summary_docs)
    
 retriever.docstore.mset(list(zip(doc_ids, docs)))
    
 # 现在可以使用MultiVectorRetriever来检索与查询相关的摘要
    
 # 例如,搜索包含"justice breyer"的摘要
    
 retriever.get_relevant_documents("justice breyer")

在上述代码中:
第一步是利用LangChain工具对每个文档进行摘要生成。
随后构建了Chroma向量数据库以及InMemoryByteStore层。
这些结构均用于存储摘要信息及其相关的元数据。
配置了MultiVectorRetriever后,在其基础上分别初始化并保存了与每个摘要相关的Document对象至向量数据库中。
通过调用retriever.get_relevant_documents方法实现了对特定查询词如'justice breyer'相关资料的检索。
执行后会输出:

复制代码
 # 执行摘要生成链后的输出

    
 [
    
     "Paul Graham's essay highlights the critical role of startups in driving technological progress and innovation.",
    
     "The State of the Union address emphasizes the nation's economic growth, job creation, and commitment to tackling future challenges together."
    
     # ... 其他文档的摘要 ...
    
 ]
    
  
    
 # 检索与"justice breyer"相关的摘要
    
 [
    
     Document(page_content="The State of the Union address emphasizes the importance of nominating a Supreme Court justice and introduces Judge Ketanji Brown Jackson as the nominee.", metadata={'doc_id': '56345bff-3ead-418c-a4ff-dff203f77474'})
    
     # ... 可能还有其他匹配的摘要 ...
    
 ]
    
  
    
 # 输出检索到的第一个文档的内容长度
    
 90  # 这是第一个匹配摘要的文本长度

在上文中,在利用chain.batch函数为每个文档生成摘要信息后,并将其整合至MultiVectorRetriever系统中。随后通过vectorstore.similarity_search这一功能模块实现对包含特定关键词(例如"justice breyer")的相关摘要进行检索操作。最终系统会获取并显示第一个匹配到的结果文本及其具体长度。

3. 假设性问题

从每个文档中提取若干假设性查询项,并为其构建相应的向量表示。如在下述案例中所示,在使用MultiVectorRetriever工具提取相应的查询项的同时,并为其构建相应的向量表示。

案例编号5-1:基于MultiVectorRetriever创建虚拟问题模型并提取向量(代码位置:codes/5/jian10.py)

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

复制代码
 # 定义生成假设性问题的功能

    
 functions = [
    
     {
    
     "name": "hypothetical_questions",
    
     "description": "Generate hypothetical questions",
    
     "parameters": {
    
         "type": "object",
    
         "properties": {
    
             "questions": {
    
                 "type": "array",
    
                 "items": {"type": "string"},
    
             },
    
         },
    
         "required": ["questions"],
    
     },
    
     }
    
 ]
    
  
    
 # 创建一个链来生成假设性问题
    
 chain = (
    
     {"doc": lambda x: x.page_content}
    
     | ChatPromptTemplate.from_template(
    
     "Generate a list of exactly 3 hypothetical questions that the below document could be used to answer:\n\n{doc}"
    
     )
    
     | ChatOpenAI(max_retries=0, model="gpt-4").bind(
    
     functions=functions, function_call={"name": "hypothetical_questions"}
    
     )
    
     | JsonKeyOutputFunctionsParser(key_name="questions")
    
 )
    
  
    
 # 为每个文档生成假设性问题
    
 hypothetical_questions = chain.batch(docs, {"max_concurrency": 5})
    
  
    
 # 接下来,我们可以将这些假设性问题存储到向量存储中,并使用MultiVectorRetriever进行检索
    
 # 创建向量存储以索引子块
    
 vectorstore = Chroma(
    
     collection_name="hypo-questions", embedding_function=OpenAIEmbeddings()
    
 )
    
 # 创建存储层以存储父文档
    
 store = InMemoryByteStore()
    
 id_key = "doc_id"
    
 # 初始化MultiVectorRetriever
    
 retriever = MultiVectorRetriever(
    
     vectorstore=vectorstore,
    
     byte_store=store,
    
     id_key=id_key,
    
 )
    
 doc_ids = [str(uuid.uuid4()) for _ in range(len(docs))]
    
  
    
 # 为每个问题创建Document对象
    
 question_docs = []
    
 for i, question_list in enumerate(hypothetical_questions):
    
     for question in question_list:
    
     question_doc = Document(page_content=question, metadata={id_key: doc_ids[i]})
    
     question_docs.append(question_doc)
    
  
    
 # 将问题文档及其向量添加到向量存储
    
 vectorstore.add_documents(question_docs)
    
 store.mset(list(zip(doc_ids, docs)))
    
  
    
 # 使用MultiVectorRetriever来检索与查询相关的问题
    
 # 例如,搜索包含"justice"的问题
    
 search_query = "justice"
    
 retriever.search_type = SearchType.similarity_search  # 可以选择使用相似性搜索或MMR搜索
    
 relevant_questions = retriever.get_relevant_documents(search_query)
    
  
    
 # 输出检索到的问题
    
 for question_doc in relevant_questions:
    
     print(question_doc.page_content)

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

首先, 利用ChatOpenAI与JsonKeyOutputFunctionsParser工具, 为每一个文档生成预设的假设性查询.
随后, 构建基于Chroma向量检索层与InMemoryByteStore缓存层的技术架构, 以组织并存储这些问题及其相关元数据.
接下来, 配置MultiVectorRetriever索引, 并为每一个检索结果建立相应的Document对象, 将其整合到向量数据库中.
最终, 通过调用retriever.get_relevant_documents方法实现精确检索特定关键词(如'justice')的相关信息. 这一过程将返回一个经过整理的文档列表, 其中每一个文档都包含完整的文本内容以及附加的信息元数据.

未完待续

全部评论 (0)

还没有任何评论哟~