Skip to main content

RAG 父子切割

RAG 父子切割(也常被称为父子分段或父子分块)是一种用于优化检索增强生成(RAG)系统的高级策略。简而言之,它的核心思想是 “小块检索,大块回复”

🤔 为什么要用“父子切割”?

在RAG应用中,处理长文本时常常面临一个两难困境:

  • 切得太小:检索精度高,容易命中关键信息。但上下文不完整,模型回答可能“断章取义”。
  • 切得太大:上下文完整,模型能看懂来龙去脉。但检索精度低,容易“找不到”或“找不准”关键细节,语义信息会被稀释。

🔧 “父子切割”如何运作?

“父子切割”策略通过引入层级结构,完美地解决了上述矛盾。

简单说,就是同一个文档内容,会被同时切割成“父块”和“子块”两种大小:

  • 子块 (Child Chunk):体积小,是检索的单位。它能更精确地匹配用户的提问。
  • 父块 (Parent Chunk):体积大,通常包含多个子块的上下文。它不参与检索,但负责在回复时提供完整背景。

整个过程可以理解为三步:

  1. 入库

    1. 切分:将一份原始文档(例如一份用户协议)同时切分为“父块”与“子块”。每个“父块”都关联着几个“子块”。
    2. 存储
      • 父块被存入一个文档存储区 (Doc Store)。
      • 子块(及其包含的文本和指向父块的ID)被存入向量数据库,生成用于检索的向量。
  2. 检索 系统收到用户提问时,只会用“子块”去向量库中进行相似度检索。子块尺寸小、语义聚焦,能找到最匹配的那段话。

  3. 回复

    1. 系统根据子块里存的ID,找到其关联的、完整的“父块”
    2. 将整个“父块”的完整文本作为上下文提交给大模型,让它来生成最终答案。

这样一来,大模型既获得了精准匹配的细节(通过子块检索),又获得了完整的上下文信息(通过父块上下文),从而生成更准确、完整的答案。

很多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 的问题,也欢迎随时提出。