RAG 的 metadata 设计:来源、页码、版本和权限
围绕source/page metadata、手动 filter、SelfQueryRetriever 与 source_documents 的示例,讲清 RAG 为什么不能只存文本,还要给每个 chunk 设计可过滤、可引用、可审计的元数据。
相关工具
只存文本的 RAG,很快会说不清答案从哪来
很多 RAG 项目早期只关心一件事:把文档切成 chunk,写入向量库,能搜回来就算成功。这个阶段系统看起来能回答问题,但资料一多,问题就会冒出来:这段答案来自哪份文件?是哪一页?是不是旧版本?当前用户有没有权限看?如果这些信息没有跟 chunk 一起保存,后面几乎没法查。
相关概念 里的检索示例很直观。打印出来的文档不只有 `page_content`,还带着 `metadata`,例如 `source` 指向 Matplotlib 的某一回讲义,`page` 标出原始页码。这个结构说明了一件事:向量库里存的不应该只是文本,还应该存文本的出处。
`metadata` 不是额外装饰。它决定 RAG 能不能过滤范围、显示引用、排查错因、控制权限。没有 metadata,RAG 更像一堆被打散的文字;有了 metadata,每个 chunk 才能回到原文、版本和业务边界。

每个 chunk 除了 page_content,还要带 source、page、version、permission 等字段,才能过滤、引用和审计。
source 和 page 是最小可用字段
文中最常见的 metadata 字段是 `source` 和 `page`。`source` 表示 chunk 来自哪份文档,`page` 表示它在原始文档里的页码。它们看起来简单,却是 RAG 可追溯的底线。
当用户问“第二讲里 Figure 讲了什么”,系统如果只按语义相似度检索,可能会把第一讲也召回来,因为第一讲也出现了 Figure。文中的失败案例正是这样:结果列表里混入了第一讲和第二讲。把每条结果的 metadata 打出来以后,错误来源马上暴露。
`source` 和 `page` 至少解决三个问题。第一,答案可以显示引用,让用户回到原文核对。第二,使用者可以知道检索是否跑偏。第三,后续可以按文档或页码过滤。不要等系统出错后才想补来源,metadata 应该从切分时就写进去。
metadata 过滤是在检索前收窄范围
文中给了一个手动过滤的例子:用户问第二讲相关问题时,检索时加上 `filter`,把 `source` 限定为第二回讲义。这样返回结果都来自对应章节,而不是在所有 Matplotlib 文档里混搜。
这类过滤比事后让模型自己判断更可靠。模型看到第一讲和第二讲混在一起,可能会努力拼出一个看似完整的回答;但如果检索阶段就把范围限定对,生成阶段的压力会小很多。
企业知识库里,`source` 可以扩展成更多字段:文档类型、部门、产品线、版本、语言、地区、发布时间、权限级别。用户问题一旦带有这些条件,就先过滤,再做向量检索。顺序很关键:先缩小可查范围,再在范围内找相似内容。

查询条件可以拆成查询词和来源条件:先按 source 过滤,再在过滤后的文档里做向量检索。
SelfQueryRetriever 适合把自然语言条件变成 filter
手动 filter 适合使用者测试,但真实用户不会按字段名提问。文中介绍的 `SelfQueryRetriever`,就是让 LLM 从问题里拆出查询词和过滤条件。示例中,问题被拆成 `query='Figure'`,同时生成 `source` 等于第二回讲义的过滤条件。
它能工作的前提,是你先定义 `metadata_field_info`。示例中定义了 `source` 和 `page` 两个字段,并写清 `source` 可能来自哪些讲义,`page` 是页码。没有字段说明,LLM 不知道可以抽取哪些条件,也不知道条件应该落到哪个字段上。
所以 `SelfQueryRetriever` 不是魔法。它依赖清楚的字段设计、稳定的字段值和足够明确的字段描述。字段乱,自动过滤也会乱。
版本和时间字段决定答案会不会过期
`source` 和 `page` 解决出处,但它们不解决时效。很多业务文档会更新:制度有新版,产品功能有新旧版本,API 参数会变,价格和权限也会调整。如果 chunk 里没有 `version`、`updated_at` 或 `effective_date`,系统很难判断哪份材料应该优先。
一个常见错误是新旧文档都在向量库里,检索时只看语义相似度。旧制度写得更像用户问题,反而排在前面。最后答案引用了过期内容,表面上有来源,实际仍然错。
版本字段不一定复杂。可以从文件名、目录、发布时间或文档管理系统中读取。关键是进入向量库前就标准化,比如 `version=2026`、`status=active`、`updated_at=2026-05-20`。检索时先排除过期材料,再让相似度排序。
权限字段不能交给 prompt 临时约束
企业 RAG 里,权限是 metadata 设计里最容易被低估的一项。不是所有用户都能看到所有文档。HR 制度、财务报表、客户合同、研发方案,哪怕语义上相关,也不一定能进入某个用户的上下文。
权限不能只写在 Prompt 里,让模型自己说“不要泄露”。更稳的是检索前过滤:当前用户属于哪些角色,能访问哪些部门、项目或密级,就把这些条件转换成 metadata filter。无权限的 chunk 不进入检索结果,也就不会进入模型上下文。
权限字段可以很简单,比如 `permission=public/internal/confidential`,或者 `allowed_roles=[hr, finance]`。复杂系统可以接 RBAC 或 ABAC,但原则不变:模型只能看到用户有权看的材料。

正式系统应把版本、权限、页码和来源放到检索前过滤与调试面板里,避免过期或无权限内容进入上下文。
字段值要稳定,否则过滤会失效
metadata 设计还有一个细节:字段名和字段值要稳定。`source` 一会儿写“第二讲”,一会儿写“第二回”,一会儿写完整路径,过滤就很难统一。`page` 有时从 0 开始,有时从 1 开始,引用也会让用户困惑。
比较稳的做法是区分展示值和系统值。系统值用稳定 ID,比如 `doc_id`、`section_id`、`page_index`;展示值保留用户能看懂的标题和页码。过滤用系统值,引用展示用展示值。
字段越早标准化,后面越省事。不要等向量库已经堆了几万条 chunk 后再清洗 metadata。那时不只是改字段,还要重建索引、重跑评估、检查旧引用。
好的 metadata 会让评估更容易
评估 RAG 时,不要只看答案。要看问题、过滤条件、命中的 `source_documents`、答案和人工判断。metadata 越完整,错误越容易归因。`source` 错了,是过滤或问题理解问题;`page` 错了,可能是切分或页码映射问题;`version` 错了,是时效过滤问题;`permission` 错了,就是安全问题。
文中反复展示检索结果和 `source_documents`,这个习惯应该保留到上线后。每条答案至少能展开看到 `source`、`page`、`score` 和 chunk 内容。企业场景再加 `version`、`permission`、`updated_at`。
metadata 做得好,RAG 出错时不是一团雾。你能知道是哪份文档进来了,为什么进来,是否应该进来。能查清楚,才有办法改。
常见问题
RAG 最少需要哪些 metadata 字段?
至少要有 source 和 page,方便引用和排查。企业场景通常还需要 doc_id、version、updated_at、permission 等字段。
metadata 过滤和向量相似度谁先谁后?
通常先用 metadata 收窄范围,再在范围内做向量相似度检索。这样能减少无关、过期或无权限内容进入上下文。
SelfQueryRetriever 能自动解决 metadata 设计吗?
不能。它需要你先定义清楚 metadata 字段和字段描述。字段混乱时,自动过滤也会不稳定。