概要
在处理大型文档集合时,为了提高检索效率和准确性,通常需要将每个文档分割成更小的块,并为每个块创建单独的向量。LangChain 提供了 MultiVectorRetriever 工具来帮助我们实现这一目标。本文将详细介绍如何使用LangChain来创建多向量检索器,并通过具体示例展示其实现过程。
技术名词解释
多向量检索器概述
多向量检索器(MultiVectorRetriever)允许多个向量对应于同一个文档。这对于提高检索的精度非常有帮助,特别是在处理长文档时。LangChain 提供了几种常见的方法来创建多个向量:
较小的块:将文档分割成较小的块,然后嵌入这些块。
摘要:为每个文档创建摘要,并将其与文档一起嵌入。
假设性问题:创建每个文档都适合回答的假设性问题,并将这些问题与文档一起嵌入。
技术细节
创建多向量检索器
准备文档
首先,我们需要加载文档。在这个例子中,我们将加载两个文本文件。
from langchain_community.document_loaders import TextLoader
loaders = [
TextLoader("./txt/faq-4359.txt", encoding="utf-8"),
TextLoader("./txt/faq-7923.txt", encoding="utf-8"),
]
docs = []
for loader in loaders:
docs.extend(loader.load())
print(docs)
分割文档
接下来,我们将使用 CharacterTextSplitter 来将文档分割成较小的块。
from langchain_text_splitters import CharacterTextSplitter
child_text_splitter = CharacterTextSplitter(
separator="\n\n",
chunk_size=100,
chunk_overlap=10,
length_function=len,
is_separator_regex=False,
)
sub_docs = []
for i, doc in enumerate(docs):
_id = str(uuid.uuid4())
_sub_docs = child_text_splitter.split_documents([doc])
for _doc in _sub_docs:
_doc.metadata["doc_id"] = _id
sub_docs.extend(_sub_docs)
print(sub_docs)
创建向量存储
为了存储这些分割后的文档块,我们需要创建一个向量存储。
from langchain_community.embeddings.huggingface import HuggingFaceEmbeddings
from langchain_community.vectorstores import Chroma
embeddings_path = "D:\\ai\\download\\bge-large-zh-v1.5"
embeddings = HuggingFaceEmbeddings(model_name=embeddings_path)
vectorstore = Chroma(
collection_name="full_documents", embedding_function=embeddings
)
初始化检索器
现在,我们可以初始化一个多向量检索器。
from langchain.storage import InMemoryByteStore
from langchain.retrievers.multi_vector import MultiVectorRetriever
store = InMemoryByteStore()
id_key = "doc_id"
retriever = MultiVectorRetriever(
vectorstore=vectorstore,
byte_store=store,
id_key=id_key,
)
添加文档到检索器
我们需要将分割后的文档块添加到向量存储中,并设置文档ID与文档内容之间的映射。
from langchain.storage import InMemoryByteStore
from langchain.retrievers.multi_vector import MultiVectorRetriever
store = InMemoryByteStore()
id_key = "doc_id"
retriever = MultiVectorRetriever(
vectorstore=vectorstore,
byte_store=store,
id_key=id_key,
)
添加文档到检索器
我们需要将分割后的文档块添加到向量存储中,并设置文档ID与文档内容之间的映射。
retriever.vectorstore.add_documents(sub_docs)
retriever.docstore.mset(list(zip(doc_ids, docs)))
检索文档
现在,我们可以使用检索器来检索文档块。
retriever.vectorstore.similarity_search("众测商品多久发货呢?")[0]
使用摘要作为向量
另一种方法是使用文档的摘要作为向量。这有助于更精确地提炼出文档的主要内容。
创建摘要
我们可以使用一个链式请求来创建文档的摘要。
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
openai_api_key = "EMPTY"
openai_api_base = "http://127.0.0.1:1234/v1"
model = ChatOpenAI(
openai_api_key=openai_api_key,
openai_api_base=openai_api_base,
temperature=0.3,
)
chain = (
{"doc": lambda x: x.page_content}
| ChatPromptTemplate.from_template("总结下面的文档:\n\n{doc}")
| model
| StrOutputParser()
)
summaries = chain.batch(docs, {"max_concurrency": 5})
print(summaries)
将摘要嵌入到向量存储中
接下来,我们将这些摘要嵌入到向量存储中。
summary_docs = [
Document(page_content=s, metadata={id_key: doc_ids[i]})
for i, s in enumerate(summaries)
]
retriever.vectorstore.add_documents(summary_docs)
retriever.docstore.mset(list(zip(doc_ids, docs)))
retriever.vectorstore.similarity_search("众测活动是否有参与限制?")
使用假设性问题作为向量
另一种方法是生成针对特定文档可能提出的假设问题列表,并将这些问题嵌入到向量存储中。
生成假设性问题
我们可以使用一个链式请求来生成假设性问题。
[
{{“question”:“问题1”,“answer”:“回答1”}},
{{“question”:“问题2”,“answer”:“回答2”}},
{{“question”:“问题3”,“answer”:“回答3”}}
]
'''
prompt = ChatPromptTemplate.from_template(promptStr)
chain = (
{"doc": lambda x: x.page_content}
| prompt
| model
| JsonOutputParser()
)
hypothetical_questions = chain.batch(sub_docs, {"max_concurrency": 5})
print(hypothetical_questions)
将假设性问题嵌入到向量存储中
最后,我们将这些假设性问题嵌入到向量存储中。
documents = []
for item in hypothetical_questions:
for obj in item:
content = "问:{}\n答:{}".format(obj['question'], obj['answer'])
documents.append(Document(page_content=content))
vectorstore = Chroma(
collection_name="Question", embedding_function=embeddings, persist_directory="./vector_store"
)
retriever.vectorstore.add_documents(documents)
retriever.vectorstore.similarity_search("众测商品多久发货呢?")[0]
小结
通过本文,我们学习了如何在LangChain中使用多向量检索器来提高文档检索的精度。无论是通过分割文档成较小的块,还是通过生成摘要或假设性问题,多向量检索器都能帮助我们更好地管理大量的文档数据。希望这篇博客能为你的项目提供有用的参考。