
如果您正在使用大型语言模型 (LLM) 构建人工智能应用程序,那么使用您的特定数据来校准生成的文本响应对于获得准确的答案至关重要。 检索增强生成 (RAG) 将大型语言模型连接到外部知识源,例如 向量数据库。 这使得它能够在创建响应之前找到相关的事实。 您的检索过程的质量是影响应用程序性能的最重要因素之一。 许多开发人员专注于选择合适的向量数据库或嵌入模型。 但通常最重要的一步是 您如何准备数据本身。
这就是 分块 发挥作用的地方。
在这篇文章中,我们将回顾一些基本的分块策略,从基础知识到高级技术,它们的权衡以及为您的 RAG 应用程序选择正确方法的技巧。
什么是分块?
简单来说, 分块 是将大型文档分解为更小、更易于管理的部分,称为块的过程。 这是在为大型语言模型 (LLM) 使用准备数据时至关重要的一步。
主要原因是 LLM 具有有限的 上下文窗口,这意味着它们一次只能关注一定量的文本。 如果上下文窗口内的文本过多,重要的细节将会丢失,从而导致不完整或不准确的答案。
分块通过创建更小、更集中的内容片段来解决这个问题,LLM 可以使用这些片段来回答用户的查询,而不会迷失在无关的信息中。
每个块的大小、内容和语义边界会影响检索性能,因此决定使用哪种技术会对您的 RAG 系统的性能产生巨大的影响。
为什么分块对 RAG 如此重要?
分块可以说是 RAG 性能最重要的因素。 您分割文档的方式会影响您的系统查找相关信息并给出准确答案的能力。 当 RAG 系统性能不佳时,问题通常不在于检索器——在于块。 即使是完美的检索系统,如果它在准备不充分的数据上进行搜索,也会失败。
这产生了一个根本性的挑战:您的块需要易于向量搜索查找,同时还要为 LLM 提供足够的上下文以创建有用的答案。
1. 优化检索准确性
第一步是确保您的系统能够在向量数据库中找到正确的信息。 向量搜索通过比较用户查询与块的嵌入向量来做到这一点。
- 以下是块太大的问题:它们通常将多个想法混合在一起,子主题可能会丢失或混淆。 想象一下试图通过平均书籍的所有章节来描述一本书。 这会创建一个嘈杂的“平均”嵌入向量,不能清楚地代表任何单个主题,使得向量检索步骤难以找到所有相关上下文。
- 小而集中的块可以捕捉一个清晰的想法。 这会产生一个精确的嵌入向量,可以编码内容的细微差别。 这使得您的系统更容易找到正确的信息。
2. 保持生成上下文
在您的系统找到最佳块之后,它们会传递给 LLM。 这时上下文质量决定了输出响应的质量。
这是一个简单的测试:如果单独阅读一个块对您来说有意义,那么它对 LLM 也有意义。
- 块太小无法通过此测试。 想象一下阅读研究论文中间的一句话——即使是人类在没有更多上下文的情况下也会难以理解正在发生什么。
- 块太大会产生不同的问题。 由于注意力分散和“迷失在中间”效应,LLM 的性能会随着更长的上下文输入而下降,在这种效应中,模型难以访问埋藏在长上下文中间的信息,而仍然可以合理地处理开头和结尾。 随着上下文长度的增加,模型的注意力会过度分散到所有输入上,使其更难找到相关信息,导致推理中出现更多错误,并增加产生幻觉响应的可能性。
分块的甜蜜点
您希望在创建足够小以便进行精确检索但又足够完整以向 LLM 提供完整上下文的块时,保留作者的“思维链条”。 这属于上下文工程:以 LLM 可以理解并生成准确响应的方式准备输入。
当您达到这种平衡时,以下几点会得到改善
- 提高检索质量: 通过创建集中、语义完整的块,您可以使检索系统能够精确地定位查询的最相关上下文。
- 管理 LLM 的上下文窗口: 有效的分块确保只有相关数据传递给 LLM,有助于避免过长的上下文长度,这可能会使模型感到困惑
- 减少幻觉: 通过向模型提供小而高度相关的块,您可以将响应置于事实数据中,并最大限度地减少模型虚构信息的风险。
- 提高效率并降低成本: 处理较小的块速度更快且计算效率更高,从而实现更快的响应时间和降低 LLM 使用成本。
如果您正在寻找一个动手 Python 教程,请查看 Weaviate Academy 中的这个单元:https://docs.weaviate.org.cn/academy/py/standalone/chunking
预分块与后分块
既然我们已经讨论了分块的基本困境,那么我们可以探讨在 RAG 管道中执行分块步骤的时间。 这一决定导致了两种主要策略:标准的预分块和更高级的替代方案,后分块。
预分块是最常用的方法。 它通过在嵌入和存储到向量数据库之前将文档分解为更小的部分来异步处理文档。 这种方法需要在预先确定块大小和边界,但由于所有块都是预先计算和索引的,因此可以在查询时实现快速检索。

