RAG 父子切割
RAG 父子切割(也常被称为父子分段或父子分块)是一种用于优化检索增强生成(RAG)系统的高级策略。简而言之,它的核心思想是 “小块检索,大块回复”。
🤔 为什么要用“父子切割”?
在RAG应用中,处理长文本时常常面临一个两难困境:
- 切得太小:检索精度高,容易命中关键信息。但上下文不完整,模型回答可能“断章取义”。
- 切得太大:上下文完整,模型能看懂来龙去脉。但检索精度低,容易“找不到”或“找不准”关键细节,语义信息会被稀释。
🔧 “父子切割”如何运作?
“父子切割”策略通过引入层级结构,完美地解决了上述矛盾。
简单说,就是同一个文档内容,会被同时切割成“父块”和“子块”两种大小:
- 子块 (Child Chunk):体积小,是检索的单位。它能更精确地匹配用户的提问。
- 父块 (Parent Chunk):体积大,通常包含多个子块的上下文。它不参与检索,但负责在回复时提供完整背景。
整个过程可以理解为三步:
-
入库
- 切分:将一份原始文档(例如一份用户协议)同时切分为“父块”与“子块”。每个“父块”都关联着几个“子块”。
- 存储:
- 父块被存入一个文档存储区 (Doc Store)。
- 子块(及其包含的文本和指向父块的ID)被存入向量数据库,生成用于检索的向量。
-
检索 系统收到用户提问时,只会用“子块”去向量库中进行相似度检索。子块尺寸小、语义聚焦,能找到最匹配的那段话。
-
回复
- 系统根据子块里存的ID,找到其关联的、完整的“父块”。
- 将整个“父块”的完整文本作为上下文提交给大模型,让它来生成最终答案。
这样一来,大模型既获得了精准匹配的细节(通过子块检索),又获得了完整的上下文信息(通过父块上下文),从而生成更准确、完整的答案。
很多RAG框架(如LangChain)都内置了此功能,其核心检索组件就是“父文档检索器” (ParentDocumentRetriever)。
⚙️ 关键参数如何选择?
参数选择取决于你的具体数据和场景,你可以参考以下建议:
| 参数 | 父块 (Parent Chunk) | 子块 (Child Chunk) |
|---|---|---|
| 核心作用 | 提供完整上下文,供模型阅读 | 进行精准匹配,用于检索 |
| 常见长度 | 较长,如300-800字符, 500-1000字符, 500 Tokens | 较短,如100-200字符, 50-200字符, 150 Tokens |
| 适用场景 | 法律合同、技术手册等需强上下文的文档 | API文档、FAQ问答等对检索精度要求高的场景 |
✨ 主要优势与潜在权衡
优势
- 提升检索精度:小块检索能精准命中关键信息点。
- 保证回答完整:父块提供完整上下文,避免断章取义,提升最终答案质量。
- 优化长文本处理:有效解决了长文档“既想检索准,又想上下文全”的核心矛盾。
需要权衡的成本
- 存储成本增加:同一份文本内容被存储了两份(父块和子块)。
- 系统复杂度提升:需要维护两个存储区并管理它们之间的关联。
- 稍高的检索延迟:检索流程从一次查询变为“检索+关联+返回”,耗时略有增加。
💡 哪些场景最适合用?
该策略尤其适用于对答案准确性要求高且上下文依赖性极强的领域:
- 法律合同/条款:在精准定位某一条款的同时,也能关联到它所属的整个合同章节。
- 医疗病历/报告:在检索某项指标时,能一同带入病人的相关病史和诊断结论。
- 技术白皮书/产品手册:查询某个具体参数时,能关联其定义、使用限制和关联说明。
- 学术论文/科研文献:查找文中某一观点时,能提供该观点所在章节的完整论述。
- 长篇商业报告:针对报告中某个数据点提问时,能返回其分析背景和上下文。
📝 快速起步实践(LangChain 示例)
你可以根据以下代码示例快速上手实践:
from langchain.retrievers import ParentDocumentRetriever
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 1. 定义分割器
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=2000) # 父块大
child_splitter = RecursiveCharacterTextSplitter(chunk_size=500) # 子块小
# 2. 初始化检索器
retriever = ParentDocumentRetriever(
vectorstore=vectorstore, # 存储子块和向量
docstore=docstore, # 存储父块原文
child_splitter=child_splitter,
parent_splitter=parent_splitter,
)
# 3. 添加文档(系统会自动进行父子切分与存储)
retriever.add_documents(docs)
小提示:如果只是想快速体验,可以先指定
child_splitter,不传parent_splitter,系统会将每个完整的原始文档视为一个“父块”。核心逻辑是将整个文档作为父块,这是一个最简上手配置。
💎 总结
“父子切割”是一种平衡了检索精度与上下文完整的有效策略,通过将小块检索与大块信息结合,显著提升了RAG系统在复杂场景下的问答质量。希望这份解释对你有帮助!如果你在实践中有更多关于 RAG 的问题,也欢迎随时提出。