RAG chunk metadata 怎样保留原文页码和标题层级
RAG 召回到一段相关文字,却只能告诉用户“来自某个 PDF”,通常不是向量检索本身出了问题,而是分块时没有把来源血缘一起保存。实用做法是:在 chunk 生成前统一页码口径和标题栈,再把 doc_id、page_start、page_end、heading_path 等字段复制到每个 chunk;召回后直接用这些字段拼出“文档名 · 第几页 · 标题路径”的来源行。
官方地址:https://platform.openai.com/docs/guides/tools-file-search
OpenAI 的检索文档展示了文件引用与文件名标注,向量库文件也支持用于过滤的 attributes。它们适合做文件级筛选和结果标识,但页码、标题层级属于更细的 chunk 级信息,应该由自己的解析与分块层明确维护。
先把“这段文字来自哪里”设计成数据字段,再谈向量库如何存储。只给文档名而没有页码和标题路径,召回结果很难稳定回指原文。
分块前先统一页码和标题路径
我在处理 PDF、Markdown 和网页导出文本时,最先固定的不是 chunk 大小,而是来源字段的含义。page_start 与 page_end 表示原文页码范围;heading_path 表示从文档标题到当前小节的完整路径,例如“部署手册 / 第 3 章 / 连接池配置”。不要把 PDF 内部从 0 开始的数组下标直接当作读者看到的页码,封面、目录和印刷页码可能让两者不同。
一条 chunk 的最小字段可以先定成下面这样。JSON 必须保持可解析,所以解释放在代码块外:
{
"chunk_id": "guide-003-004",
"doc_id": "guide-003",
"source_uri": "s3://docs/guide.pdf",
"page_start": 12,
"page_end": 13,
"heading_path": ["部署手册", "连接池配置"],
"chunk_index": 4,
"text": "连接池上限需要与数据库并发能力一起设置……"
}
chunk_id 用于定位记录,doc_id 用于聚合同一份文档,页码字段用于展示,标题路径用于让读者判断上下文。chunk_index 只表示在当前文档中的稳定顺序,不能替代页码;source_uri 则保存原始入口,是否公开要由你的产品权限策略决定。

切块时复制当前页码和标题栈
分块器的关键不是“切完再猜来源”,而是在扫描页面和标题时维护当前状态:遇到新页就更新页码,遇到标题就更新标题栈,产出 chunk 时把当前状态做一份不可变快照。这样一个 chunk 即使跨越两页,也能保留范围;跨越标题时,也能知道它覆盖了哪些层级。
from dataclasses import dataclass
@dataclass(frozen=True)
class Block:
# 每个解析块都携带页码和当前标题路径,避免后续再次猜测来源。
text: str
page: int
heading_path: tuple[str, ...]
def make_chunks(blocks, max_chars=900):
# 连续拼接正文块,并把首尾页码与标题路径复制到 chunk。
chunks = []
buffer = []
size = 0
def flush():
# 清空缓冲区前创建快照,保证列表后续变化不影响已产出的记录。
nonlocal buffer, size
if not buffer:
return
chunks.append({
"text": "\n".join(item.text for item in buffer),
"page_start": buffer[0].page,
"page_end": buffer[-1].page,
"heading_path": list(buffer[-1].heading_path),
})
buffer, size = [], 0
for block in blocks:
# 超过上限时先提交已有片段,再开始新的 chunk。
if buffer and size + len(block.text) > max_chars:
flush()
buffer.append(block)
size += len(block.text)
flush()
return chunks
示例把标题路径取为 chunk 最后一个解析块的路径,适合“一个 chunk 尽量不跨标题”的策略。如果你的分块器允许跨越多个小节,就不要假装只有一个标题;可以把路径改成 heading_paths 数组,或在切块前按标题边界拆开。关键是字段语义要和实际切分策略一致。
我还会把页码定义写进数据字典:是 PDF 物理页、文档印刷页,还是网页导出的虚拟页。不同来源统一成一种展示口径后,再把原始页索引作为内部字段保留,排查偏移时会轻松很多。
召回后用 metadata 生成可回指来源
向量记录中至少要能同时取回 chunk 文本和来源字段。若使用支持文件 attributes 的向量库,可以把 tenant、doc_type、published_at 这类文件级过滤字段放进去;但 page_start、page_end 和 heading_path 仍应跟着每个 chunk 返回,不能因为文件级 attributes 存在就丢掉它们。
def format_citation(hit):
# 召回结果缺字段时给出可识别的降级文本,不拼接 None 或错误页码。
metadata = hit.get("metadata") or {}
title_path = metadata.get("heading_path") or ["未标注标题"]
page_start = metadata.get("page_start")
page_end = metadata.get("page_end", page_start)
if page_start is None:
page_text = "页码未标注"
elif page_end and page_end != page_start:
page_text = f"第 {page_start}-{page_end} 页"
else:
page_text = f"第 {page_start} 页"
# 标题路径只负责提供上下文,正文仍应来自召回的 chunk 文本。
return f"{metadata.get('doc_name', '未命名文档')} · {page_text} · {' / '.join(title_path)}"
引用格式化器只做展示,不应该重新推断页码,也不应该把检索分数写成“证据强度”。分数是排序信号,页码和标题是来源定位;两者解决的是不同问题。