后分块采取了不同的方法,首先嵌入整个文档,然后在查询时仅对实际检索到的文档执行分块。 可以缓存分块结果,因此随着经常访问的文档建立缓存块,系统会随着时间的推移而变得更快。 这种方法避免了分块可能永远不会被查询的文档,同时允许基于特定查询进行更动态、感知上下文的分块策略。 但是,它会引入首次访问时的延迟,并需要额外的基础设施决策。

我们将后分块策略构建到 Elysia 中,我们的开源代理 RAG 框架中。 您可以在这里了解更多信息。
分块策略
最佳分块策略取决于您正在处理的文档类型以及 RAG 应用程序的需求。 以下方法主要设计用于基于文本的文档。 对于其他格式,例如 PDF,需要额外的步骤将其转换为干净的文本。
处理 PDF
在分块 PDF 之前,您需要干净、结构化的文本。 PDF 是一种视觉格式,因此提取文本可能很棘手。 列、表格、标题或扫描页面可能会使文本提取不可靠。 对于扫描的文档,需要光学字符识别 (OCR) 才能获取任何文本。
小技巧:最可靠的方法是首先将 PDF 转换为结构化格式,例如 Markdown。 这一预处理步骤可确保您在应用以下任何分块策略之前拥有干净、逻辑有序的文本。
简单的分块技术
固定大小分块(或 Token 分块)
固定大小分块是最简单、最直接的方法。 它将文本分割成预定大小的块,通常以 token(模型处理的文本片段)或字符为单位。 这种方法易于实现,但不尊重文本的语义结构。 因此,它可能会在句子甚至单词的中间中断,从而导致尴尬的断裂。
一种常见的解决方案是块重叠,即将一个块末尾的一些 token 重复在下一个块的开头。这样可以保留在块边界处可能丢失的上下文。
关键注意事项
- 块大小: 一个常见的起点是与嵌入模型的上下文窗口对齐的块大小。较小的块可能更适合捕获细粒度细节,而较大的块可能更适合理解更广泛的主题。
- 块重叠: 典型的重叠量在块大小的 10% 到 20% 之间。
何时使用: 快速原型设计和了解 RAG 系统表现如何的基线。这是最容易开始的地方,尤其是在处理结构不一致的文档或不确定正在处理的内容时。只需确保使用合理的重叠量 - 10-20% - 以避免信息在块之间分割时丢失重要上下文。

