老铁们,今天我们来聊聊关于如何在一个文档中存储多个向量的问题。这技术点其实不难,但应用起来却相当有用。比如说,我们可以将一个文档拆分成多个小块,然后将这些小块的嵌入与父文档相关联,这样一来,当我们在小块中进行检索命中时,就能返回整个大文档。这对提高检索精度和信息完整性都大有裨益。
LangChain 实现了一个名为 baseMultiVectorRetriever
的基础多向量检索器,可以大大简化此过程。大部分难点在于如何为每个文档创建多个向量。接下来,我会为大家展示一些创建这些向量的常用方法以及如何使用 MultiVectorRetriever
。
方法解析
- 更小的块: 可以将文档拆分成更小的块,并对这些内容进行嵌入。这样做可以让嵌入捕捉到最接近的语义意义,同时尽可能多传递上下文。
- 摘要: 为每个文档创建一个摘要,并对其进行嵌入,与文档一起(或代替文档)使用。
- 假设性问题: 创建每个文档适合回答的假设性问题,对这些问题进行嵌入,与文档一起(或代替文档)使用。这样做的好处是可以手动添加一些问题或查询,提高检索的相关性和精确性。
我先前踩过这个坑,发现手动添加问题能大幅提高命中率,给大家推荐一下这种方法。
实战代码演示
首先,我们需要实例化一些文档。接着,我们会在一个(内存中)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—