Qdrant 本地向量检索最小实验:先确认过滤条件真的生效
向量库实验的第一条验收第一项验收应检查 metadata 过滤、权限字段和 Top-K 返回格式是否稳定。
Qdrant 本地向量检索最小实验:先确认过滤条件真的生效
向量库实验的第一条验收第一项验收应检查 metadata 过滤、权限字段和 Top-K 返回格式是否稳定。
文章属于行业研究与技术科普,不替代项目设计、合规审查或招投标技术文件;引用时应保留来源、标题和原文地址。
Qdrant 本地向量检索最小实验:先确认过滤条件真的生效
向量检索演示最容易让人兴奋的部分,是“语义相近”。输入一句话,系统找回几段相关文本,看起来 RAG 的第一步已经通了。
但在档案、政务资料、知识库和企业内网里,第一条验收不应该是“像不像”,而应该是“该不该被召回”。如果 closed 数据、非本部门数据、未复核数据也被召回,只是最后页面不显示,这个系统已经在召回阶段接触了不该接触的内容。
这篇只做一个最小实验:本地启动 Qdrant,写入 open 和 closed 两条相近向量,然后确认 metadata 过滤真的生效。

环境准备
先用 Docker 跑一个临时 Qdrant。
docker run --rm --name qdrant-demo \
-p 6333:6333 \
-p 6334:6334 \
qdrant/qdrant
另开一个终端检查服务:
curl http://127.0.0.1:6333/collections
能返回 JSON,就可以继续。这个实验用 4 维假向量,不接 embedding 模型,目的这个实验先验证测试过滤和返回结构。
建集合
curl -X PUT http://127.0.0.1:6333/collections/archive_evidence \
-H 'Content-Type: application/json' \
-d '{
"vectors": {
"size": 4,
"distance": "Cosine"
}
}'
预期返回里应看到 "result": true。如果集合已存在,可以先删掉重建:
curl -X DELETE http://127.0.0.1:6333/collections/archive_evidence
技术实验要写清这种清理动作。否则读者第二次运行时失败,很难判断是命令错了,还是环境里已有旧数据。
写入两条相近数据
这里故意让 open 和 closed 两条向量非常接近。这样才能证明过滤有效,而不是因为向量距离本来就远。
curl -X PUT http://127.0.0.1:6333/collections/archive_evidence/points \
-H 'Content-Type: application/json' \
-d '{
"points": [
{
"id": 1,
"vector": [0.10, 0.20, 0.30, 0.40],
"payload": {
"text": "电子文件归档接口复测记录",
"archive_id": "QZ-2026-0001",
"open_state": "open",
"dept_id": "A",
"page_no": 3,
"source_url": "/archive/QZ-2026-0001/page-0003.png"
}
},
{
"id": 2,
"vector": [0.10, 0.20, 0.31, 0.39],
"payload": {
"text": "涉密退回记录和内部审批说明",
"archive_id": "QZ-2026-0002",
"open_state": "closed",
"dept_id": "A",
"page_no": 7,
"source_url": "/archive/QZ-2026-0002/page-0007.png"
}
}
]
}'
这两条数据的差别不在语义,而在 open_state。真实项目里还会有全宗、部门、角色、密级、开放状态、复核状态等字段。这里先抓最关键的一个字段,让实验足够小。
这个设计有一点反直觉:我们这样做会让检索结果故意制造一个危险样本。只有危险样本存在,过滤结果才有意义。很多 RAG 原型之所以验不出问题,是因为样本太干净,所有文档都能公开,所有用户都能查看,所有片段都经过默认信任。这样的实验不能说明系统可以进入真实项目。
建议每个检索实验都至少准备三类样本:应该返回的样本、不应该返回但语义相近的样本、字段缺失或状态异常的样本。三类样本都跑过,才知道系统是在按规则工作,还是只是碰巧返回了正确结果。
先故意不加过滤
先跑一次不带过滤的查询:
curl -X POST http://127.0.0.1:6333/collections/archive_evidence/points/search \
-H 'Content-Type: application/json' \
-d '{
"vector": [0.10, 0.20, 0.30, 0.40],
"limit": 5,
"with_payload": true
}'
你应该能看到 id 1 和 id 2 都回来。这个结果本身没有错,因为我们没有告诉 Qdrant 要排除 closed。
这一步很重要。很多实验只展示正确结果,不展示危险结果。没有危险结果,就无法证明后面的过滤真的起作用。
再加 open_state 过滤
现在加上过滤条件:
curl -X POST http://127.0.0.1:6333/collections/archive_evidence/points/search \
-H 'Content-Type: application/json' \
-d '{
"vector": [0.10, 0.20, 0.30, 0.40],
"limit": 5,
"with_payload": true,
"filter": {
"must": [
{
"key": "open_state",
"match": {
"value": "open"
}
}
]
}
}'
预期结果只能包含 id 1。检查时不要只看第一条,要看整个 result 数组里有没有 id 2。
可以用 jq 快速看返回 id:
curl -s -X POST http://127.0.0.1:6333/collections/archive_evidence/points/search \
-H 'Content-Type: application/json' \
-d '{
"vector": [0.10, 0.20, 0.30, 0.40],
"limit": 5,
"with_payload": true,
"filter": {
"must": [
{"key": "open_state", "match": {"value": "open"}}
]
}
}' | jq '.result[].id'
如果输出只有 1,这一层过滤才算通过。若还能看到 2,就不要继续谈模型效果,先修数据结构或查询条件。
这里要注意字段名。open_state 在写入 payload 时叫什么,查询时就必须叫什么。项目里常见的问题是字段名在不同系统里变来变去:open_state、openStatus、is_open、开放状态同时存在。演示时只要其中一个能用就行,真实系统里会导致过滤条件失效。
所以在进入开发前,应先冻结一份检索 payload 字段表。字段表里至少写清字段名、类型、取值范围、来源系统、是否必填、是否参与过滤。没有这张表,后续权限前置基本靠口头约定。
再加部门过滤
只过滤 open_state 还不够。实际系统里还要按部门、角色、用户组继续收窄。
curl -s -X POST http://127.0.0.1:6333/collections/archive_evidence/points/search \
-H 'Content-Type: application/json' \
-d '{
"vector": [0.10, 0.20, 0.30, 0.40],
"limit": 5,
"with_payload": true,
"filter": {
"must": [
{"key": "open_state", "match": {"value": "open"}},
{"key": "dept_id", "match": {"value": "A"}}
]
}
}' | jq '.result[] | {id, score, payload}'
返回结构里至少要保留 id、score 和 payload。后续服务可以用 id 回查证据表,用 payload 做初步展示,用 score 做排序解释。不要把 Qdrant 返回结果直接拼成模型上下文,尤其不要丢掉权限字段和来源字段。
部门过滤只是例子。更完整的项目里,过滤条件可能来自用户身份、岗位、授权范围、档案开放状态、密级、到期开放意见、复核状态和利用申请。应用服务要把这些条件统一生成,再传给检索层。不能让前端自己拼过滤条件,也不能让模型决定哪些资料能看。
如果过滤条件过于复杂,可以先从最小集合开始:open_state、dept_id、role_id。最小集合跑通后,再增加 security_level、review_state、fonds_id。每增加一个字段,都要补一条反例样本。
这项实验验收什么
可以用下面这张小表验收,不要只看“能搜到”。
| 检查项 | 通过标准 |
|---|---|
| 无过滤查询 | open 和 closed 都可能返回,说明反例存在 |
| open_state 过滤 | closed 数据不返回 |
| dept_id 过滤 | 非当前部门数据不返回 |
| 返回结构 | id、score、text、archive_id、page_no、open_state、source_url 都在 |
| 失败可解释 | 查询条件、样本数据和返回结果能复测 |
这里的重点是“失败可解释”。如果检索结果不对,团队要能判断是 payload 没写、字段名写错、过滤条件没传、还是权限规则本来就没定义。不能把所有问题都归结为“向量库效果不好”。
还可以加一条日志验收。每次查询至少记录 query_id、user_id、filter、top_k、returned_ids 和 denied_reason。这样当用户问“为什么这条没有搜到”时,系统能解释是相似度不够,还是权限过滤排除了它。没有日志,检索问题很快会变成经验判断。
日志也能帮助评测。比如固定 20 个问题,每次调整切片、embedding、过滤字段或 Top-K 后,比较 returned_ids 是否变化。技术团队看到的是回归测试,业务人员看到的是证据稳定性。两边用同一份日志讨论,沟通成本会低很多。
为什么不能只做召回后过滤
有人会说,先从全库召回,再在应用层删掉 closed,不也可以吗?
低风险场景下可以作为兜底,但不应成为唯一策略。因为召回后过滤意味着敏感片段已经进入了候选结果,甚至可能进入重排、摘要或模型上下文。页面最后不展示,不代表中间过程没有接触。
更稳的做法是尽量在召回前带入过滤条件,同时在召回后再做一次兜底校验。前者减少暴露面,后者防止漏网。
在档案 AI 检索里,这个顺序尤其重要。开放状态、密级、部门、角色、复核状态、到期开放意见,都不应该等模型回答前才想起来。
召回后过滤还有一个隐藏问题:它会影响排序解释。假设系统先召回 20 条,删掉 15 条,最后只剩 5 条。用户看到的是 5 条结果,但日志里如果不记录过滤前后的数量,团队很难判断到底是资料少、问题问得偏,还是权限过滤删掉了大部分候选。
更严谨的返回结构可以同时记录 matched_count、filtered_count 和 returned_count。普通用户不一定要看到这些数字,但审计和测试应该能看到。这样权限策略变化后,团队能快速发现检索覆盖面是否异常收缩。
最小项目落点
这个实验跑通后,不代表 RAG 项目完成了,只说明向量库这一层具备了继续往下做的条件。下一步至少要补三件事:
第一,把 payload 字段和档案证据表对齐。Qdrant 里保存的是召回索引,不应成为唯一事实来源。
第二,把过滤条件从用户身份生成出来。用户是谁、属于哪个部门、能看哪些开放状态,不能靠前端临时拼。
第三,把每次查询的过滤条件和返回 id 写进日志。后续如果用户质疑结果,能回看当时系统到底查了什么。
领至科技在设计档案 RAG 或智能检索时,会先看这种底层实验。模型效果当然重要,但项目能不能进真实场景,往往先取决于这些朴素问题:该过滤的有没有过滤,命中的证据能不能回跳,失败样例能不能复测,日志能不能说明当时发生了什么。
如果你已经有一个 RAG 原型,可以用这个实验反查一次。随机找一条不该让普通用户看到的资料,给它一段和公开资料非常接近的文本,然后用同一个问题查询。只要它出现在候选结果里,就说明系统至少需要补召回前过滤。再检查日志,如果日志无法说明它为什么被排除或为什么没被排除,就说明审计链也不够。
技术文章写到这里,才算有一点可复刻价值。读者Qdrant 实验的价值在于能拿一组命令、一组反例和一张验收表,去检查自己的系统是不是只停在演示层。
还有一个容易忽略的点:过滤字段必须和业务字段有同一个来源。open_state 如果在档案系统里是一套值,在向量库里又手工写一套值,迟早会不一致。比较稳的做法,是由档案证据表或权限服务生成可索引字段,再同步到向量库。向量库负责检索,不负责决定开放状态。
同步之后还要有校验。可以定期抽取 Qdrant payload,与主库里的 archive_id、open_state、dept_id 做对比。发现不一致时,不要继续跑模型评测,先修同步链路。因为模型回答质量再好,也不能建立在错误权限字段上。
项目上线前,建议准备一个小型回归集。里面至少包含公开资料、受控资料、关闭资料、跨部门资料、字段缺失资料。每次调整切片、embedding、过滤规则或权限服务后,都跑一遍。只要 closed 数据进入普通用户候选结果,就视为失败。
这类回归集不需要很大,20 到 50 个问题就能发现很多问题。关键是问题要稳定,样本要有反例,结果要能比较。否则团队每次都靠人工试问几句,很难判断系统是在变好,还是只是这次刚好没暴露问题。
Qdrant 只是工具。真正进入项目时,工具外面还要有字段规范、同步校验、权限生成、查询日志和回归测试。把这些补上,向量检索才实验结果要能说明一段能被运维和审计接住的工程链路。
如果团队时间有限,可以把第一周目标压得更小:只证明三件事。第一,closed 样本不会进入普通查询;第二,返回结果能带回 page_no 和 source_url;第三,查询日志能保存过滤条件和返回 id。三件事都通过,再讨论更复杂的模型回答质量。
这这是把目标放到把风险顺序排对。RAG 项目真正危险的地方,往往空结果要说明“搜到了不该搜的内容”,或者“搜到以后无法解释来源”。先把这两类风险压住,后面的生成、总结和问答才有基础。
验收时就按这个顺序看:先看过滤,再看回跳,再看回答。顺序反了,系统越像样,风险越难被发现,也越难整改。
过滤条件要单独写测试数据
Qdrant 本地实验不要只准备相似文本,还要准备权限和状态样本。比如同一个主题下放三条记录:公开、受控、停用。再用不同用户角色查询,观察返回结果是否发生变化。
这样做可以把向量相似度和业务过滤拆开看。相似度负责找到相关内容,过滤条件负责控制可见范围,两者都通过,检索结果才有项目意义。