代码示例
from typing import List
import re
# Split the text into units (words, in this case)
def word_splitter(source_text: str) -> List[str]:
source_text = re.sub("\s+", " ", source_text) # Replace multiple whitespces
return re.split("\s", source_text) # Split by single whitespace
def get_chunks_fixed_size_with_overlap(text: str, chunk_size: int, overlap_fraction: float = 0.2) -> List[str]:
text_words = word_splitter(text)
overlap_int = int(chunk_size * overlap_fraction)
chunks = []
for i in range(0, len(text_words), chunk_size):
chunk_words = text_words[max(i - overlap_int, 0): i + chunk_size]
chunk = " ".join(chunk_words)
chunks.append(chunk)
return chunks
递归分块
递归分块是一种更细致的方法。它使用优先级的常见分隔符列表来分割文本,例如双换行符(用于段落)或单换行符(用于句子)。它首先尝试使用最高优先级的分隔符(段落)分割文本。如果任何生成的块仍然太大,该算法会递归地将下一个分隔符(句子)应用于该特定块。
这种方法适应文档的结构,尽可能地将结构相关的单元保持在一起。它避免了固定大小分块的突然切割,并确保块保留其原始格式的结构。
推荐用于: 非结构化文本文档,例如文章、博客文章和研究论文。这通常是一个可靠的默认选择,因为它尊重文本的自然组织方式,而不是随机分割它。

代码示例
from typing import List
def recursive_chunking(text: str, max_chunk_size: int = 1000) -> List[str]
# Base case: if text is small enough, return as single chunk
if len(text) <= max_chunk_size:
return [text.strip()] if text.strip() else []
# Try separators in priority order
separators = ["\n\n", "\n", ". ", " "]
for separator in separators:
if separator in text:
parts = text.split(separator)
chunks = []
current_chunk = ""
for part in parts:
# Check if adding this part would exceed the limit
test_chunk = current_chunk + separator + part if current_chunk else part
if len(test_chunk) <= max_chunk_size:
current_chunk = test_chunk
else:
# Save current chunk and start new one
if current_chunk:
chunks.append(current_chunk.strip())
current_chunk = part
# Add the final chunk
if current_chunk:
chunks.append(current_chunk.strip())
# Recursively process any chunks that are still too large
final_chunks = []
for chunk in chunks:
if len(chunk) > max_chunk_size:
final_chunks.extend(recursive_chunking(chunk, max_chunk_size))
else:
final_chunks.append(chunk)
return [chunk for chunk in final_chunks if chunk]
# Fallback: split by character limit if no separators work
return [text[i:i + max_chunk_size] for i in range(0, len(text), max_chunk_size)]
基于文档的分块
基于文档的分块使用文档的固有结构。它不依赖于通用的分隔符,而是根据其格式特定的元素解析文档。例如
- Markdown: 通过标题(
#、##)分割以捕获章节或子章节。 - HTML: 通过标签(
<p>、<div>)分割以保留逻辑内容块。 - PDF: 在预处理(例如 OCR 或转换为 Markdown)之后,通过标题、段落、表格或其他结构元素分割。
- 编程代码: 通过函数或类(例如 Python 中的
def)分割以维护逻辑代码单元。
使用这种方法,块与文档的逻辑组织保持一致,这通常也与语义含义相关。LangChain 和 LlamaIndex 都提供针对各种文档类型(包括 Markdown、代码和 JSON)的专用分割器。
何时使用: 结构高度化的文档,其格式可以轻松定义逻辑分隔。非常适合 Markdown、HTML、源代码或任何具有清晰结构标记的文档。

代码示例
from typing import List
import re
def markdown_document_chunking(text: str) -> List[str]:
# Split by markdown headers (# ## ### etc.)
header_pattern = r'^#{1,6}\s+.+$'
lines = text.split('\n')
chunks = []
current_chunk = []
for line in lines:
# Check if this line is a header
if re.match(header_pattern, line, re.MULTILINE):
# Save previous chunk if it has content
if current_chunk:
chunk_text = '\n'.join(current_chunk).strip()
if chunk_text:
chunks.append(chunk_text)
# Start new chunk with this header
current_chunk = [line]
else:
# Add line to current chunk
current_chunk.append(line)
# Add final chunk
if current_chunk:
chunk_text = '\n'.join(current_chunk).strip()
if chunk_text:
chunks.append(chunk_text)
return chunks
高级分块技术
语义分块(上下文感知分块)
语义分块从传统的基于规则的分割转变为基于含义的分割。这种更高级的技术不依赖于字符计数或文档结构,而是根据其语义相似性来划分文本。该过程包括
- 句子分割: 将文本分解为单个句子
- 嵌入生成: 将每个句子转换为向量嵌入
- 相似性分析: 比较嵌入以检测语义断点(主题发生变化的地方)
- 块形成: 在这些断点之间创建新的块
结果是一组高度连贯的语义块,每个块包含一个自包含的想法或主题。这种方法适用于密集、非结构化文本,您希望保留论点或叙述的逻辑流程。
推荐用于: 密集、非结构化文本,以保留想法的完整语义上下文。这种方法非常适合学术论文、法律文件或长篇故事。这些文本并不总是使用清晰的分隔符(如段落)来显示主题变化。当您处理语义边界与文档结构不整齐的复杂内容时,这种方法非常有效。

