RAG 的元数据过滤:比调嵌入模型见效快

2026-07-28

数据截至 2026-07,各项目能力以官方文档当前版本为准。

在多数企业知识库里,“检索不准”这件事有相当一部分不是语义没匹配上,而是根本不该被检索到的东西混了进来——三年前作废的旧制度、别的分公司的报销标准、还在草稿状态的方案。这类问题换多大的嵌入模型都解决不了,因为旧文档和新文档在语义上本来就该是相似的。真正对症的做法是给每个块挂上结构化字段,查询时先按条件把范围圈小,再在圈子里比语义。

一个流传很广的误解是:元数据过滤属于”锦上添花的工程细节”,等召回和重排都调完了再补也不迟。实际顺序常常反过来。嵌入模型换代、加重排模型、做查询重写,这些都要花时间和算力,收益还得靠评估集才能看出来;而给文档补上”所属部门、生效日期、文档类型、密级”这几个字段,多数团队在入库脚本里改几十行就能落地,效果是肉眼可见的——错误答案里那一整类”引用了过期文件”的问题会直接消失。

这篇默认你已经跑通过最小链路,如果还没有,建议先看 RAG 是什么?一文讲清给大模型外挂知识库的原理

一、元数据过滤和嵌入模型分别管什么

先把分工说清楚,很多投入错位就是从这里开始的。

嵌入模型管的是”意思像不像”。 它把一段文字压成一个向量,判断依据是语义相近。它没有能力知道这份文件是不是已经废止、提问的人有没有权限看、这条规定适用的是华东还是华南。这些信息压根不在文本语义里,或者说即使写在正文里,也会被”相似度”这个尺度稀释掉。

元数据过滤管的是”该不该被看到”。 它比的是精确条件:部门等于市场部、生效日期晚于 2025-01-01、状态不等于草稿。这是一个布尔判断,没有模糊空间。

两者不是竞争关系,而是先后关系。合理的顺序是:过滤先把候选池从十万条压到几百条,再让向量检索在这几百条里排序。反过来做——让向量在十万条里选出前二十条,再指望模型自己识别哪条过期了——就是在赌运气,而且知识库越大,这个赌局的赔率越差。

顺带说一句判断依据:RAG 框架选型那篇提到过按瓶颈定投入方向,瓶颈在解析就去改解析,在检索就去调检索。元数据过滤属于检索段里投入产出比最好的那一项,因为它不依赖任何模型能力,纯粹是数据建模问题。

二、值得挂上去的字段有哪几类

不要一上来就设计几十个字段,那会让写入端维护不动。按实际用得上的频率,值得优先考虑的是下面五类:

第一类,时间。 创建时间、生效日期、失效日期、最后修订时间。这是最高频的过滤维度,因为企业文档最典型的问题就是新旧并存。注意区分”文件的创建时间”和”制度的生效时间”,两者经常差好几个月,用错了会筛掉正确答案。

第二类,归属。 部门、分公司、产品线、项目代号。多租户或者多地区的知识库离了这个字段基本没法用——不同分公司的差旅标准在语义上几乎一模一样,纯靠向量分不开。

第三类,文档类型与状态。 是制度、合同、会议纪要、FAQ 还是产品手册;是正式发布、征求意见还是已作废。类型字段的额外价值是它能反过来影响提示词,比如检索到的是合同条款时,可以让模型回答得更保守。

第四类,权限与密级。 可见范围、密级标签。这一类下面第六节会单独展开,因为它的实现方式和其他字段有本质区别。

第五类,来源与定位。 原始文件名、文件路径或链接、页码、章节标题。这一类严格说不是用来过滤的,而是用来在答案里给出可追溯的出处。但它和过滤字段一起挂在同一份 payload 上,顺手就做了,不做的话事后补极其麻烦。

这五类里,前四类是筛选用的,第五类是溯源用的。真正需要一开始就想清楚的其实只有前两类,剩下的可以随需求逐步加。

三、元数据必须在写入时挂上,事后补不回来

这是最容易被低估的一点。向量入库之后,块和原始文档之间的对应关系如果没有存下来,想补字段就只能全量重跑。