用四个检查项定位引用不准
当回答里的来源位置不可信时,我会按下面的顺序排查,而不是先重建向量索引:
- 页码口径:解析器记录的是物理页还是印刷页?封面和目录是否导致固定偏移?
- 标题快照:chunk 是否在每个页面和标题变化处更新了状态?是否只给第一块写过标题?
- 跨页范围:跨页 chunk 是否保存了
page_start与page_end,而不是只保留首尾之一? - 召回回传:向量检索结果是否把 metadata 一并带回?是否在序列化或重排时丢了字段?
如果只需要 OpenAI File Search 的文件级引用,官方结果可以提供文件名等标识;如果产品要显示“第 12 页 / 连接池配置”,就必须在自己的摄取链路里保存更细的 chunk metadata。页码和标题层级不是装饰字段,而是让用户能回到原文、判断上下文并复核答案的最短路径。
常见问题
只保存 source_uri,不保存页码可以吗?
可以完成文件级回指,但无法稳定告诉读者具体位置。对于长文档,至少保留文档标识、页码范围和标题路径;网页没有页码时,可用章节路径、段落序号或字符偏移替代。
标题层级应该保存字符串还是数组?
数组更适合结构化处理和后续展示,字符串更适合直接输出。实践中可以用数组作为主字段,展示时再用分隔符拼成路径,避免标题中本来就含有分隔符时难以还原。
向量库的 attributes 能替代 chunk metadata 吗?
通常不能。attributes 更适合文件级过滤和分类;页码、跨页范围和标题路径会随每个 chunk 变化,应跟着 chunk 文本一起进入召回结果,或由可追踪的旁路表按 chunk_id 取回。
一句话总结:先在解析阶段定义页码口径和标题栈,再把来源字段随每个 chunk 一起存储;召回后只格式化已有 metadata,不要临时猜页码。
PyTorch AMP 怎样区分 autocast 与 GradScaler 的职责
- 上一篇
- PyTorch AMP 怎样区分 autocast 与 GradScaler 的职责
- 下一篇
- AI 代理执行循环怎样设置工具调用最大轮数
-
- 科技周边 · 人工智能 | 33分钟前 |
- Transformers DataCollatorWithPadding 怎样按 batch 动态补齐
- 351浏览 收藏
-
- 科技周边 · 人工智能 | 35分钟前 | 缓存 · 人工智能 · mcp · 工具调用 · MCP JSON Schema tools/list tools/list_changed tools/call AI 工具调用
- MCP 客户端怎样处理工具列表变化而不缓存旧 schema
- 320浏览 收藏
-
- 科技周边 · 人工智能 | 37分钟前 | MCP Tasks 进度通知 progressToken
- MCP 服务器怎样为长任务报告进度而不阻塞请求
- 414浏览 收藏
-
- 科技周边 · 人工智能 | 40分钟前 | OpenAI OUTPUT 结构化输出 Responses API output_text
- OpenAI Responses API output_text 与 output 结构如何选择
- 212浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 1018次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 1020次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 1155次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 1121次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 962次使用
-
- CodeGeeX for Jetbrains IDEs正式上线!
- 2023-01-17 284浏览
-
- 技术阿里云实现ocr批量图片和pdf文件表格图片转换excel文档/支持票据图片提取/普通图片文字提取处理
- 2023-01-18 387浏览
-
- 直播预告|FeatureStore Meetup V2
- 2023-01-10 328浏览
-
- 深入浅出特征工程 – 基于 OpenMLDB 的实践指南(上)
- 2023-02-25 426浏览
-
- 开源机器学习数据库OpenMLDB v0.4.0产品介绍
- 2023-01-10 147浏览

