当前位置:首页 > 文章列表 > 科技周边 > 人工智能 > RAG chunk metadata 怎样保留原文页码和标题层级

RAG chunk metadata 怎样保留原文页码和标题层级

来源:17golang原创 2026-11-14 04:39:03 0浏览 收藏

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 则保存原始入口,是否公开要由你的产品权限策略决定。

原文文档、页面记录、标题路径和正文块汇聚到 RAG chunk metadata 的结构说明图
图1:结构说明图,展示原文页码和标题路径如何随正文块进入 chunk metadata;这是静态示意,不是运行截图。

切块时复制当前页码和标题栈

分块器的关键不是“切完再猜来源”,而是在扫描页面和标题时维护当前状态:遇到新页就更新页码,遇到标题就更新标题栈,产出 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 文本、页码范围和标题层级后交给引用格式化器的关系说明图
图2:关系说明图,展示召回结果如何保留 chunk 文本与页码、标题字段,再交给引用格式化器;这是静态示意,不是运行结果。

用四个检查项定位引用不准

当回答里的来源位置不可信时,我会按下面的顺序排查,而不是先重建向量索引:

  • 页码口径:解析器记录的是物理页还是印刷页?封面和目录是否导致固定偏移?
  • 标题快照: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,不要临时猜页码。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
PyTorch AMP 怎样区分 autocast 与 GradScaler 的职责PyTorch AMP 怎样区分 autocast 与 GradScaler 的职责
上一篇
PyTorch AMP 怎样区分 autocast 与 GradScaler 的职责
AI 代理执行循环怎样设置工具调用最大轮数
下一篇
AI 代理执行循环怎样设置工具调用最大轮数
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    1018次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    1020次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    1155次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    1121次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    962次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码