如何解决 RAG 检索中长文本切片导致的语义断裂问题?
我对比测试了三种方案,实测结论是:简单的 RecursiveCharacterTextSplitter(递归字符切片)在处理复杂技术文档时基本是灾难,因为它只管长度,不管语义。
方案一:固定窗口 + 重叠区 (Overlap)
这是最基础的做法,设置 500 token 切片,重叠 100 token。
实测表现: 能缓解断裂,但无法根治。如果关键信息刚好在重叠区边缘,检索出来的 Chunk 依然缺乏足够的上下文。
缺点: 增加了冗余计算,且由于 Chunk 之间重复,容易在生成阶段导致模型复读。
方案二:语义切片 (Semantic Chunking)
利用 Embedding 模型计算相邻句子的余弦相似度,当相似度跌破阈值时才切分。
实测表现: 效果明显好于固定长度。它能保证一个自然段落的完整性,在处理法律条文或产品手册这种逻辑严密的文本时,召回的准确率提升了约 20%。
缺点: 预处理速度极慢,因为每一句都要跑一遍 Embedding,对大规模文档库来说成本太高。
方案三:父子索引架构 (Parent-Document Retrieval)
这是目前我认为最稳的解法。核心逻辑是:切分两个版本,一个极小的子块(用于检索)和一个较大的父块(用于喂给 LLM)。
实测逻辑:
1. 文档切成 128 token 的子块 → 存入向量库。
2. 文档切成 1000 token 的父块 → 存入 KV 存储。
3. 检索时匹配子块 → 根据 ID 溯源回传整个父块。
模型适配对比:
在处理这类 RAG 召回结果时,不同模型的承接能力差异很大:
Claude 3.5 Sonnet: 处理长上下文能力极强,即使父块包含少量噪声,它也能精准定位答案,不容易被干扰。
GPT-4o: 逻辑严密,但如果父块过长且无关信息多,偶尔会出现“迷失在中间 (Lost in the Middle)”的情况。
DeepSeek-V2.5: 在处理中文技术文档的上下文关联上表现惊人,性价比最高,但对噪声的容忍度比 Claude 略低。
如果追求极致效果,建议直接上父子索引 + Claude 3.5;如果对处理速度有要求,可以用语义切片。
实现父子索引的伪代码逻辑参考:
from langchain.retrievers import ParentDocumentRetriever
from langchain.storage import InMemoryStore
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 定义子切片(用于检索)和父切片(用于提供上下文)
child_splitter = RecursiveCharacterTextSplitter(chunk_size=200)
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=1000)
retriever = ParentDocumentRetriever(
vectorstore=vectorstore,
docstore=InMemoryStore(),
child_splitter=child_splitter,
parent_splitter=parent_splitter,
)全部回复 (0)
还没有回复,来发第一条吧!
