检索
本质上都是最近邻检索 Aproxingmate Nearest Neighbor.
ES检索(关键字检索)
通过paddle的实体抽取模型,从Query中抽取实体信息的关键字段,在ES检索的时候进行不同的加权,ES返回匹配的source文档集合,source可以认为是doc向量索引的parent_id。
向量检索
向量查询本质上是将Query向量和collection中的所有向量进行相似度匹配,所以需要确定一个相似度指标,比如欧氏距离、Cosine相似度等。
当向量都在单位球面上(即长度为1)时,直线距离(欧氏距离)越短,夹角(余弦相似度)就越小,两者是严格单调转换关系。两个方法严格成反比。
L2 归一化 (L2 Normalization): 它的作用就是把所有长长短短的向量,全部“缩放”到这个单位球的表面上。无论原来的向量多长,归一化后,它们的模长(Length/Norm)都变成了 1。
把向量投影到单位球面上(L2 归一化),实际上是会损失信息的。我们丢失了向量的“模长,但在现代深度学习(特别是 NLP 和 Embedding 模型)的语境下,“模长”通常代表噪音或无关特征,而“方向”才代表语义。 不能因量忽略质。
归一化后维度不变,数值变了。
一些索引构建方式,需要先对所有节点进行相似度计算,才能决定 谁是邻居,所以相似度算法会影响索引。
很多底层向量索引库(如 FAISS 或 Milvus 早期版本)对 L2 距离(欧氏距离) 的优化做得最好,支持的索引类型最全。 余弦相似度本质上是“内积(Inner Product)”。虽然不用归一化,但是其实将 Embedding 向量统一进行 L2 Normalize 是一个标准操作。一旦归一化了,使用内积(Inner Product)就等于余弦相似度,使用 L2 距离也等价于余弦相似度。这给了后端数据库更大的灵活性。
L2欧式距离向量相似度计算
- Cosine 相似度: 衡量的是两个向量的夹角。夹角越小,越相似(Cosine 值越大,接近 1)。
- 欧氏距离: 衡量的是两点之间直线连线(弦)的长度。
余弦向量相似度计算:
1 | /* |
稀疏检索TF-IDF BM25
![[Pasted image 20260311190635.png]]
然而,TF-IDF 存在两个明显的问题:它没有考虑文档的长度(长文档天然地具有更高的词频),并且词频的增长是线性的(一个词出现10次的重要性真的是出现5次的2倍吗?)。
为了解决这些问题,BM25 (BestMatching 25)算法应运而生。BM25 引入了两个关键的调节参数:k1和 b。
k1 参数用于控制词频的”饱和度”,即随着词频的增加,其对总分数的贡献会逐渐趋于平缓,解决了词频线性增长的问题。b 参数则用于控制文档长度的归一化程度,使得模型能够更公平地处理不同长度的文档。
稠密检索
但是还是没法处理一词多义
所以上下文感知嵌入模型产生:BERT BGE-M3
复杂架构
“漏斗式(Funnel)”混合检索架构
先ES前置检索初筛出子切片,再用3种方向的模型进行Milvus精筛,大大减少了计算量,也几乎杜绝了“跨文档误召回”(比如搜 A 合同的条款,结果搜出了 B 合同的内容)。
对source集合内的每个子doc进行更精确的向量检索匹配,我们选取了3个异质化的Embedding模型,构建了三个collection集合,在这三个collection上并行检索topk,最后再通过RRF算法进行融合截断。
RRF (Reciprocal Rank Fusion): 倒数排名融合算法
不看具体的相似度分数(因为不同模型的分数分布不同,0.8 和 0.6 没法直接比),它只看排名。如果一个文档在三个模型里都排前三,那它一定是最终的 No.1。
混合检索策略
所谓的混合检索是靠多种Embedding+索引方式实现的。
混合检索 = 路 A (稠密) + 路 B (稀疏) -> 结果融合。 它是在物理层面上维护了两套完全不同的索引结构,然后在查询层面上进行了合并。加权合并、RRF融合。
RAGFlow 支持向量检索 + 关键词检索的混合模式:
1 | requestBody.put("vector_similarity_weight", 0.7); // 向量检索权重70% |
重排序 (Reranker)
跨编码器。
这与我们在检索阶段使用的”双编码器(Bi-Encoder)”形成了鲜明对比。双编码器为査询和文档独立生成向量然后通过简单的向量运算(如点积)来计算相似度,速度极快,适合从海量数据中进行初步筛选。
而跨编码器则将査询和每个候选文档拼接在一起,作为一个整体输入给一个更强大的 Transformer 模型(如项目 retrieval-pipeline 中使用的 BAAl/bge-reranker-v2-m3),让模型在深层次上对两者之间的每一个词进行交互式、细粒度的相关性判断。这种“共同关注”的机制,使得跨编码器能够捕捉到双编码器无法感知的.更细微的语义关联,从而输出一个远比任何单一检索方法都更准确、更相关的最终排序结果。
使用 bge-reranker-v2-m3 对初步检索结果进行重排序:
Reranker 工作原理
1 |
|
re-ranking:检索回来10条结果,怎么用BGE-Reranker模型把最相关的排到前面?(这点非常加分) 向量库为了速度,用的是ANN(近似最近邻),召回的东西里经常混着垃圾。直接把这些垃圾丢给大模型,大模型就会产生幻觉。用一个专门的 Cross-Encoder 模型(如 BGE-Reranker)Cross-Encoder架构,就是把query和每个候选doc组成一个pair对,对每个pair进行相似性打分,取一个小的topk进行截断
一个形象的比喻:招聘流程
假设你要招一个“懂 Rust 的后端工程师”。
- 混合检索(HR 筛简历):
- HR 看简历(倒排索引):包含 “Rust”、”后端” 关键词的留下。
- HR 看眼缘(向量索引):经历看起来像是个高手的留下。
- 结果: 选出了 50 份简历。这里面可能混进去了“做 Rust 游戏开发的前端”(关键词中了,但方向不对)。
- 重排序(技术总监面试):
- 总监(Cross-Encoder)把这 50 个人叫来,面对面聊(深度交互)。
- 总监发现:“这个人虽然简历写了 Rust,但其实只会 Hello World,淘汰。”,“那个人虽然简历没写 Rust,但他 C++ 极强,学 Rust 只要两天,排第一。”
- 结果: 选出最终 3 个 Offer 人选。
召回重点是在topk中能把真实相关的doc给捞出来,而重排则需要把真实相关的doc排在前面,同时减少topk的数量。


