如何使用多向量检索器在单个文档中实现多向量检索

老铁们,今天我们来聊聊关于如何在一个文档中存储多个向量的问题。这技术点其实不难,但应用起来却相当有用。比如说,我们可以将一个文档拆分成多个小块,然后将这些小块的嵌入与父文档相关联,这样一来,当我们在小块中进行检索命中时,就能返回整个大文档。这对提高检索精度和信息完整性都大有裨益。

LangChain 实现了一个名为 baseMultiVectorRetriever 的基础多向量检索器,可以大大简化此过程。大部分难点在于如何为每个文档创建多个向量。接下来,我会为大家展示一些创建这些向量的常用方法以及如何使用 MultiVectorRetriever

方法解析

  1. 更小的块: 可以将文档拆分成更小的块,并对这些内容进行嵌入。这样做可以让嵌入捕捉到最接近的语义意义,同时尽可能多传递上下文。
  2. 摘要: 为每个文档创建一个摘要,并对其进行嵌入,与文档一起(或代替文档)使用。
  3. 假设性问题: 创建每个文档适合回答的假设性问题,对这些问题进行嵌入,与文档一起(或代替文档)使用。这样做的好处是可以手动添加一些问题或查询,提高检索的相关性和精确性。

我先前踩过这个坑,发现手动添加问题能大幅提高命中率,给大家推荐一下这种方法。

实战代码演示

首先,我们需要实例化一些文档。接着,我们会在一个(内存中)Chroma 向量存储中索引这些文档使用 OpenAI 嵌入,不过任何 LangChain 的向量存储或嵌入模型都可以。

%pip install --upgrade --quiet langchain-chroma langchain langchain-openai > /dev/null

from langchain.storage import InMemoryByteStore
from langchain_chroma import Chroma
from langchain_community.document_loaders import TextLoader
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter

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)

# 使用的向量存储
vectorstore = Chroma(
    collection_name="full_documents", embedding_function=OpenAIEmbeddings()
)

创建子文档和向量存储

对于每个父文档,我们可以将其拆分为更小的子文档,然后存储这些子文档的索引:

import uuid
from langchain.retrievers.multi_vector import MultiVectorRetriever

store = InMemoryByteStore()
id_key = "doc_id"
retriever = MultiVectorRetriever(
    vectorstore=vectorstore,
    byte_store=store,
    id_key=id_key,
)

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)))

优化建议

说到提高稳定性,我们可以使用代理服务来提高连接可靠性和速度。这样一来,整个操作就相当丝滑了。

使用假设性问题提高检索效果

通过生成一套假设性问题,可以进一步提升我们的检索效果。以下是一个简单的例子:

from typing import List
from langchain_core.pydantic_v1 import BaseModel, Field

class HypotheticalQuestions(BaseModel):
    """Generate hypothetical questions."""

    questions: List[str] = Field(..., description="List of 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-4o").with_structured_output(
        HypotheticalQuestions
    )
    | (lambda x: x.questions)
)

# 批量处理文档
hypothetical_questions = chain.batch(docs, {"max_concurrency": 5})

总结

通过这些方式,我们可以有效地使用多向量检索器进行文档检索,提高精确度和灵活性。今天的技术分享就到这里,希望对大家有帮助。开发过程中遇到问题也可以在评论区交流~

—END—

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值