在LangChain中实现多向量检索器

概要

在处理大型文档集合时,为了提高检索效率和准确性,通常需要将每个文档分割成更小的块,并为每个块创建单独的向量。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中使用多向量检索器来提高文档检索的精度。无论是通过分割文档成较小的块,还是通过生成摘要或假设性问题,多向量检索器都能帮助我们更好地管理大量的文档数据。希望这篇博客能为你的项目提供有用的参考。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值