系列:100 天系统学习 AI Agent 开发
当前阶段:RAG、知识库与工具边界
今日目标:元数据能帮助系统按日期、来源、作者、产品版本、权限范围过滤资料。

“最相关”不一定是“当前用户能看的”

假设知识库同时收录公开手册、内部排障记录和旧版文档。用户问“退款时限”,向量相似度最高的片段来自两年前的内部流程。只看相似度,它也许排第一;从版本和权限看,它根本不该进入候选。

元数据就是给文本补上可计算的上下文。正文负责“说了什么”,metadata 负责“它是谁、什么时候有效、谁可以看”。两者都要进入检索设计,但权限不能只靠 Prompt 提醒。

先定一份最小 schema

{
  "chunk_id": "refund-policy-v3-0007",
  "source": "docs/refund_policy.md",
  "version": "3.0.0",
  "created_at": "2026-06-01T08:00:00Z",
  "topic": ["refund", "after_sales"],
  "access_level": "internal",
  "tenant_id": "tenant-a",
  "is_active": true
}

字段不是越多越好。每个字段都要回答三个问题:谁负责写入,值域是否统一,什么时候更新。否则 source 一会儿存文件名、一会儿存 URL;version 有“v2”“2.0”“最新版”三种写法,过滤就会失效。

字段 用途 建议约束 常见坑
source 引用与删除 规范 URI/路径 文件移动后失联
version 版本过滤 结构化版本或独立生效时间 按字符串错误比较 10 与 9
created_at 时效判断 UTC ISO 8601 混用本地时区
topic 业务分类 受控词表,可多值 同义标签无限增长
access_level 粗粒度权限 enum 把它当唯一鉴权条件
tenant_id 租户隔离 必填且不可由模型生成 跨租户泄露
is_active 下线旧资料 boolean 删除索引却没留审计

过滤应该发生在哪

用户身份与问题

服务端生成权限过滤器

在允许范围内做向量/关键词检索

按版本、日期、主题进一步筛选

重排与生成

返回引用和 metadata 摘要

权限过滤应尽量在检索前或检索过程中生效,而不是先把机密片段取回,再让模型决定“不要说”。一旦敏感文本进入模型上下文、日志或 trace,泄露面已经扩大。服务端必须从已认证身份生成 tenant_id 和访问范围,不能接受模型或用户随意传入“access_level=admin”。

查询草案:自然语言与过滤条件分开

from dataclasses import dataclass
from datetime import datetime

@dataclass(frozen=True)
class SearchScope:
    tenant_id: str
    allowed_levels: tuple[str, ...]
    active_only: bool = True
    valid_at: datetime | None = None

def build_filter(scope: SearchScope, topic: str | None) -> dict:
    result = {
        "tenant_id": {"eq": scope.tenant_id},
        "access_level": {"in": list(scope.allowed_levels)},
    }
    if scope.active_only:
        result["is_active"] = {"eq": True}
    if topic:
        result["topic"] = {"contains": topic}
    return result

这里的过滤语法只是中立数据结构,接具体向量库时要映射为其支持的表达式。示例没有执行查询,也不假定所有数据库都支持同样操作。

版本不是“越新越好”

用户问“2.4 版本怎么部署”,正确策略是锁定 2.4,而不是只拿最新文档。更稳的设计是区分:

  • 文档自身版本;
  • 适用的产品版本范围;
  • 生效时间和失效时间;
  • 是否被另一条记录替代。

这样“历史问题”“当前规则”“指定版本”能走不同过滤条件。版本缺失时应追问或返回多版本差异,而不是默认为最新。

我会专门测的反例

  1. 两个租户有标题相同的内部文档,不能交叉召回。
  2. 旧文档相似度更高,但 is_active=false,应被排除。
  3. 用户明确指定旧版本,应允许检索对应历史资料。
  4. metadata 缺 tenant_id 的片段,默认拒绝进入生产索引。
  5. topic 标签拼写不统一,入库阶段就应校验。

元数据让我看到,RAG 不只是算法问题,也是数据治理和访问控制问题。下一步 Query Rewrite 可以改善“搜什么”,但它绝不能改写用户身份、权限和版本约束。

面试官会追问:权限过滤放在检索前还是生成后?

必须尽量在检索前完成。若先召回无权限文档,再要求模型“不要泄露”,敏感内容已经进入上下文和 trace,风险已经发生。正确做法是把 tenant_id、document_acl、department、valid_from 等字段写入索引元数据,由可信身份映射生成过滤条件,模型不能自行修改。

还要测试两种绕过:用户在问题里声称自己是管理员;同一内容存在公开旧版和私有新版。系统应只相信服务端身份,并在引用里保留文档版本。

作品集证据可以是一组 A/B 权限测试:同一个问题由普通用户和管理员查询,记录候选文档 ID、过滤条件与最终引用,证明隔离发生在检索层而不是靠一句 Prompt。

今日检查清单

  • 每个字段有类型、值域、负责人和更新规则
  • 租户与权限条件由服务端身份生成
  • 敏感数据在进入模型上下文前已过滤
  • 版本、时区和失效规则可以机器比较
  • 引用能带回 source、version 和 chunk_id
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