在 LangChain 尝试了 N 种可能后,我发现了分块的奥义!
分块(Chunking)是构建检索增强型生成(RAG)应用程序中最具挑战性的问题。分块是指切分文本的过程,虽然听起来非常简单,但要处理的细节问题不少。根据文本内容的类型,需要采用不同的分块策略。
在本教程中,我们将针对同一个文本采用不同的分块策略,探索不同分块策略的效果。访问链接获取本文中涉及的代码。
01.LangChain 分块简介
LangChain 是一个 LLM 协调框架,内置了一些用于分块以及加载文档的工具。本次分块教程主要围绕设置分块参数,并最小限度地使用 LLM。简而言之,通过编写一个函数并设置其参数来加载文档并对文档进行分块,该函数打印结果为分块后的文本块。在下述实验中,我们会在这个函数中运行多个参数值。
LangChain 分块代码导入和设置
代码第一部分主要是导入和设置工具。下面代码有很多导入语句,os
和dotenv
都比较常用。它们仅用于环境变量。
接下来,我们深入讲解一下有关 LangChain 和 pymilvus 部分的代码。
首先是用于获取文档的三个导入:
NotionDirectoryLoader
用于加载含有 markdown/Notion 文档的目录。然后,MarkdownHeader 和 RecursiveCharacter 文本分割器会根据标题(标题分割器)或一组预先选定的字符分隔符(递归分割器)分割 markdown 文档中的文本。
接下来,是检索器导入。我们用 Milvus 、OpenAIEmbeddings 模型和 OpenAI 大语言模型(LLM)。SelfQueryRetriever 是 LangChain 原生检索器,允许向量数据库“查询自身”。
最后一个 LangChain 导入是AttributeInfo
,它将一个带有信息的属性传入 SelfQueryRetriever。
至于 pymilvus
导入,通常我只将这些导入在结束时用于清理数据库。
编写函数之前的最后一步是加载环境变量并声明一些常量。headers_to_split_on
变量列出了我们希望在 markdown 中分割的所有标题;path
用于帮助 LangChain 了解在哪里找到 Notion 文档。
import osfrom langchain.document_loaders import NotionDirectoryLoader from langchain.text_splitter import MarkdownHeaderTextSplitter, RecursiveCharacterTextSplitter from langchain.vectorstores import Milvus from langchain.embeddings import OpenAIEmbeddings from langchain.llms import OpenAI from langchain.retrievers.self_query.base import SelfQueryRetriever from langchain.chains.query_constructor.base import AttributeInfo from pymilvus import connections, utility from dotenv import load_dotenv load_dotenv() zilliz_uri = os.getenv("ZILLIZ_CLUSTER_01_URI") zilliz_token = os.getenv("ZILLIZ_CLUSTER_01_TOKEN") headers_to_split_on = [ ("##", "Section"), ] path='./notion_docs'
构建一个分块实验函数
构建分块实验函数是本教程中最关键的部分。如前所述,此函数需要一些参数用于档导入和分块。我们需要提供文档的路径、要分割的标题(分割器)、分块大小、分块重叠(chunk overlap)以及我们是否希望通过删除 Collection 来清理数据库。默认情况下,将该参数设置为 True,即删除 Collection 清理数据库。
注意,要尽可能少地创建和删除 Collection,从而避免不必要的开销。
函数第一部分通过 Notion 目录加载器(Notion Directory Loader)从路径加载文档,此处只抓取第一页的内容。
接下来,获取分割器。首先,使用 markdown 分割器根据上面传入的标题进行分割。然后,用递归分割器根据分块大小和 overlap 来分割。
分割完成后,使用环境变量、OpenAI embedding、分块工具以及 Collection名 称初始化一个 LangChain Milvus 实例。此外,我们还通过 AttributeInfo
对象创建了一个元数据字段列表,帮助 SelfQueryRetriever 了解文本块所属的“章节”。
完成所有上述设置后,获取 LLM 并将其传递给 SelfQueryRetriever。当我们针对文档提出问题时,检索器开始发挥作用。我还设置了函数从而了解其正在测试哪种分块策略。最后,可以按需删除 Collection。
def test_langchain_chunking(docs_path, splitters, chunk_size, chunk_overlap, drop_collection=True): path=docs_path loader = NotionDirectoryLoader(path) docs = loader.load() md_file=docs[0].page_content # Let's create groups based on the section headers in our page markdown_splitter = MarkdownHeaderTextSplitter(headers_to_split_on=splitters) md_header_splits = markdown_splitter.split_text(md_file) # Define our text splitter text_splitter = RecursiveCharacterTextSplitter(chunk_size=chunk_size, chunk_overlap=chunk_overlap) all_splits = text_splitter.split_documents(md_header_splits) test_collection_name = f"EngineeringNotionDoc_{chunk_size}_{chunk_overlap}" vectordb = Milvus.from_documents(documents=all_splits, embedding=OpenAIEmbeddings(), connection_args={"uri": zilliz_uri, "token": zilliz_token}, collection_name=test_collection_name) metadata_fields_info = [ AttributeInfo( name="Section", description="Part of the document that the text comes from", type="string or list[string]" ), ] document_content_description = "Major sections of the document" llm = OpenAI(temperature=0) retriever = SelfQueryRetriever.from_llm(llm, vectordb, document_content_description, metadata_fields_info, verbose=True) res = retriever.get_relevant_documents("What makes a distinguished engineer?") print(f"""Responses from chunking strategy: {chunk_size}, {chunk_overlap}""") for doc in res: print(doc) # this is just for rough cleanup, we can improve this# lots of user considerations to understand for real experimentation use cases thoughif drop_collection: connections.connect(uri=zilliz_uri, token=zilliz_token) utility.drop_collection(test_collection_name)
02.LangChain 分块实验和结果
接下来就是激动人心的时刻了!让我们来看看分块实验的结果。
测试 LangChain 分块
以下代码块展示了如何运行我们的实验函数。我添加了五个实验,这个教程测试的分块长度从 32 到 64、128、256、512 不等,分块 overlap 从 4 到 8、16、32、64 不等的分块策略。为了测试,我们遍历元组列表并调用上面写的函数。
chunking_tests = [(32, 4), (64, 8), (128, 16), (256, 32), (512, 64)]for test in chunking_tests: test_langchain_chunking(path, headers_to_split_on, test[0], test[1])
以下为输出结果。接着让我们来仔细观察每一组实验的输出结果。我们使用的测试问题是“What makes a distinguished engineer?”
分块长度 32,重叠 4
显而易见,32 的长度太短了,这种分块策略完全无效。
分块长度 64,重叠 8
这种策略一开始效果也不理想,但最终也给出了问题的答案—— Werner Vogels,亚马逊(Amazon)首席技术官(CTO)。
分块长度 128,重叠 16
长度变为 128 时,答案出现了更多完整句,更少“工程师”类型的回答。这个策略的效果还不错,能够提取出 Werner Vogel 相关文本片段。但是这个策略的一个劣势是答案中会出现 \xa0
和 \n
这种特殊字符。也许我们分块长度过长了。
分块长度 256,重叠 32
虽然答案会返回相关内容,但这个分块长度过长。
分块长度 512,重叠 64
已知 256 的分块长度已经过长了。但是将长度设置为 512 时,会提取出整个 section 的内容。这时候就要思考:我们到底是想要结果中返回单独的一行文字,还是整个 section 内容?这就需要根据使用场景进行判断。
03.总结
本教程探索了 5 种不同分块策略的效果。选择分块策略时,我们要根据期望获得的返回结果来确定最合适的分块长度,后续我们将测试不同分块 overlap 的效果。敬请期待!
-
如果在使用 Milvus 或 Zilliz 产品有任何问题,可添加小助手微信 “zilliz-tech” 加入交流群。
-
欢迎关注微信公众号“Zilliz”,了解最新资讯。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
聊聊如何在Java应用中发送短信
很多业务场景里,我们都需要发送短信,比如登陆验证码、告警、营销通知、节日祝福等等。 这篇文章,我们聊聊 Java 应用中如何优雅的发送短信。 1 客户端/服务端两种模式 Java 应用中发送短信通常需要使用短信服务提供商提供的短信 API 。 我们经常使用的短信渠道有:阿里云、腾讯云、华为云、亿美等。 发送短信模式分为两种: 1、客户端模式 客户端模式是指应用系统直接调用短信服务提供商提供的短信 API 发送短信。 2、服务端模式 服务端模式是独立创建一个短信平台服务,应用系统直接使用短信平台服务提供的 SDK 发送短信。 核心流程如下: 前端调用应用服务接口发送短信 ; 应用服务收到短信请求后,调用 SDK 方法根据模版发送短信; 短信平台服务收到请求,根据路由算法选择配置的渠道(比如阿里云、腾讯云)发送短信; 短信成功发送到用户手机 。 2 客户端模式 1、使用三方短信渠道 SDK 客户端模式是非常简单的模式,很多短信服务提供商会提供成熟的 SDK ,业务系统只需要添加 SDK 依赖以及相关配置,就可以调用 SDK 提供的方法发送短信。 我们以阿里云短信服务为例, 调用 API 发...
- 下一篇
HelloGitHub 社区动态,开启新的篇章!
今天这篇文章是 HelloGitHub 社区动态的第一篇文章,所以我想多说两句,聊聊为啥开启这个系列。 我是 2016 年创建的 HelloGitHub,它从最初的一份分享开源项目的月刊,现如今已经成长为 7w+ Star 的开源项目、1w+ 用户的开源社区、全网 50w+ 的自媒体。 我本是一名普通的程序员,三流的技术水平、毫无文笔、开源门外汉,起初连 Git 都不会,也不知道什么是开源,就一个猛子扎进来做了 HelloGitHub。为了想让更多人看到 HelloGitHub 月刊,稀里糊涂地就做起了“自媒体”。我为了圆自己的站长梦,饿着肚子咬牙重构了 HelloGitHub.com 网站,从最初的 Web 1.0 的月刊展示,升级到了 Web 2.0 的开源社区。 聪明的人追着风口跑,很容易就赚到钱了。像我这种愚笨的人,只做「分享开源项目」这一件事情,就花了 7 年的事情,钱没赚到人还瘦了两圈😂。 有人说我不会玩流量,确实我不会,因为在我眼里每一次点击、每一个阅读、每一位粉丝背后都是我的一位朋友。说起来真是惭愧,就是我和朋友们的沟通太少了,因为我总想一个人、一台电脑、一把键盘,做...
相关文章
文章评论
共有0条评论来说两句吧...