基于 LLM 的分块
基于 LLM 的分块使用一个大型语言模型 (LLM) 来决定如何分割文本。它不依赖于固定的规则或基于向量的相似性分数,而是让 LLM 处理文档并生成语义上连贯的块,通常还会添加额外的上下文、摘要或其他信息。这可以通过以下方式完成
- 识别命题(将文本分解为清晰、逻辑的陈述)
- 总结章节为更小、保留含义的块
- 突出关键点以确保捕获最相关的信息
结果是一组块,比传统方法更准确地保留语义含义。这使得基于 LLM 的分块成为检索增强生成 (RAG) 最强大的策略之一。
何时使用: 高价值、复杂文档,其中检索质量至关重要且预算不太重要。非常适合法律合同、研究论文、合规文档或企业知识库。这种方法可以生成总结或突出显示关键思想的块,但它也有权衡。与其它分块技术相比,它是计算成本最高、最慢的方法。

Agentic 分块
Agentic 分块将基于 LLM 的分块概念更进一步。它不是应用一种方法,而是让 AI 代理动态决定如何分割您的文档。它会查看整个文档,包括其结构、密度和内容。然后,它决定使用哪种分块策略或策略组合。例如,代理可能会看到该文档是一个 Markdown 文件。然后它通过标题分割该文件。它还可能发现更密集的文档需要一种命题方法。它甚至可以为更高级的检索添加元数据标签到块中。
这些“LLM 驱动的方法”可以创建非常清晰和上下文丰富的块。但是,它们需要大量的计算能力并且成本更高。它们通常需要为每个文档向强大的模型发出许多调用。
何时使用: 高风险 RAG 系统,您需要尽可能最好的块并且成本不是决定性因素。非常适合需要针对每个文档的独特特征定制分块策略的情况。

延迟分块
延迟分块是一种略有不同的技术,旨在解决其它分块策略中的一个常见问题:上下文丢失。
在其它分块技术中,当您首先分割文档然后创建嵌入时,每个块都会被隔离。这可能会导致块中出现歧义或丢失的上下文,该上下文在文档的早期部分被解释或引用过。
延迟分块反向工作。它不是首先分割,而是首先将整个文档馈送到具有长上下文嵌入模型的嵌入模型中。这将创建详细的、token 级别的嵌入,从而理解全局情况。然后,您才将文档分割成块。
当您为每个块创建嵌入时,您将使用已经使用完整上下文创建的 token 嵌入。您只需对相关 token 嵌入进行平均即可。这意味着每个块都保留有关整个文档的上下文。
何时使用: 在 RAG 系统中,检索质量取决于理解块与整个文档之间的关系时使用此方法。这对于技术文档、研究论文或法律文本非常有用。这些文档的各个部分引用文档其它地方提到的想法、方法或定义。这有助于捕获常规分块方法遗漏的文档不同部分之间的联系。

