1. 项目概述:为什么文本切割是RAG的“命门”?
如果你正在构建一个RAG(检索增强生成)应用,无论是做一个智能客服、一个文档问答机器人,还是一个企业内部的知识库,那么你大概率已经和LangChain打过交道了。在搭建RAG流水线的过程中,有一个环节看似不起眼,却直接决定了你最终应用效果的上限与下限,那就是 文本切割(Text Splitting) 。很多人把大把精力花在选模型、调Prompt、优化检索上,结果最后发现回答得牛头不对马嘴,或者关键信息总是丢失,追根溯源,问题往往就出在最开始的这一步:文本切得不对。
你可以把RAG系统想象成一个拥有超强记忆力(大模型)但阅读速度极慢的学生。你不能直接把一整本百科全书(你的文档)塞给他,让他现场翻找答案。你得先请一位图书管理员(文本切割器),把百科全书拆分成一页页、一段段有逻辑的“知识卡片”(文本块)。这位管理员的工作质量,直接决定了学生后续能否快速、准确地找到他需要的那一页。切得太碎,上下文断裂,学生看不懂碎片信息;切得太大,冗余信息多,学生找起来费劲,还可能被无关内容干扰。
LangChain作为当前最流行的LLM应用开发框架,提供了一系列强大且灵活的文本切割器,其中
RecursiveCharacterTextSplitter
更是被广泛使用的“瑞士军刀”。但工具强大,并不意味着用起来就顺手。参数怎么设?不同的文档类型(代码、Markdown、论文)该怎么切?
chunk_size
和
chunk_overlap
背后的trade-off是什么?这些问题不搞清楚,你构建的RAG系统就像建立在流沙之上。
在这篇实战指南里,我不会只给你罗列API用法。我会带你彻底搞懂LangChain文本切割器的设计哲学、核心参数的内在逻辑,并通过大量实际代码示例,展示如何针对不同场景进行“外科手术式”的精准切割。我们最终的目标是:让你不仅能“用”Splitter,更能“懂”它,从而为你的RAG应用打下最坚实的数据基础。
2. 核心设计哲学:LangChain Splitter是如何思考的?
在深入代码之前,我们必须先理解LangChain设计文本切割器时的核心思路。这绝不是简单地把长字符串按固定长度截断那么简单。它的设计遵循了几个关键原则,理解了这些,你才能更好地驾驭它。
2.1 原则一:尽可能保持语义完整性
这是最高原则。一个理想的文本块应该是一个完整的语义单元,比如一个段落、一个列表项、一个代码块,或者一个完整的句子。强行在句子中间切断,会破坏语言模型对上下文的理解。因此,LangChain的切割器(特别是
RecursiveCharacterTextSplitter
)采用了一种“递归”策略:它首先尝试用最符合文档结构的分隔符(如
\n\n
代表段落)进行切割;如果切出来的块还是太大,再换用次一级的分隔符(如
\n
代表换行),如此递归下去,直到每个块的大小都满足要求。这个过程就像用不同精细度的筛子层层过滤,目的是在满足大小限制的前提下,尽可能找到最大的自然语义边界。
2.2 原则二:通过重叠(Overlap)维护上下文连贯性
这是RAG中防止信息丢失的经典技巧。想象一下,你把一篇文档按段落切成了A、B、C、D四个块。如果用户的问题答案恰好横跨B块末尾和C块开头,那么单独的B块或C块都无法提供完整信息,检索就可能失败。
chunk_overlap
参数就是为了解决这个问题。它让相邻的文本块之间有一部分内容是重叠的。这样,B块的尾部包含了C块开头的一点内容,C块的开头也包含了B块尾部的一点内容。在检索时,无论相关上下文落在哪个边界附近,都有更大的概率被完整的包含在某个块中。这相当于在“知识卡片”之间建立了缓冲带。
2.3 原则三:适配多样化文档类型
不同的文档有不同的内在结构。纯文本、Markdown、代码、LaTeX论文,它们的分隔逻辑天差地别。LangChain没有试图用一个规则处理所有情况,而是提供了
RecursiveCharacterTextSplitter
,并允许你自定义分隔符列表。对于代码,你可能更关心函数、类的边界;对于Markdown,你则要尊重标题、代码块等语法。框架内置了一些预设(如用于Python代码的
RecursiveCharacterTextSplitter.from_language
),但其灵活性在于,你可以为任何特定格式定制专属的切割策略。
注意 :
chunk_size的限制是硬性的,但切割器会优先保证语义完整。这意味着,最终产出的块大小可能会略小于chunk_size(如果在一个好的分隔符处提前切开了),但绝不会大于它。这是设计上的一个安全保证。
理解了这些原则,我们再去看那些参数,就不再是冰冷的数字,而是一个个影响最终效果的设计杠杆。
3. 核心参数深度解析与实战配置
现在,让我们打开工具箱,仔细审视
RecursiveCharacterTextSplitter
的几个核心参数。我会解释每个参数的含义、背后的考量,以及如何根据你的具体场景进行设置。
3.1
chunk_size
: 块大小的黄金数字
这是最关键的参数,没有之一。它定义了每个文本块的最大字符数(或
tokens
数,如果你设置
length_function
为token计数器)。
-
它如何工作?
切割器会努力将每个块控制在
chunk_size以内,但会优先在定义的分隔符处断开。 -
为什么重要?
它直接关联到两方面:
-
嵌入模型限制
:大多数文本嵌入模型(如OpenAI的
text-embedding-3-small,或开源的BGE系列)都有输入长度限制。chunk_size必须小于这个限制。 - 大模型上下文窗口 :当检索到的文本块被送入LLM生成最终答案时,它需要和用户问题、系统指令等一起消耗上下文窗口。块太大,会挤占其他内容的空间。
-
嵌入模型限制
:大多数文本嵌入模型(如OpenAI的
-
如何设置?
-
一个实用的起点
:对于英文,
1000字符是个不错的起点;对于中文,由于单字信息密度高,可以尝试500字符。但这只是起点。 -
更科学的方法
:考虑你的嵌入模型。例如,OpenAI的
text-embedding-3-small限制是8191个tokens。一个经验法则是,将chunk_size设置为模型最大限制的1/4到1/2,为重叠和其他文本留出余地。比如,设置为2000字符左右。 - 必须测试 :用你的典型文档和典型问题做测试。切出来的块,是否包含了回答问题的完整上下文?是否又包含了太多无关的“噪音”?
-
一个实用的起点
:对于英文,
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 一个基础的配置示例
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000, # 最大1000字符
chunk_overlap=200, # 重叠200字符
length_function=len, # 使用Python的len函数计算字符长度
separators=["\n\n", "\n", " ", ""] # 默认的分隔符优先级
)
3.2
chunk_overlap
: 信息丢失的“安全气囊”
这个参数定义了相邻块之间重叠的字符数。
-
它如何工作?
在切割时,切割器会确保下一个块的开始部分,复制前一个块末尾的
chunk_overlap个字符。 - 为什么需要它? 核心目的是 防止关键信息因恰好落在块边界而被切断 。例如,一个关键的定义在段落末尾,而问题相关的例子在下一段开头,没有重叠这两个信息就会分离。
-
如何设置?
-
经验值
:通常设置为
chunk_size的10%-20%。例如,chunk_size=1000,chunk_overlap可以设为100到200。 - 权衡 :重叠不是免费的。它增加了存储的嵌入向量数量(略微),也增加了检索时计算相似度的负担。更重要的是,如果重叠部分设置过大,可能会导致检索结果中出现大量高度重复的块,影响效果。 绝不是越大越好 。
- 与内容相关 :如果你的文档结构松散,段落间关联性强,可以适当增加重叠。如果是结构清晰、每段独立性强的手册,重叠可以小一些。
-
经验值
:通常设置为
# 重叠设置示例
text_splitter_with_overlap = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=150, # 15%的重叠,这是一个常用比例
separators=["\n\n", "\n", " ", ""]
)
3.3
separators
: 定义切割的“手术刀”
这是
RecursiveCharacterTextSplitter
的灵魂所在。它是一个字符串列表,定义了用于切割的分隔符,并按列表中的顺序优先使用。
-
默认值
:
["\n\n", "\n", " ", ""]-
首先尝试用双换行(
\n\n,通常代表段落)切割。 -
如果切出的块还太大,再用单换行(
\n)切割。 - 如果还大,用空格切割。
-
最后,如果以上都不行,则按单个字符切割(
"",这几乎是保底策略,会破坏单词)。
-
首先尝试用双换行(
-
如何自定义?
这是你优化切割策略的主战场。
-
处理Markdown
:你可以优先按Markdown标题(
#)、代码块(```)来切。
markdown_separators = [ "\n## ", # 二级标题 "\n### ", # 三级标题 "\n```\n", # 代码块结束 "\n\n", "\n", " ", "" ] md_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=150, separators=markdown_separators)- 处理代码 :对于Python,你可能想按函数、类定义来切。
code_separators = [ "\ndef ", # 函数定义 "\nclass ", # 类定义 "\n\n", "\n", " ", "" ]-
处理中文文档
:中文段落通常用
\n或\u3000(全角空格)分隔,句子用句号、问号等。可以调整优先级。
chinese_separators = [ "\n\n", "。", # 句号 ";", # 分号 ",", # 逗号 "\n", " ", "" ] -
处理Markdown
:你可以优先按Markdown标题(
实操心得 :调整
separators是提升切割质量最有效的手段之一。花时间分析你的源文档的结构,设计匹配的分隔符列表,效果立竿见影。一个简单的测试方法是,用你的分隔符列表手动对样例文档进行split,观察切割点是否落在你期望的语义边界上。
3.4
length_function
与
is_separator_regex
这两个参数提供了更精细的控制。
-
length_function:默认是len,即按字符数计算。但LLM的世界更关心 token数 。如果你追求精确,尤其是使用按token计费的API时,应该使用token计数器。from transformers import AutoTokenizer # 使用Hugging Face tokenizer计算tokens tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased") def tiktoken_len(text): return len(tokenizer.encode(text)) text_splitter = RecursiveCharacterTextSplitter( chunk_size=500, # 这里指的是500个tokens,不是字符! chunk_overlap=50, length_function=tiktoken_len, separators=["\n\n", "\n", " ", ""] )重要提醒 :
chunk_size的单位随length_function改变。如果你用token计数器,chunk_size指的就是token数,通常比字符数小很多(英文中,1个token约等于0.75个单词)。务必注意单位统一! -
is_separator_regex:默认为False。如果设为True,则separators列表中的每个元素都会被当作正则表达式模式来处理。这提供了极大的灵活性,例如匹配任意数量的空白字符r“\s+”,或者更复杂的模式。
4. 多场景实战:针对不同文档类型的切割策略
理论说再多,不如实战。下面我们针对几种最常见的文档类型,展示具体的切割策略和代码。
4.1 场景一:处理纯文本文档(TXT/小说/文章)
这是最基础的情况。目标是在段落和句子边界处切割,保持可读性。
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 示例文本:一篇长文章
long_text = """
人工智能(AI)是计算机科学的一个分支,旨在创造能够执行通常需要人类智能的任务的机器。
这些任务包括学习、推理、问题解决、感知和语言理解。
AI的研究可以追溯到20世纪50年代。早期的AI系统依赖于符号逻辑和硬编码规则。
然而,现代AI,特别是机器学习(ML),通过从数据中学习模式而取得了巨大成功。
深度学习是机器学习的一个子领域,它使用称为神经网络的多层模型。
这些模型在图像识别、自然语言处理等领域实现了突破性进展。
"""
# 使用默认分隔符,适合通用英文文本
general_splitter = RecursiveCharacterTextSplitter(
chunk_size=150, # 故意设小以便演示
chunk_overlap=30,
separators=["\n\n", "\n", " ", ""] # 默认
)
chunks = general_splitter.split_text(long_text)
print(f"切分出 {len(chunks)} 个块:")
for i, chunk in enumerate(chunks):
print(f"\n--- Chunk {i+1} ---")
print(chunk)
输出分析
:你会看到切割器首先在
\n\n
(段落间)进行切割,将文本分成三个大段。但由于
chunk_size
设得很小(150),它会对每个大段继续用
\n
和空格进行递归切割,最终得到若干个小块,并且块与块之间保持了30字符的重叠。
4.2 场景二:处理Markdown技术文档
技术文档(如API文档、产品手册)通常用Markdown编写,具有标题、代码块等清晰结构。切割时应尊重这些结构。
markdown_content = """
# LangChain 快速入门
LangChain 是一个用于开发由语言模型驱动的应用程序的框架。
## 安装
你可以使用pip安装LangChain:
```bash
pip install langchain
核心概念
链(Chains)
链是将多个组件组合在一起以完成特定任务的方式。
代理(Agents)
代理允许语言模型与工具进行交互。 """
为Markdown定制的分隔符列表
markdown_separators = [ "\n# ", # 一级标题 "\n## ", # 二级标题 "\n### ", # 三级标题 "\n```\n", # 代码块结束(注意前后换行) "\n\n", "\n", " ", "" ]
md_splitter = RecursiveCharacterTextSplitter( chunk_size=300, chunk_overlap=50, separators=markdown_separators, is_separator_regex=False )
md_chunks = md_splitter.split_text(markdown_content) print(f"Markdown文档切分出 {len(md_chunks)} 个块:") for i, chunk in enumerate(md_chunks): print(f"\n--- Chunk {i+1} ---") print(chunk[:200] + "..." if len(chunk) > 200 else chunk) # 预览
**关键点**:这个策略会优先在标题处切割,从而将“安装”和“核心概念”部分分开。代码块`\n```\n`也被作为高优先级分隔符,保证了代码块的完整性。这样切割出来的块,每个都围绕一个明确的主题(如一个安装步骤、一个概念解释),非常利于后续的检索和问答。
### 4.3 场景三:处理源代码
将代码仓库转化为知识库进行问答(如“这个函数是做什么的?”)是一个常见需求。切割代码时,函数、类定义是最理想的边界。
```python
python_code = """
import os
from typing import List, Optional
class DataProcessor:
\"\"\"一个用于处理数据的类。\"\"\"
def __init__(self, source_dir: str):
self.source_dir = source_dir
self._data_cache = {}
def load_data(self, filename: str) -> List[str]:
\"\"\"从文件加载数据。\"\"\"
path = os.path.join(self.source_dir, filename)
with open(path, 'r') as f:
return f.readlines()
def process(self, data: List[str]) -> Optional[List[int]]:
\"\"\"处理数据并返回结果。\"\"\"
if not data:
return None
# 一些复杂的处理逻辑...
return [len(line) for line in data]
def helper_function(x: int, y: int) -> int:
\"\"\"一个辅助函数。\"\"\"
return x + y
"""
# 针对Python代码的分隔符
code_separators = [
"\nclass ", # 类定义
"\ndef ", # 函数定义
"\n\n",
"\n",
" ",
""
]
code_splitter = RecursiveCharacterTextSplitter(
chunk_size=400,
chunk_overlap=80,
separators=code_separators
)
code_chunks = code_splitter.split_text(python_code)
print(f"Python代码切分出 {len(code_chunks)} 个块:")
for i, chunk in enumerate(code_chunks):
print(f"\n--- Chunk {i+1} ---")
print(chunk[:250] + "..." if len(chunk) > 250 else chunk)
效果
:你会看到
DataProcessor
类被切成了一个独立的块,
load_data
和
process
方法因为同属一个类且总大小未超限,被保留在了同一个块里(保持了类的上下文)。
helper_function
则被切成了另一个块。这样的切割方式,使得当用户询问“
load_data
方法是做什么的?”时,检索系统能返回包含整个类定义和该方法的完整上下文块,回答质量更高。
4.4 场景四:处理PDF/扫描件(经过OCR后)
PDF文档,尤其是扫描版,经过OCR转换后,文本格式可能混乱,段落分隔丢失,充满换行符。这时需要更“激进”的清洗和切割策略。
# 模拟一段OCR后格式混乱的文本
ocr_text = """
项目 计划书
(2024年度)
1. 项目目标
本 项目旨在开发一个
智能文档分析系统。
该系统能够自动提取
合同中的关键条款。
2. 技术路线
我们将采用自然语言处理(NLP)
技术。特别是基于Transformer
的预训练模型。
"""
# 策略:先进行简单的文本规范化,比如合并被错误断开的行
lines = ocr_text.split('\n')
cleaned_lines = []
for line in lines:
line = line.strip()
if not line:
continue
# 简单的启发式规则:如果一行很短且不以句号结束,可能与下一行是同一句
if len(line) < 40 and not line.endswith(('。', '.', '!', '?')):
cleaned_lines.append(line + " ") # 合并空格
else:
cleaned_lines.append(line + "\n") # 保留换行
cleaned_text = ''.join(cleaned_lines)
# 使用更注重句子边界的分隔符
ocr_separators = [
"\n\n",
"。", # 中文句号
".",
"\n",
" ",
""
]
ocr_splitter = RecursiveCharacterTextSplitter(
chunk_size=200,
chunk_overlap=40,
separators=ocr_separators
)
ocr_chunks = ocr_splitter.split_text(cleaned_text)
print("处理后的OCR文本块:")
for i, chunk in enumerate(ocr_chunks):
print(f"\n--- Chunk {i+1} ---")
print(chunk)
核心要点 :对于OCR文本,预处理(清洗、规范化)至关重要。在切割时,优先使用句号等句子终止符作为分隔符,比单纯依赖换行符更可靠,因为OCR的换行可能是随机的。这能帮助恢复出更符合人类阅读习惯的语义块。
5. 高级技巧与性能优化
掌握了基础用法后,我们来看看如何进一步提升切割效果和系统效率。
5.1 使用Token计数进行精确控制
如前所述,LLM按Token计费和理解文本。使用字符数作为
chunk_size
的度量是粗略的。更精确的做法是使用Token计数器。
# 方法1:使用tiktoken (OpenAI模型兼容)
import tiktoken
def tiktoken_len(text):
# 选择与你使用的LLM相匹配的编码,例如`cl100k_base`对应GPT-4/Turbo
encoding = tiktoken.get_encoding("cl100k_base")
return len(encoding.encode(text))
# 方法2:使用Hugging Face Transformers的tokenizer
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
def hf_token_len(text):
return len(tokenizer.encode(text))
token_aware_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 500个tokens
chunk_overlap=50, # 50个tokens重叠
length_function=tiktoken_len, # 或 hf_token_len
separators=["\n\n", "\n", " ", ""]
)
重要提示
:不同模型的token化方式不同(如OpenAI的
cl100k_base
与BERT的
WordPiece
)。请确保你的
length_function
与最终生成答案的LLM或计算嵌入的模型相匹配,这样才能最准确地预算上下文窗口。
5.2 元数据保留:让文本块“记得”出处
在RAG流水线中,切割后的文本块会被嵌入并存入向量数据库。但光有文本内容还不够,我们通常还需要知道这个块来自哪个文档、第几页、什么标题等。这就是元数据(Metadata)的作用。
RecursiveCharacterTextSplitter
的
create_documents
方法可以很好地处理这一点。
from langchain_core.documents import Document
from langchain_text_splitters import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=100)
# 假设我们有一份文档,并知道它的元数据
full_text = "这是文档的完整内容..."
metadata = {
"source": "年度报告_2024.pdf",
"page": 5,
"section": "财务摘要"
}
# 使用 create_documents,传入文本和元数据
docs = text_splitter.create_documents([full_text], metadatas=[metadata])
print(f"创建了 {len(docs)} 个Document对象。")
for i, doc in enumerate(docs):
print(f"\n--- 块 {i+1} ---")
print(f"内容预览: {doc.page_content[:100]}...")
print(f"元数据: {doc.metadata}")
# 每个块的元数据都会继承自传入的metadata
输出中
,每个
Document
对象的
metadata
字段都包含了
{“source”: “年度报告_2024.pdf”, “page”: 5, “section”: “财务摘要”}
。这在后续检索时非常有用,你不仅可以返回匹配的文本,还能告诉用户这个信息出自哪份文档的哪一页,极大增强了可信度和可追溯性。
5.3 与文档加载器(Document Loader)无缝集成
在实际项目中,我们很少直接操作原始字符串。LangChain的生态优势在于其组件的无缝连接。文本切割器通常紧接在文档加载器之后使用。
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 1. 加载文档
loader = PyPDFLoader("path/to/your/document.pdf")
raw_documents = loader.load() # 返回一个Document列表,每个Document可能对应一页
# 2. 配置切割器
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=["\n\n", "\n", "。", " ", ""] # 添加中文句号
)
# 3. 切割文档
all_splits = []
for doc in raw_documents:
# 可以基于原始文档的元数据,为每个块添加更细粒度的信息,比如页码
splits = text_splitter.split_documents([doc])
# 或者简单地将所有页合并后再切
# all_splits.extend(splits)
# 更常见的做法:将所有页内容合并成一个字符串再切,但会丢失页码信息。
# 更好的做法是分页切割,并保留页码元数据。
all_text = " ".join([d.page_content for d in raw_documents])
# 注意:合并会丢失页面边界信息。对于PDF,更推荐分页处理。
最佳实践建议 :对于多页PDF,建议 分页切割 。在切割时,将当前页的元数据(尤其是页码)传递给切割器,这样生成的每个文本块都能携带准确的出处信息。合并所有页再切割虽然简单,但会丢失重要的位置上下文,当用户问“第三页讲了什么?”时,你将无法精确回答。
6. 常见陷阱、问题排查与效果评估
即使参数设置得当,在实际操作中还是会遇到各种问题。下面是一些典型的“坑”及其解决方案。
6.1 陷阱一:块大小“漂移”与token计数不准
问题描述
:你设置了
chunk_size=500
,但发现有些块的实际字符数远小于500,有些则非常接近但偶尔超过(如果
length_function
计算不准)。
原因与排查 :
-
语义边界优先
:这是设计使然。切割器会在不超过
chunk_size的前提下,优先在separators列表中找到的第一个成功分割符处切割。如果在一个段落结束处(\n\n)切割时块大小只有450,它就不会继续填充到500。 -
Token与字符混淆
:如果你用
len作为length_function,chunk_size指的是字符数。但LLM和嵌入模型处理的是tokens。对于英文,500字符可能只有100-150个tokens;对于中文,500字符可能就是500个tokens。务必确认你的length_function和chunk_size单位匹配你的模型上下文窗。 -
分隔符包含在长度内
:
length_function计算的是整个文本的长度,包括用于分割的分隔符本身。
解决方案 :
- 接受块大小不均的合理性,这是保持语义完整的代价。
-
使用正确的
length_function。如果你用OpenAI的模型,强烈建议使用tiktoken进行精确计数。 - 进行小规模测试:切分一段样例文本,打印每个块的大小,检查是否符合预期。
# 诊断脚本示例
test_text = "..." # 你的测试文本
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50, length_function=len)
chunks = splitter.split_text(test_text)
for i, chunk in enumerate(chunks):
print(f"Chunk {i}: Length = {len(chunk)}")
# 如果你想看token数
# print(f"Chunk {i}: Tokens = {tiktoken_len(chunk)}")
6.2 陷阱二:重叠导致的信息重复与检索噪音
问题描述 :检索结果中出现了内容高度相似的块,导致回答冗余或混乱。
原因
:
chunk_overlap
设置过大,或者文档本身有很多重复的模板文字(如每页的页眉页脚),导致重叠部分包含了大量无意义的重复信息。
解决方案 :
- 调整重叠大小 :从10%-20%的比例开始尝试,如果发现重复严重,可以降低到5%-10%。
- 预处理文档 :在切割前,移除文档中的页眉、页脚、重复的标题等。
- 后处理检索结果 :在将检索到的块送给LLM之前,进行去重。可以计算块之间的相似度(如使用嵌入向量余弦相似度),如果相似度超过某个阈值,则只保留一个。
6.3 陷阱三:复杂格式文档切割效果差
问题描述 :处理PDF表格、扫描图片生成的文本、HTML网页时,切割出来的块支离破碎,完全无法理解。
原因
:
RecursiveCharacterTextSplitter
默认的分隔符列表是针对纯文本或简单富文本设计的。复杂格式的文档在转换为文本时,原有的视觉结构信息(如表格行列、图片位置)已经丢失,或者变成了奇怪的字符。
解决方案 :
-
使用专用加载器
:对于PDF,尝试使用能保留表格和布局信息的加载器,如
UnstructuredPDFLoader,它可能提供带有关联区域信息的文本。 -
预处理与清洗
:编写自定义脚本,识别并处理转换后文本中的特定模式(如一连串的
-或=可能代表分割线)。 -
采用更高级的切割策略
:
-
语义切割
:使用基于嵌入的语义切割器,如
SemanticChunker(LangChain社区组件)。它通过计算句子或小段落的嵌入向量,在语义变化大的地方进行切割。这对格式混乱但语义连贯的文本效果更好,但计算成本更高。 -
固定大小切割+滑动窗口
:对于质量极差、无法找到任何可靠分隔符的文本,可以退而求其次,使用
CharacterTextSplitter进行固定大小切割,并配合一个较大的滑动窗口重叠,来保证信息不被割裂。这是保底方案。
-
语义切割
:使用基于嵌入的语义切割器,如
6.4 如何评估切割效果?
没有放之四海而皆准的“最佳参数”。评估必须结合你的 下游任务 (即你的RAG系统要回答的问题类型)。
评估步骤 :
- 构建测试集 :收集一批有代表性的文档,并针对这些文档设计一批真实用户可能会问的问题。
-
定义评估指标
:
- 检索召回率 :对于每个问题,人工标注出文档中所有包含答案的“理想文本块”。然后,用你的切割策略和检索系统,看能召回多少个“理想块”。召回率越高,说明切割越能保证答案的完整性。
- 块内容相关性 :随机抽样一些切割后的块,人工评估其语义是否完整、独立。是否是一个可以独立理解的单元?
- 最终答案质量 :这是终极测试。用不同的切割参数配置运行完整的RAG流程,让人或用一个好的LLM(如GPT-4)来评判最终生成答案的准确性、完整性。进行A/B测试。
-
迭代优化
:根据评估结果,调整
chunk_size、chunk_overlap和separators。这是一个需要反复实验的过程。
记住,文本切割是RAG的基石,值得你投入时间进行精细化的调优。一个好的切割策略,能让后续的检索和生成事半功倍。

1333

被折叠的 条评论
为什么被折叠?



