在回顾[对LangChain的全面分析]时,我决定更深入地研究Retriever。于是,我决定仔细研究官方文档中的MultiVectorRetriever示例。
多向量检索器 | 🦜️🔗 LangChain | 🦜️🔗 LangChain通常情况下,为每个文档存储多个向量是有好处的,在这种情况下,有多种应用场景需要这样做……python.langchain.com
粗略一看,我并没有太懂。我有点懵。首先,我搞不懂这段代码最后要干啥。
# 检索工具返回更大的块
len(retriever.invoke("justice breyer")[0].page_content)
思考了一会儿,阅读了代码后,我突然恍然大悟。看这部分代码,哦,我明白了。
运行这行代码来获取布莱尔大法官的信息:retriever.invoke("justice breyer")[0]
我突然明白,在 invoke()
方法中,它不仅使用向量存储进行相似性搜索,而且还在文档中查找具有相同 id 的内容。这时一切都豁然开朗了。
在这份官方文档中间提到:
有时获取较大的信息片段是有用的,但嵌入较小的信息片段。这能让嵌入尽可能精确地捕捉语义,同时也尽可能多地传递上下文信息。
向量搜索,或称为相似度搜索,在使用更小的部分时会更加准确。我了解到这个工具MultiVectorRetriever 对更小的部分执行向量搜索,但以更大的部分检索原始文档数据。这就是它的操作方式!
当我回头查看这段代码,试图理解为什么它会这样工作时,我注意到在创建MultiVectorRetriever对象时,他们设定了id_key="doc_id"参数。这个细节非常微妙,很容易被忽视。
id_key = "doc_id" # 文档的ID键
// 检索器一开始为空
retriever = MultiVectorRetriever(
vectorstore=vectorstore,
byte_store=store,
id_key=id_key,
)
在以下代码中,docs进一步细分为sub_docs,但这些sub_docs使用名为id_key=”doc_id”的元数据被分配了与其父doc相同的id。这种设置允许Retriever使用向量存储在sub_doc级别上执行精确的相似性搜索,然后使用匹配sub_docs的“doc_id”检索包含更广泛上下文信息的docs。
# 用于将文本分割成更小块的拆分器,chunk_size 为 400
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))) # 将文档 ID 和文档列表进行多集合设置,用于更新文档存储
这里,doc.metadata[id_key] = _id 设置了 sub_doc 的元数据,其键名为 id_key(即 “doc_id”)。因为之前在 MultiVectorRetriever 的构造函数中指定了 ‘doc_id’,检索器可以利用它来将 sub_doc 和 doc 联系起来。因此,通过调用检索器,MultiVectorRetriever 可以在向量存储中执行相似度搜索,找到匹配的 sub_doc 后,利用它们的 ‘doc_id’ 在 byte_store 中查找具有相同 id 的 doc 并返回它们。
// 检索器调用“金斯布雷尔”
retriever.invoke("金斯布雷尔")
有了这个核心概念后,当我再次查看样本时,一切变得清晰多了。下面从头开始回顾一下当初让我感到困惑的部分。
加载器们 = [
TextLoader("../../paul_graham_essay.txt"),
TextLoader("../../state_of_the_union.txt"),
]
文档列表 = []
for 加载器 in 加载器们:
文档列表.extend(加载器.load_docs())
这两个 txt 文件,paul_graham_essay.txt 和 state_of_the_union.txt,通过调用 docs.extend(loader.load()) 被合并到一个单一的 docs 列表中。(它们不是以文件为界限的嵌套列表。)
接下来,使用RecursiveCharacterTextSplitter将这个文档列表进一步分割成最多10000字符的块,并将这些块以列表形式存储在docs中。
# 文本分割器,用于将长文本分割成较小的块
text_splitter = RecursiveCharacterTextSplitter(块大小=10000)
# 将文档列表中的每个文档分割成多个较小的文档
文档列表 = text_splitter.split_documents(文档列表)
每个在此文档列表中的项目都是一个doc。对于每个doc,都会为其生成一个UUID,并将其存储在doc_ids列表中。
doc_ids = [str(uuid.uuid4()) for _ in docs]
# 用于将文本分割成更小块的拆分器,块大小为400
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)
接下来,每个文档会被细分为最多400个字符的片段。因为每个文档最初已分割成最多10000个字符的部分,这进一步将其分成大约25个部分,生成子文档。在将这些添加到子文档之前,每个子文档(暂时存储在代码中的变量_doc中)会将其元数据名称设置为“doc_id”,包含其原始文档(父文档)的ID。(换句话说,多个子文档可以共享同一个doc_id。)
sub_docs 被添加到向量存储库中,而文档则被添加到文档库中。
retriever.vectorstore.add_documents(sub_docs) # 添加子文档到向量存储
retriever.docstore.mset(list(zip(doc_ids, docs))) # 设置文档ID和文档列表
文档存储通过在创建检索器对象时传递给byte_store参数的InMemoryByteStore对象来处理。
这设置了MultiVectorRetriever来进行搜索,允许我们在向量数据库中搜索相似的子文档。
# 向量存储仅检索小段文本:
retriever.vectorstore.similarity_search("justice breyer")[0]
或者,如前面所说,使用 invoke() 来执行对 sub_docs -> docs 的协调查找:
# 检索插件返回更大的段落
len(retriever.invoke("justice breyer")[0].page_content)
虽然LangChain的官方示例因为解释不够详细可能显得不够友好,但代码本身其实很友好,显示出友好之意,透露出一种传达意图的愿望。
待续。
感谢您细读这份关于MultiVector Retriever示例代码的详细分析。希望这份解析能帮助您更好地理解,并能帮助您更清晰地理解LangChain中一些复杂的功能。
虽然这次我们深入探讨了MultiVector Retriever,但人工智能和语言技术的探索是一个持续的探索之旅。在这一快速发展的领域里,总有更多东西等着我们去学习和发现。
如果你对这篇博客有任何疑问,或者想讨论关于OpenAI API、LLM或LangChain的开发项目,欢迎直接联系我。你可以直接联系我:
联系人邮箱:mizutori@goldrushcomputing.com
在 Goldrush Computing,我们自豪于作为一家日本公司并拥有母语为日语的专家。我们擅长开发针对日语和日本文化的提示和RAG系统。如果您需要为日本市场优化AI解决方案或开发日语应用程序,我们能为您提供独特的帮助。对于需要特定日本市场AI优化的合作或项目,欢迎随时联系我们。
敬请关注我即将发布的新博客,关于AI和大型语言模型!
继续阅读这个系列的下一部分:
LLM学习日记:解码LangChain官方的多模态RAG示例 LangChain官方示例里有一个很好的多模态RAG案例,所以我这次决定仔细研究一下,从头到尾……medium.com](https://medium.com/llm-study-diary-a-beginners-path-through-ai/llm-study-diary-decoding-langchains-official-multimodal-rag-sample-b4a645bdabe2?source=post_page-----e6d8360d10e0--------------------------------)