如何解决 RAG 检索时因文档切片截断导致的上下文丢失问题
chunk_size=500 硬生生劈成两半,检索时只搜到了后半段,LLM 面对缺失的前文只能在那儿一本正经地胡说八道。我之前尝试过增加 overlap(重叠区),但发现这只是治标不治本。重叠区设太小没效果,设太大又会让上下文充斥重复信息,浪费 Token 且干扰模型判断。
目前我实测最有效的方案是 「父子索引(Parent Document Retrieval)」 策略。简单说,就是用小的切片(Child Chunk)负责检索,用大的上下文(Parent Chunk)负责喂给模型。
具体操作流程是这样的:
1. 将文档切分为较大的父块(比如 1000 token)。
2. 每个父块再细分为 3-5 个子块(比如 200 token)。
3. 向量数据库里只存子块的 Embedding,但每个子块都要关联一个 parent_id。
4. 检索时,命中子块 → 根据 parent_id 溯源 → 将整个父块交给 LLM。
在用 LangChain 实现时,可以配置 ParentDocumentRetriever,核心逻辑大致如下:
from langchain.retrievers import ParentDocumentRetriever
from langchain.storage import InMemoryStore
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import Chroma
# 定义父子切分器
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=1000)
child_splitter = RecursiveCharacterTextSplitter(chunk_size=200)
# 存储父文档的内存库
store = InMemoryStore()
retriever = ParentDocumentRetriever(
vectorstore=Chroma(embedding_function=embeddings),
docstore=store,
child_splitter=child_splitter,
parent_splitter=parent_splitter,
)除此之外,我还踩过一个坑:不要完全依赖字符数切分。如果你的文档是 Markdown 格式,建议用 MarkdownHeaderTextSplitter。它会根据 # 标题层级来切分,这样能保证同一个语义段落(比如一个 API 接口的描述)被完整保留在同一个块里,从根源上避免了截断导致的语义丢失。
如果必须用字符切分,记得把 separators 参数配置得精细点。不要只给个 \n,建议按照 ["\n\n", "\n", "。", " ", ""] 的优先级排列,让 AI 尽量在段落末尾或句子末尾截断,而不是在单词中间切断。
这种方案上线后,我测试的复杂业务逻辑问答准确率提升了约 30%,尤其是那些跨句子的因果关系分析,不再出现“断片”现象。
全部回复 (0)
还没有回复,来发第一条吧!