写入端有三件事要落实:

块继承文档级字段。 一份文档切成若干块之后,每个块都要带上文档级的部门、时间、类型。这一步在框架里通常是现成的,分块环节把文档对象的 metadata 透传给每个 node 或 document 就行,但需要你确认默认行为——有些自定义切分脚本会把 metadata 丢掉,跑完才发现全空。

块级独有字段单独算。 页码、章节标题、所在表格的表头,这些是每个块各不相同的,需要在切分时就记录下来。如果你的解析器保留了版面结构,这一步几乎是白送的;如果解析出来就是一坨纯文本,那这些字段基本没救,只能回头先修解析。

更新与删除要能定位。 制度改版时旧块必须能被找到并删掉,靠的就是”文档 ID”这个元数据字段。没有它,增量更新就只能靠全量重建索引。这一条建议在第一天就做,代价最低。

顺便提一个现实的取舍:字段值尽量用枚举和标准格式,别用自由文本。部门写成”市场部""市场中心""Marketing”三种写法混着来,过滤条件就没法写;日期存成字符串还带各种格式,范围查询直接失效。这类问题在小规模时不痛,数据一多就是灾难。

四、查询时的过滤条件从哪里来

过滤字段准备好了,还有一个现实问题:用户输入的是一句自然语言,条件从哪儿变出来?常见的有三条路径,按可靠性从高到低排,实现难度恰好反过来递增。

最可靠的一条:从会话上下文直接带。 用户的账号本身就携带部门、地区、权限,登录态里有什么就往过滤条件里塞什么。这条路不需要任何模型参与,也不会出错,能用就优先用。

其次:让用户在界面上选。 在搜索框旁边放几个下拉框或标签——文档类型、时间范围、业务线。这看起来”不够智能”,但企业内部用户其实很乐意点两下换来更准的结果,而且它把不确定性完全消除了。

最后才是:让模型从提问里抽字段。 用户问”去年的差旅标准是多少”,让一个小模型把”去年”解析成日期范围。这条路能覆盖前两条覆盖不到的场景,但它有两个代价:一是抽错了就会筛掉正确答案,而且这种失败很隐蔽——系统不会报错,只会返回空或者返回一堆不相干的东西;二是多一次模型调用就多一段延迟。有第三方基准提到,LangChain 的 LCEL 虽然能做复杂的查询重写,但相比 LlamaIndex 的直接检索会多出大约 15% 到 20% 的延迟开销,这是第三方口径的测试结果、不是官方指标,但它至少说明查询前置处理的成本不是零,值得在设计时算进去。

实践里合理的组合是:权限和归属走第一条,时间和类型给第二条留入口,只有确实需要自然语言表达复杂条件时才上第三条,并且给它加兜底——抽不出条件就退回不过滤,而不是硬塞一个猜的值。

五、前置过滤、后置过滤,以及索引这件事

同样是”按条件筛”,实现方式不同,结果差别很大。

后置过滤是先做向量检索取前 K 条,再把不满足条件的剔掉。实现最简单,问题也最明显:如果符合条件的文档在整个库里占比很低,前 K 条里可能一条都不剩,最后返回空。你会看到一个很奇怪的现象——库里明明有答案,就是搜不出来。

前置过滤是先按条件把候选集圈定,再在候选集内做近似最近邻搜索。这才是我们想要的语义,但它对向量库有要求:过滤条件得能高效地和向量索引配合,否则圈定这一步本身就慢得离谱。

这就是为什么向量库的过滤能力值得单独看。以 Qdrant 为例,它支持过滤、payload 索引与量化,其中 payload 索引就是给元数据字段建索引、让条件筛选不至于退化成全表扫描;有第三方评测称它在 100 万以上向量的规模下可以做到 50ms 以内的检索,这个数字是第三方口径、且依赖具体硬件和配置,不能当成你自己环境的承诺值,但它反映的方向是对的——带过滤的检索在设计得当时并不必然变慢。Qdrant 可以作为独立二进制运行,也可以用 Docker 容器部署,数据要留在内网时这一点比较实用。更完整的选型讨论见 向量库选型:Qdrant 适合什么场景

