跳转至

Vector Search

向量检索按 embedding 相似度召回语义相近的文档片段。它把「词语是否相同」换成「向量是否相近」,因此能召回同义改写,但代价是牺牲了精确匹配能力,并且相似度度量与过滤方式会直接影响结果。它解决的问题是关键词检索无法匹配同义改写,而其自身对精确字符串、生僻词的不足,又催生了 混合检索 与 Reranker 的组合。

快速开始

先固定索引版本和 query 集,作为评估基准。明确相似度度量:归一化向量可用内积等价于余弦,未归一化则需在余弦与内积之间二选一,并保证建索引与查询使用同一度量。两种度量的定义为

\[ \text{cos}(q,d)=\frac{q\cdot d}{\|q\|\,\|d\|},\qquad \text{IP}(q,d)=q\cdot d \]

余弦只关心方向不关心模长,适合「向量长度不携带语义」的场景;内积对模长敏感,且是很多 ANN 索引(如 Faiss 的 IndexFlatIP)的原生度量。

数据量大时,逐条比较(暴力检索)的成本不可接受,于是用近似最近邻(Approximate Nearest Neighbor, ANN)换召回精度以换取速度。两类主流结构:IVF(倒排文件索引)先用聚类把向量空间分成多个单元,查询只搜索最近的几个单元(nprobe 控制搜索单元数),把「全量比对」变成「子集比对」;HNSW(层次可导航小世界图,见 Malkov 与 Yashunin 的论文)构建多层图,查询从高层到低层做贪心跳转逼近近邻。前者的召回-速度权衡由 nprobe 调,后者由图连接度等参数调;Faiss 是二者最常见的开源实现。

import faiss
import numpy as np

d = 768
index = faiss.IndexHNSWFlat(d, 32)   # 32 为每节点连接数
index.hnsw.efSearch = 64             # 查询时扩展的候选数,越大召回越高越慢
index.add(doc_vectors)               # 归一化后等价于余弦检索
D, I = index.search(query_vectors, k=10)  # D 距离, I 文档 id

用固定 query 集报告三件事:Recall@k(标准片段是否进前 k)、延迟(单次查询的端到端耗时)、过滤正确性(带条件的查询是否只在正确范围内召回)。过滤正确性往往被忽略,但它是权限与多租户场景的正确性前提——ANN 索引的过滤与普通查询不同,若在召回后再硬过滤,可能因「近邻都落在过滤范围之外」而漏召回,因此更稳妥的是用支持过滤的索引(如 IndexIVFFilter 或先过滤再检索)。

评估时把「纯向量召回」与「过滤后召回」分开看:先确认过滤逻辑本身不越界、不漏,再优化相似度召回。任何索引重建或度量变更后,都回到同一 query 集复测,保证可比。

案例

用户询问退款规则,系统先按租户与文档版本做过滤,再在过滤后的范围内召回 top-k。这样既保证只搜本租户、当前版本的内容,又用向量相似度找到语义相近的退款条款。注意「过滤 -> 检索」与「检索 -> 过滤」的顺序差异:后者的召回数在过滤后可能不足 k,前者则始终在合法范围内做近邻搜索。

若过滤缺失或写错(例如租户条件漏配),向量检索会跨租户召回其他客户的条款,产生越权引用;若过滤过严,又可能漏掉本应命中的标准片段。因此过滤条件必须来自可信上下文(如登录态、文档版本号),不能依赖模型自行判断——让模型「自觉」判断租户边界是危险的,它可能因语义相似而越界。

该案例说明向量检索的正确性由「度量 + 过滤 + 索引一致性」共同决定。评估时单独报告过滤正确率,能及早暴露这类安全问题,而不是只看 Recall@k 数字是否好看——Recall@k 只度量「找没找到相关」,过滤正确率度量「有没有越界」,两者都是上线前的必查项。

相关主题