本节摘要:只搜"最像的 K 条"解决不了真实业务。一个用户想找"红色、五百到一千元之间、库存不为零"的商品,就需要把向量相似度和字段过滤叠在一起。Qdrant 的高级查询接口就是干这件事的:它用过滤对象把多个精确条件用与、或、非组合起来,再和向量搜索合流执行。这一节拆开过滤条件、搜索参数、推荐与发现三类能力,讲清它们各自的用法和代价。读完你能写出带业务约束的精确检索,并知道
recommend和discover分别解决什么问题。
阅读完本节,你应当能够:
search、recommend、discover 三种查询的语义差异。向量搜索很擅长回答"什么跟这个像",但它天生回答不了"哪些是红色""哪些是今年新上的""哪些还没下架"。这类问题靠的是字段的精确匹配,跟"像不像"完全是两码事。
真实业务里,这两种需求几乎总是同时出现。电商搜"跟用户历史购买相似、且是最新款、折扣力度大"的商品;知识库搜"跟这个问题语义相关、且来自特定部门、发布于去年"的文档。你既要有语义上的"近",又要有字段上的"准"。
如果先把向量搜出来一百条,再在应用里一条条过滤字段,问题就来了:你可能搜出一百条里只有三条符合字段条件,真正该进的候选反而因为"不够像"被排在一百名之外,根本进不了你的过滤视野。这就是"先搜后筛"的召回陷阱。Qdrant 的做法是把过滤条件揉进搜索过程里,让不符合字段条件的点,从一开始就不参与相似度计算。
过滤的核心是一个过滤对象,它用三个字段表达三种逻辑:必须满足、至少满足其一、必须不满足。这三个字段分别对应逻辑与、或、非。你可以把它们任意嵌套——一个"必须满足"里再套一层"至少满足其一",就能表达"红色 且(耐克 或 阿迪)且 价格在区间内"这种多条件。
这个"组合—嵌套"的能力是过滤的精髓。它让你把业务规则直接翻译成查询结构,而不是在应用层拼一堆 if 判断。
原子条件有几种,对应不同的数据类型。
匹配条件用于精确或模糊匹配一个值,也可以匹配一个值列表里的任意一个,或者对文本字段做全文匹配。范围条件针对数值字段,比如价格、评分、时间戳,设上下界。地理条件针对经纬度,可以圈一个矩形区域,也可以以某点为圆心划半径。ID 条件直接指定要哪些点或排除哪些点。空值条件检查某个字段是否存在或为空。
| 条件类型 | 作用 | 典型例子 |
|---|---|---|
| 匹配 | 精确或模糊匹配值 | 颜色等于红、标签含运动 |
| 范围 | 数值上下界 | 价格在五百到一千之间 |
| 地理 | 经纬度区域 | 半径五公里内的门店 |
| ID | 指定或排除点 | 只看某几个编号 |
| 空值 | 字段存在性 | 找出没有标签的记录 |
这几种条件不是互斥的,实际查询里经常混用。比如"距离我五公里内、评分大于四点五、且营业中"的咖啡馆,就同时用上了地理、范围、匹配三类条件。关键在于,你要把业务里那句自然语言的筛选要求,翻译成过滤对象里的结构化条件——这一步做对,查询就成了一半。剩下的另一半,是别把"应该用过滤表达"的硬约束,漏到了应用层去手动判断。
除了过滤,搜索接口还有一堆参数控制结果形态。返回数量上限和偏移量配合实现分页;分数阈值把相似度不够的候选挡在门外;是否返回载荷、是否返回向量,决定结果里带多少信息,直接影响网络传输量。多向量场景下还能指定对哪条命名向量搜索。分组参数则能把结果按某个字段去重,比如按品牌分组、每组只取一个代表,避免推荐结果被某个品牌刷屏。
recommend 和 discover 是两个容易被低估的接口。它们都建立在向量搜索之上,但引入了"正例"和"负例"的概念。
recommend 的用法是:给几个你喜欢的点作为正例、几个不喜欢的作为负例,系统聚合正例向量、排除负例的影响,返回一批"像你喜欢的、不像你讨厌的"新结果。它适合"猜你喜欢"这类场景,比单条查询向量更能表达用户的偏好方向。
discover 则更偏探索。你通过一组正例和负例,在向量空间里指定一个"语义方向",去发现这个方向上的新东西,同时还能叠加字段过滤。比如用几篇"AI 医疗"文章做正例、"传统医疗器械"做负例,再限"去年之后发表",去挖最新的研究方向。
两者的区别可以这样记:recommend 是"给我推荐我大概率会喜欢的",discover 是"带我去看看这个方向还有什么我不知道的"。
⚠️ 常见坑:过滤条件里用到的字段,如果没有建对应索引,查询会退化成全量扫描,数据量大时延迟会很难看。设计数据模型时就要想清楚哪些字段将来要参与过滤,提前为它们建索引,而不是等查询慢了才回头补。
💡 关键直觉:过滤不是搜索的"附加项",而是搜索的一部分。让不符合条件的点根本不进相似度计算,才是高性能混合检索的正解。先搜后筛,等于放弃了 Qdrant 最核心的那层优化。
性能调优上还有一个经验:能用字段过滤表达的硬性约束,就别把它放到应用层去做。把业务规则翻译成过滤结构,既省网络、又省计算,还能让结果更可解释——你知道这条结果为什么被返回。
再往细说,过滤条件的先后也影响执行。把选择性最强、能最快缩小候选集的条件放在前面,能减少后续相似度计算的压力。这个优化在数据量大、条件多时才有明显收益,小数据量下不必过度纠结——先保证正确,再谈优化,这个顺序永远不能颠倒。
分页用 offset 靠谱吗? 浅层翻页可以,翻到很后面就变慢,因为偏移量是"先算出前 N 个再跳过"。要深度遍历大量结果时,用滚动式分页更稳,它靠上一次返回的游标继续往下拉,不重复计算前面的结果。
分数阈值设多少合适? 这没有统一答案,取决于你的距离度量和数据分布。余弦相似度下,文本检索常见阈值在零点几这个量级,但具体值要靠你的数据集实测——拿一批真实查询,看"相关"和"不相关"结果落在什么分数段,再切一刀。拍脑袋设阈值,要么漏掉相关的,要么放进一堆噪声。
分组能替代过滤吗? 不能。分组是在已召回的结果里按字段去重,解决的是"多样性";过滤解决的是"准入条件"。两者配合才有意义:先过滤圈定候选,再分组保证每组都有代表,别让某个强势值霸榜。
批量查询用在什么时候? 当你一次要为多个用户或多个问题同时做检索时,把多个独立查询打包成一次批量请求,服务端并行处理,能省掉一次次往返的延迟。实时推荐、批量打分这类场景尤其受益。
推荐和搜索什么时候互换? 用户有一条明确的查询向量、就是要找"最像的",用搜索;用户没有明确查询、只想基于历史偏好猜他喜欢什么,用推荐;想往某个方向探索未知,用发现。三者的边界,在"你手上有没有一条明确的查询向量"。
多向量场景怎么指定? 一个集合可以存多条不同用途的向量,比如正文向量和标题向量。查询时如果不指定对哪条向量搜,很可能搜到你不想要的那条。多向量场景下,明确"搜哪条向量"和"返回哪些字段"同样重要,别让两条向量混着算相似度。
结果里的分数能直接比较吗? 只能在同一集合、同一距离度量下比。跨集合、跨度量的分数没有可比性,别拿两个不同配置的分数去排序。分数是相对的,不是一个绝对值。
排查查询问题时,第一个要确认的是"过滤字段有没有建索引"。没建索引的字段过滤,数据量一大查询会慢得反常,这往往是最容易漏的一环。第二个要确认的是距离度量:如果你建集合时用余弦,查询时却按点积的直觉去理解分数,会得出错误结论——余弦看方向,点积看大小和方向,两者对"高分"的定义不一样。
第三个误区是拿返回顺序当"绝对正确"。近似最近邻搜索追求的是"够近"而非"绝对最近",所以偶尔漏掉一两条真正最近的,是算法的正常表现,不是 bug。如果业务对召回率要求极高,要在搜索范围参数上做取舍,牺牲一点速度换更高召回。
第四个是忽略过滤与排序的先后关系。过滤先圈定候选,向量相似度再在候选里排序,最终返回的是"既符合条件、又最像"的那批。把过滤漏了,或把排序想成单纯按字段排,结果都会跑偏。理解这条"先准入、后排序"的顺序,能帮你快速判断一次查询到底对不对。调试时一次只加一个条件、逐步观察结果怎么变,也比一次写全所有参数更容易定位问题。
下一节我们回到数据的最上游——向量到底从哪来。文本、图片是怎么变成向量的,嵌入模型又怎么接进这条查询链里。