分层分块
分层分块对于非常大和复杂的文档来说可能是一个改变游戏规则的方法。这个想法很简单:您在不同的细节级别创建多个块层级。
- 在顶层,您创建大型块,总结广泛的章节或主题,例如标题和摘要。
- 在下一层,您将这些章节分割成逐渐变小的块,以捕获更细致的细节,例如论点、示例或定义。
这让您的 RAG 系统可以从高级概述开始,然后在用户需要更多详细信息时深入研究具体内容。LlamaIndex 的 HierarchicalNodeParser 使您能够轻松实现这种方法。
何时使用: 非常大和复杂的文档,例如教科书、法律合同或广泛的技术手册。当您需要回答基于高级摘要的问题和高度具体、详细的查询时,这种策略非常理想。它在广泛的上下文和细粒度访问之间提供了一个很好的中间地带,虽然它比基本分割方法更复杂,但并不像分层分块那样复杂。

自适应分块
自适应分块技术会根据文档的内容动态调整关键参数(例如块大小和重叠)。
这种方法不为整个文档应用单个固定规则,而是将文本视为一个多变的景观。它可能会使用机器学习模型来分析不同部分的语义密度和结构。例如,它可以自动为复杂的、信息丰富的段落创建更小、更细粒度的块以捕获细粒度细节,同时为更通用、介绍性的部分使用更大的块。
目标是创建块大小和边界根据其包含的特定内容量身定制,从而实现更精确和上下文感知的检索。这不同于 Agentic 分块,在 Agentic 分块中,代理决定使用哪种分块策略,而不是仅仅调整单个参数。
何时使用: 具有多样且不一致的内部结构的文档。想想一份包含密集、技术段落以及稀疏、叙事段落的长篇报告。自适应策略在这里表现出色,因为它避免了“一刀切”的问题。它可以为复杂的片段创建小的、细粒度的块以捕获每个细节,并为更简单的文本使用更大的块以保留上下文,所有这些都在同一文档中。
如何选择最佳分块策略
没有一种“最佳”的分块方法;最佳策略始终取决于您的具体用例。但在深入研究不同的技术之前,最重要的问题是:
“我的数据是否需要分块?”
分块旨在分解长而非结构化的文档。如果您的数据源已经包含小的、完整的信息片段,例如常见问题解答、产品描述或社交媒体帖子,通常不需要对其进行分块。分块甚至可能导致问题。目标是创建有意义的语义单元,如果您的数据已经处于这种格式,那么您就可以进入嵌入阶段了。
一旦您确认您的文档足够长,可以从分块中受益,您可以使用以下问题来指导您选择策略
- 我的文档的性质是什么? 它们是高度结构化的(例如代码或 JSON),还是非结构化的叙述性文本?
- 我的 RAG 系统需要多大的细节? 它需要检索特定的、细粒度的事实,还是总结更广泛的概念?
- 我正在使用哪个嵌入模型? 输出向量的大小是多少(更多的维度可以提高存储更细粒度信息的能力)?
- 我的用户查询会有多复杂? 它们是需要小的、有针对性的块的简单问题,还是需要更多上下文的复杂问题?
| 分块策略 | 工作原理 | 复杂度 | 最适合 | 示例 |
|---|---|---|---|---|
| 固定大小(或 Token) | 按 Token 或字符数分割。 | 低 | 小型或简单的文档,或者当速度最重要时 | 会议记录、简短的博客文章、电子邮件、简单的常见问题解答 |
| 递归 | 通过反复分割文本来拆分文本,直到它符合所需的块大小,通常会保留一些结构。 | 中 | 需要保持一些结构但速度仍然重要的文档 | 研究文章、产品指南、简短的报告 |
| 文档型 | 将每个文档视为单个块,或仅在文档边界处分割。 | 低 | 短小、独立的文档集合 | 新闻文章、客户支持工单、简短的合同 |
| 语义 | 在自然含义边界(主题、想法)处分割文本。 | 中-高 | 技术性、学术性或叙述性文档 | 科学论文、教科书、小说、白皮书 |
| 基于 LLM | 使用语言模型根据上下文、含义或任务需求来确定块边界。 | 高 | 复杂的文本,其中有意义的分块可以改善下游任务,例如摘要或问答 | 长篇报告、法律意见、医疗记录 |
| Agentic | 让 AI 代理根据含义和结构来决定如何分割。 | 非常高 | 需要自定义策略的复杂、细微的文档 | 监管备案、多节合同、公司政策 |
| 后期 | 首先嵌入整个文档,然后从中派生块嵌入。 | 高 | 需要了解完整文档上下文的块的用例 | 案例研究、综合手册、长篇分析报告 |
| 分层 | 将文本分解为多个级别(章节 → 段落 → 句子)。保持结构不变。 | 中 | 大型、结构化的文档,例如手册、报告或合同 | 员工手册、政府法规、软件文档 |
| 自适应 | 使用机器学习或启发式方法动态调整块大小和重叠。 | 高 | 具有不同结构和长度的混合数据集 | 来自多个来源的数据:博客、PDF、电子邮件、技术文档 |
| 代码 | 按逻辑代码块(函数、类、模块)分割,同时保留语法。 | 中 | 源代码、脚本或编程文档 | Python 模块、JavaScript 项目、API 文档、Jupyter 笔记本 |
分块工具和库
在为您的 RAG 应用程序设置数据摄取管道时,您通常会面临分块的经典权衡:您可以依赖专门的库来提高速度和易用性,也可以自己构建逻辑以获得完全控制。
使用库
幸运的是,您不必从头开始。LLM 社区经常转向两个强大的开源库:LangChain 和 LlamaIndex,它们各自采用不同的分块方法
- LangChain:一个用于构建 LLM 应用程序的广泛框架。其灵活的
TextSplitters使它易于将分块集成到更大的系统中,例如多步骤 AI 代理。- 最适合:模块化工作流程,其中分块只是拼图的一部分。
- LlamaIndex:专门为 RAG 管道设计。其复杂的
NodeParsers产生“节点”,这些节点针对摄取和检索进行了优化。- 最适合:高性能、数据中心型检索系统。
- chonkie:一个轻量级、专门的分块库,专门用于分割文本。它提供各种分块策略,例如
SemanticChunker,并且易于与其他 RAG 库集成。- 最适合:您想要一个简单、专注的解决方案而无需较大框架开销的项目。
手动实现
使用库的替代方法是自己实现分块逻辑。固定大小或递归分块等策略易于用 Python 编写代码,从而让您完全控制数据的处理方式,并避免将外部依赖项添加到项目中。
- 最适合:您想避免添加大型库、需要实现高度自定义的分块策略或需要数据管道完全透明的项目。
如何在生产环境中优化 RAG 的块大小
在生产环境中优化块大小需要进行许多测试和审查。以下是一些您可以采取的步骤
- 从常见的基线策略开始,例如固定大小的分块。一个好的起点是 512 个 Token 的块大小和 50-100 个 Token 的块重叠。这为您提供了一个易于重现和与其他分块策略进行比较的坚实基线。
- 通过调整块大小和重叠等参数来试验不同的分块方法,以找到最适合您数据的方案。
- 通过运行典型查询并检查命中率、精确度和召回率等指标来测试您的检索效果如何,以查看哪种策略能够提供最佳效果。
- 让人参与进来,以审查检索到的块和 LLM 生成的响应 - 他们的反馈将捕捉到指标可能遗漏的内容。
- 持续监控您的 RAG 系统在生产环境中的性能,并准备根据需要迭代您的分块策略。
通过我们的免费电子书深入了解高级 RAG 技术:https://weaviate.org.cn/ebooks/advanced-rag-techniques
总结
了解不同的分块策略是第一步,但掌握它们的最佳方法是在实践中看到它们。如果您正在寻找一个实际示例,请查看 Verba,我们的开源 RAG 应用程序。您甚至可以分叉该存储库,加载您自己的数据,并立即开始实验。开源只有因为您和社区💚才有可能实现,因此如果您有任何问题或想要排除故障,请加入 Weaviate Community Slack 或 论坛的对话。在那里见!
准备开始构建了吗?
请查看 快速入门教程,或使用 Weaviate Cloud (WCD) 的免费试用版构建令人惊叹的应用程序。