做 RAG 最容易被盲抄的一个功能是父页面召回(Parent Document Retrieval)。我研究了 2 天,结论:我不做,而且很多人一开始就做错了。
30 秒拿走一份判断清单,下面是完整论证版。
它是什么,治什么病
一句话:索引小块管查得准,返回父文档管上下文全。
检索命中一个小 chunk 后,不直接把它喂给模型,而是顺着元数据找回它所属的大单元(父段落/整页/整文档),用大单元做生成上下文。
它治的病只有一个:分块把意义切断了——结论依赖前文铺垫,小块单独拿出来语义不成立。
副作用同样明确:prompt 塞进大量无关内容、模型注意力被稀释(回答变吵)、token 成本变高、时延变长。
它是治「切断意义」的药,不是补脑药。 没这个病就别吃。
3 种场景不值得做
- 段落自包含:问答对、条款、产品参数这类文体,按分隔符切开后每块意义完整。小块已经够用,展开父页只是往 prompt 里灌水。
- 语料不大且检索稳:混合检索+重排做完,top3/top5 一直命中得很稳,根本不需要回父页凑上下文——你的问题在「查得准」,不在「上下文全」。
- 抽取型答案:只要从文档里抽一个字段/一条规定。返回整页反而给模型发挥空间,它开始在你不该有自由度的地方自行总结。
2 种场景值得做
- 长文档、结论依赖上下文才成立:法律意见书、实验报告,「综上所述」的「上述」就是病。
- 怎么切都装不下一个答案:切小块检索不准、切大块上下文还是不够——说明你缺的不是查得准,是上下文完整。这时父页面召回才是正解。
我的数据形状:为什么落在「不做」区
语料是部门制度类文档,txt/word/pdf/xlsx 上传解析成纯文本后走我的递归切分器(chunker.ts):
export const DEFAULT_TARGET_CHARS = 800 // 目标字符数 ≈ 上限值
// 短文档:直接一片(核心保护,避免过切)
if (cleaned.length <= targetChars) {
return [{ content: cleaned, chunk_order: 1, page_no: pageNo }]
}
// 超长才逐层下降粒度:标题 → 空行段落 → 句号断句 → 固定兜底
制度文档的特点:条款天然自包含,一条「婚假申请流程」就是一个完整语义单元,800 字符目标 + 贪心合并(每片尽量落在 [400, 800] 区间)切出来的就是本来就自包含的小块。全库 172 个 chunk,混合检索(BM25+向量 RRF 融合)+ 精排取前 5 之后,命中一直挺稳——正好命中上面「不值得做」的前两种。
还有一个隐性成本:父页面召回需要 chunk→parent 的元数据链路(parent_id/页文档正文回查)。我的 chunk 有 page_no 和全文档连续 chunk_order,页级回查技术上做得动,但文档级展开等于把「检索」降级成「整本塞」——为我这 172 个块的语料引入这层结构和运行期回查,收益负期望。
所以父页面召回对我是锦上添花,不是药。功能不是看别人做了就抄,是看自己的数据形状。
3 秒自查法(建议抄走)
随机抽库里 5 个段落,只问一句:
「只留这一段,答案还成立吗?」
- 多数成立 → 不用做父页面,钱花在检索精排上更值
- 多数不成立 → 再动手,而且先试便宜的「取相邻片段」(命中 chunk 前后各带 1 条),还解决不了才上父文档级展开
速查表
| 判断 | 动作 |
|---|---|
| 条款/FAQ/参数,块块自包含 | 不做,省成本 |
| top5 命中稳定 | 不做,问题不在这 |
| 只要抽字段 | 不做,整页反而引入发挥空间 |
| 长文结论依赖铺垫 | 值得做 |
| 怎么切都不装不下答案 | 值得做(先试相邻片段) |
我的清单里后续真要排的是 eval 验证集和生成侧引用校验——先有尺子再谈优化,这是下一个话题。
本篇最早于 2026 年 10 月 20 日发布于小红书,此为博客完整版。