需要提醒的是,不同向量库对过滤的支持程度差异不小,有的只做后置过滤却不明说。选型时把”我最常用的那几个过滤条件”直接拿去压测一遍,比看功能列表可靠得多。

六、四个最常踩的坑

坑一:过滤太狠,召回变成零。 条件叠了五六个,交集为空。对策是给过滤加分级——先用完整条件查一次,命中数低于阈值就放宽非关键条件(比如时间范围往前推),并且在返回结果里明确告诉用户”未找到今年的规定,以下是更早版本”。静默放宽而不告知,比返回空更危险。

坑二:权限过滤写进提示词。 把”只回答该用户有权限看到的内容”写在系统提示里,指望模型自觉。这条路不成立——内容已经进了上下文,模型说不说出来是概率问题,而且日志里已经留痕了。权限必须在检索层作为硬过滤条件执行,不合规的块从一开始就不该被取出来。这是元数据过滤里唯一一个不允许”大概正确”的场景。

坑三:字段没建索引,规模一上来就卡。 小库时随便怎么过滤都快,几十万条以后差别就出来了。上线前用接近真实规模的数据压一遍带过滤的查询,别用几千条的样本得出结论。

坑四:把过滤当成了混合检索的替代品。 元数据过滤解决的是”范围对不对”,混合检索解决的是”型号、编号这类精确字符串匹配不上”。两件事互不替代。产品型号如果只是出现在正文里而没有抽成字段,那它是混合检索的活;如果你把它抽成了结构化字段,那才轮到过滤。

七、怎么确认它真的有效

加了过滤之后,别凭感觉说”好像准了一些”。做法可以很轻:

从线上或测试集里捞二三十个答得不好的问题,人工标注每个问题的正确出处。然后跑两遍——一遍不带过滤,一遍带过滤,比较正确文档是否出现在候选里、排在第几位。这个对照实验的成本很低,但它能回答一个关键问题:你的错误里到底有多大比例属于”范围不对”,多大比例属于”排序不对”。前者归过滤管,后者归 重排管,投入方向完全不同。

如果想把这件事做成常规动作,可以接上 RAGAS 这类评估工具,把带过滤和不带过滤当成两个配置版本长期跟踪。

局限:它解决不了什么

说完好处也得说清边界。元数据过滤对下面这几种情况帮助有限:

  • 语料本身没有结构化信息。 一堆来路不明的散装文档,作者、时间、部门全都无从考证,那就没有字段可挂。这时候真正该做的是治理源头,而不是硬编字段。
  • 解析阶段就崩了。 扫描版 PDF、复杂版式的财报和法律文书,如果解析出来的文本本身就是乱的,挂再多元数据也没用。这类语料的瓶颈在解析,按第三方实践的经验,从擅长版面理解的工具入手更对症——比如 LlamaIndex 的解析能力,或者以深度文档理解见长、分块时会尊重文档结构的 RAGFlow,具体能力以各项目官方文档当前版本为准。
  • 问题本来就在生成段。 检索回来的片段都对,模型却答偏了、或者编了细节,那是 幻觉控制的范畴,跟过滤没关系。

还有一点值得诚实说明:元数据过滤把复杂度从”模型调参”转移到了”数据治理”。它见效快,但它要求上游能持续提供干净的字段。如果文档管理本身就一团糟,这套机制会慢慢腐烂——字段值越来越乱,过滤条件越来越不敢加,最后退回到不过滤。这不是技术问题,得靠流程和责任人来兜。

小结

元数据过滤之所以值得优先做,是因为它的成本主要在入库脚本里,而不在算力和模型上。先把时间、归属、类型、权限这几个字段挂稳,再谈换嵌入模型。写入端要保证块能继承文档级字段、块级字段在切分时就记录、更新删除能靠文档 ID 定位,这三件事事后补的代价远高于一开始就做。查询端优先从登录态和界面选择拿条件,让模型抽字段是最后的手段,并且必须有兜底。权限过滤是唯一不能靠提示词、必须硬执行在检索层的场景。最后,加没加效果,用一份标注好正确出处的小评估集跑对照,比任何主观感受都可靠。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。