在 RAG 系统中,向量相似度检索解决了“语义相关”的问题,但真实业务场景往往还需要精确的筛选条件,例如“只检索 2024 年之后更新的政策”“只看技术部门的手册”“排除草稿状态的文档”。这类需求无法仅靠向量距离完成,必须借助元数据(metadata) 和基于元数据的过滤检索(filtered search)。
7.3.1 为什么需要元数据
如果把每个文档切片比作一本书中的段落,那么元数据就是贴在每个段落上的标签——来源文档名、所属类别、生效日期、适用范围、语言、版本号等。有了这些标签,检索就不是全库漫游,而是可以配合业务逻辑精确限定范围。
在 RAG 中,元数据至少有四大作用:
- 业务分区:不同部门、不同产品线的知识往往需要隔离,避免回答串场。
- 时效性控制:政策类文档有生效和废止时间,只检索在有效期内的片段,防止出现过时信息。
- 权限管理:部分文档仅对特定角色开放,元数据可作为权限过滤的依据。
- 检索质量提升:纯语义检索可能会召回“话题相关但类别不符”的碎片,加入元数据过滤后,结果更聚焦。
一个直观对比:用户问“今年的报销标准是多少?”,如果只做语义检索,可能会把 2020 年的旧标准也召回来(话题相似度很高)。如果加入 year=2025 的元数据过滤,就能精准锁定今年的文档。
7.3.2 元数据的设计原则
元数据的字段并非越多越好,过于复杂反而会增加维护负担和查询延迟。在设计时应遵循以下实用原则:
- 只保留与检索过滤直接相关的字段
例如 source(文档来源)、category(分类)、effective_date(生效日期)、department(归属部门)、status(发布状态)、language(语言)等。用于展示但不参与过滤的信息(如文档全文链接)可以单独存,不必写入检索过滤条件。
- 字段类型明确,值稳定
尽量使用枚举值或有限选项,避免自由文本。例如 category 可设为 ["policy","manual","faq","product"],而不是让上传者随意填写同义词(“制度”“规章”“办法”混用)。统一的值便于过滤语句编写,也便于后续统计和维护。
- 考虑分面组合查询
在实际使用中,过滤往往需要组合多个条件,如“部门=研发 AND 状态=已发布 AND 文件类型=SOP”。设计时就要考虑这些字段如何组合索引,向量数据库是否支持高效的多字段过滤。
- 预留扩展性,但不过度设计
初期从最关键的 3~5 个字段开始,业务验证后再按需添加。过度设计会导致入库流程繁琐,增加人工标注成本。
- 与文档切分策略同步规划
同一个文档的不同切片通常共享完全相同的元数据(如来自同一份 PDF),因此可以在切分阶段自动继承父文档的标签,减少手工标注。
7.3.3 常见元数据字段示例
根据大量企业 RAG 落地经验,以下字段组合能够覆盖大多数场景:
| 字段名 | 类型 | 说明 | 典型值 |
|--------|------|------|--------|
| source | 字符串 | 来源文档名或 ID | "员工手册_v2.3.pdf" |
| category | 字符串 | 内容分类 | "policy", "technical_guide", "faq" |
| department | 字符串 | 所属部门 | "HR", "Engineering", "Finance" |
| effective_date | 日期 | 生效日期 | "2025-01-15" |
| expiry_date | 日期 | 失效日期(可选) | "2025-12-31" |
| status | 字符串 | 文档状态 | "draft", "published", "archived" |
| language | 字符串 | 语言 | "zh", "en" |
| chunk_id | 字符串 | 切片的唯一标识 | "emp_handbook_chunk_042" |
7.3.4 如何在检索中加入元数据过滤
多数向量数据库(如 Milvus、Qdrant、Weaviate、Pinecone)都支持在向量相似度搜索时附加标量过滤条件。实现流程一般是:
- 索引时同时写入向量和元数据
每个切片除了生成 embedding 向量,还要带上设计好的 JSON 元数据一起存入。
- 检索时构造过滤表达式
根据业务需求生成过滤条件,例如:
status == "published"(只检索已发布内容)department in ["HR","Finance"] and effective_date <= "2025-07-01"(检索特定部门且在指定日期前生效的文档)
- 组合语义检索 + 标量过滤
向数据库发起请求时,同时传入查询向量和过滤条件,数据库先按条件筛选出候选集合,再在其中进行向量相似度排序,最后返回 top-k 结果。
以伪代码示意:
results = vector_db.search(
query_vector=embedding(question),
top_k=5,
filter={
"status": "published",
"department": "HR",
"effective_date": {"$lte": "2025-07-01"}
}
)
7.3.5 动态过滤的应用场景
在实际对话流程中,过滤条件往往来自用户身份或上下文,而非在检索时写死。例如:
- 用户角色差异:普通员工只检索
access_level="public"的文档,HR 管理员额外能看access_level="confidential"的内容。 - 时效性自动适配:系统根据当前日期自动附加
effective_date <= today and (expiry_date is null or expiry_date >= today),确保只使用有效文档。 - 多租户隔离:为不同企业客户服务时,通过
tenant_id字段强制隔离数据。
通过在查询前动态拼接过滤条件,系统可以在不改变知识库内容的前提下,为不同用户提供差异化的、合规的知识访问。
7.3.6 常见问题与应对
- 元数据缺失或错误:历史文档可能缺少有效日期等字段,导致过滤失效。需要设计入库流程强制填写关键字段,或设置默认值(如未填生效日期则视为长期有效)。
- 过滤过严导致无结果:条件过于精细可能筛掉所有候选切片,可以用宽松过滤先查一次,若结果为空则自动放宽部分条件(如移除有效期限制),并告知用户未找到最新资料。
- 日期格式不统一:需要事先规范所有日期为 ISO 8601 标准(如
YYYY-MM-DD),避免字符串比较出错。
元数据设计和过滤检索,是把 RAG 从“通用语义搜索”升级为“企业级知识服务”的关键一步。合理的元数据规划能让检索结果更精准、更合规,并且让系统随业务演化时保持灵活可控。