如何解决 RAG 在处理超长文档时的检索碎片化和上下文丢失问题
我之前在做一份行业研究报告的问答系统时就踩了这个坑。传统的 RecursiveCharacterTextSplitter 简单粗暴,切分点在语义上是断层的。解决这个问题的核心不能只靠调大 chunk_size(因为这会引入过多噪声并触碰 Token 上限),得在索引阶段做“语义增强”。
目前实测最有效的方案是 Parent Document Retrieval (父文档检索)。逻辑很简单:索引时用小块(Child Chunk)保证检索精度,检索到后再把这个小块所属的大块(Parent Chunk)整个喂给 LLM。
具体在 LangChain 里的配置逻辑如下:
from langchain.retrievers import ParentDocumentRetriever
from langchain.storage import InMemoryStore
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
# 定义子块(用于检索)和父块(用于提供上下文)
child_splitter = RecursiveCharacterTextSplitter(chunk_size=400)
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=2000)
vectorstore = Chroma(collection_name="split_parents", embedding_function=OpenAIEmbeddings())
store = InMemoryStore() # 存储父文档的内存库
retriever = ParentDocumentRetriever(
vectorstore=vectorstore,
docstore=store,
child_splitter=child_splitter,
parent_splitter=parent_splitter,
)如果文档具有极强的层级结构(比如有明确的一级、二级标题),建议放弃纯字符切分,改用 MarkdownHeaderTextSplitter。通过标题标签将内容分组,这样检索出的片段会自带层级前缀,LLM 瞬间就能明白这段话是在哪个章节下,解决了上下文丢失的问题。
另外,针对超长文档的“全局性”问题,我尝试过在向量库之上加一层 Summary Index。在处理文档时,先用 LLM 为每个大章节生成一个 200 字的摘要,并把摘要单独存入另一个索引。当用户提问时,先检索摘要库确定目标章节,再进入具体内容库检索。
实战避坑指南:
不要迷信 Hybrid Search (混合检索) 能解决碎片化,它只能提高找得准的概率,不能解决找回来之后“读不懂”的问题。
注意 Overlap 的设置,如果非要用简单切分,chunk_overlap 至少要设在 15%-20%,否则句子被腰斩的情况非常严重。
Embedding 模型选择,处理长文档时,建议用 BGE-M3 这种支持长文本且检索能力更强的模型,否则长段落的向量表征会迅速失效。
全部回复 (0)
还没有回复,来发第一条吧!
