Appearance
12.2.1 什么是分层检索(Hierarchical Retrieval)?
先给结论:分层检索的本质是“把文档结构放回索引里”,让检索先在上层定位章节,再在下层拿细节。它不是更复杂的分块,而是把“父子关系”和“层级路径”变成可检索的结构。
在大文档或复杂结构里,用户的问题往往先指向“哪个章节”,再指向“哪个条款”。如果你只用 flat chunk 检索,系统只能在碎片里碰运气,结构信息被彻底丢失。
分层检索解决的核心问题
- 文档层级很深,单次相似度无法准确定位“正确的层”
- 需要先确定章节范围,再在章节内找具体条款
- 章节标题或目录对检索非常关键,但在 chunk 中被稀释
分层检索的典型结构
分层检索通常把文档拆成多层节点:
- L0:文档级(doc)
- L1:章节级(section)
- L2:小节级(subsection)
- L3:段落级(paragraph)
检索时常见两种做法:
- 先检索细粒度节点,再“回溯”到父节点补上下文
- 先检索章节级节点,再在章节内做二次检索
两种路线都强调一个原则:先确定层,再找细节。
最小可用的实现思路
分层检索落地时,建议按下面顺序做:
- 结构化分块:把标题层级作为分块边界,保留章节路径
- 建立父子映射:每个子块记录
parent_id和path - 多层索引:对不同层级建索引或在同一索引里保留
level - 检索后合并:子块命中后回溯到父块,组合成可用上下文
- 控制返回规模:同一父块的子块合并,避免碎片化堆叠
下面是一个最小示例,展示如何把文档解析成多层节点(示意):
python
from llama_index.core import SimpleDirectoryReader
from llama_index.core.node_parser import HierarchicalNodeParser, get_leaf_nodes
docs = SimpleDirectoryReader("./docs").load_data()
parser = HierarchicalNodeParser.from_defaults(
chunk_sizes=[2048, 512, 128]
)
nodes = parser.get_nodes_from_documents(docs)
leaf_nodes = get_leaf_nodes(nodes)
# leaf_nodes 可用于检索;nodes 中保留父子关系与层级信息这段代码的重点不是 API 细节,而是:你需要“层级分块 + 父子关系”,否则检索无法跨层工作。
分层检索什么时候最值得用
如果你遇到以下情况,分层检索通常是低成本高收益:
- 文档是“目录-章节-条款”的规范或手册
- 用户问题经常包含章节级线索(如“第 3 章”“第 2 节”)
- top-k chunk 命中零散,但上下文拼不起来
常见误区
误区一:分层检索只是“分得更细”
真正的分层检索不是更细的 chunk,而是保留结构。如果你只把 chunk 变小,而没有父子关系,问题依然存在。
误区二:只返回子块就够了
子块命中后如果不回溯到父块,检索仍然是碎片化的。分层检索的价值在于“让上下文回到结构里”。
误区三:层级越多越好
层级过深会带来维护成本和检索延迟。建议先用 2-3 层验证收益,再决定是否加深层级。
自检清单
- 是否能从命中的子块回溯到父块?
- 章节标题和路径信息是否进入索引?
- 命中结果能否组成完整语义段落,而不是碎片堆叠?
一句话总结
分层检索的核心是“先找层,再找细节”。只要你的文档有明显结构,分层检索就不是可选项,而是让检索可靠的基本结构。