从零理解 Milvus:从向量数据库到 RAG、Hybrid Search 与 Reranker

如果你刚开始接触 Milvus,很容易被一堆名词劝退:向量、Embedding、Collection、Schema、Index、HNSW、IVF、Sparse Vector、Hybrid Search、Reranker、RAG。

这些词看起来很多,但背后其实是一条很清楚的主线:

text 复制代码
把文本、图片、音频等内容变成向量
→ 用 Milvus 存储和检索这些向量
→ 找到和用户问题最相关的资料
→ 交给大模型生成答案

这篇文章会从最基础的直觉开始讲,一直讲到比较真实的 RAG 检索架构:dense 检索、sparse 检索、hybrid search、RRF 融合、reranker 重排、权限过滤、引用来源和常见问题排查。

目标不是堆 API,而是让你真正知道:Milvus 在系统里到底干什么,为什么需要索引,为什么需要 hybrid search,为什么 Milvus 搜完以后还要 reranker。

一、为什么需要 Milvus

传统数据库很擅长回答确定性问题。

比如:

sql 复制代码
select * from products where category = 'phone' and price < 5000;

这种查询有明确条件:

text 复制代码
category 等于什么
price 小于多少
id 是多少
status 是不是 published

但是现在很多 AI 应用想问的问题不是这种。

比如:

text 复制代码
找一些和"怎么提升向量检索召回率"语义相近的文章
找和这张图片视觉上相似的图片
根据用户历史行为推荐相似商品
在公司知识库里找最可能回答这个问题的文档片段

这类问题的核心不是"字段等于某个值",而是:

text 复制代码
谁和谁更相似?

这就是向量数据库的用武之地。

Milvus 是一个开源向量数据库。它的核心能力是:

text 复制代码
存储向量
索引向量
搜索相似向量
管理向量相关的元数据

一个非常重要的分工是:

text 复制代码
Embedding 模型负责理解内容,把内容变成向量。
Milvus 负责管理向量,并快速找相似向量。
LLM 或业务系统负责基于检索结果回答、推荐或展示。

Milvus 本身不负责"理解中文"或"理解图片",它看到的是数字向量。真正把"苹果手机"和"iPhone"放到相近位置的,是 embedding 模型。

二、向量和 Embedding 到底是什么

向量可以先理解成一组数字。

比如用三个数字描述一个人:

text 复制代码
[身高, 体重, 年龄]
[175, 70, 28]

这就是一个 3 维向量。

文本也可以被模型变成向量:

text 复制代码
"Milvus 是一个向量数据库"
→ [0.13, -0.27, 0.91, ...]

真实 embedding 通常不是 3 维,而可能是:

text 复制代码
384 维
768 维
1024 维
1536 维

Embedding 就是把复杂对象转换成向量的过程或结果。复杂对象可以是:

text 复制代码
文本
图片
音频
视频
商品
用户行为
代码片段

好的 embedding 模型会让语义相近的内容在向量空间里更接近。

比如:

text 复制代码
"我喜欢喝咖啡"
"我想来一杯拿铁"

这两句话字面不一样,但意思接近。好的 embedding 模型会让它们的向量距离比较近。

而:

text 复制代码
"我喜欢喝咖啡"
"汽车发动机需要保养"

语义差很远,对应向量距离也应该更远。

所以向量检索的核心问题可以写成:

text 复制代码
给定一个查询向量 Q,在数据库里找到和 Q 最接近的 K 个向量。

这就是 Milvus 每天在做的事情。

三、Metric:Milvus 怎么判断两个向量像不像

两个向量是否相似,需要一个计算标准。Milvus 里常见 metric 有:

text 复制代码
L2
IP
COSINE

L2:欧氏距离

L2 可以理解成几何距离。

text 复制代码
距离越小,越相似

如果两个点在空间里站得近,L2 距离就小。

IP:内积

IP 是 inner product。

text 复制代码
分数越大,越相似

IP 会受到向量长度影响,在推荐召回、深度模型召回里比较常见。

COSINE:余弦相似度

COSINE 更关注两个向量的方向是否一致。

text 复制代码
方向越接近,越相似

文本语义搜索中,COSINE 很常见。因为很多文本 embedding 更强调语义方向。

一句话记忆:

text 复制代码
L2:看距离,越小越像。
IP:看内积,越大越像。
COSINE:看方向,越同向越像。

注意,metric 只是数学比较方式。真正让文本拥有语义关系的,仍然是 embedding 模型。

四、Milvus 的数据模型:Collection、Field、Schema

Milvus 的数据组织可以类比传统数据库。

text 复制代码
Collection:类似一张表
Field:类似字段 / 列
Schema:定义这张表有哪些字段

比如我们要做一个知识库搜索系统,可以建一个 collection:

text 复制代码
knowledge_chunks

每一条数据代表一个文档片段。

一个最小 schema 可能是:

text 复制代码
id        INT64          主键
text      VARCHAR        原始文本
chapter   VARCHAR        章节
vector    FLOAT_VECTOR   文本向量

真实 RAG 系统里会更复杂:

text 复制代码
id
doc_id
chunk_id
text
tenant_id
permission_group
status
source
page
section
metadata
vector

其中:

text 复制代码
vector 用于相似搜索。
text 用于返回给 LLM。
doc_id 用于文档级更新和删除。
tenant_id / permission_group / status 用于权限过滤。
source / page / section 用于引用来源。
metadata 用于存灵活附加信息。

一个核心注意点:

text 复制代码
向量字段的 dim 必须和 embedding 模型输出维度一致。

如果模型输出 1024 维,Milvus schema 里也必须是 1024 维。

更重要的是:即使两个模型都输出 1024 维,也不能把不同模型生成的向量混在同一个 vector 字段里。因为它们的向量空间不同,数字坐标没有可比性。

五、精确搜索和近似搜索

假设 Milvus 里有 100 万个向量。

最朴素的搜索方式是:

text 复制代码
查询向量 Q 和第 1 个向量比较
查询向量 Q 和第 2 个向量比较
...
查询向量 Q 和第 1,000,000 个向量比较
排序后返回 Top K

这叫精确搜索,也就是暴力搜索。

优点:

text 复制代码
结果最准

缺点:

text 复制代码
数据大时很慢

向量数据库之所以需要索引,是因为大多数真实场景无法每次都全量比较。

近似最近邻搜索叫 ANN:

text 复制代码
Approximate Nearest Neighbor

它的目标是:

text 复制代码
用很高概率找到足够好的近邻,同时大幅减少计算量。

这里有一个永恒取舍:

text 复制代码
更准:通常更慢。
更快:可能漏掉少量最优结果。
更省资源:可能牺牲速度或召回。

六、索引:FLAT、HNSW、IVF_FLAT

索引是 Milvus 搜索快起来的关键。

不同索引不是谁绝对更好,而是适合不同数据规模、延迟要求、内存预算和召回要求。

FLAT:全量比较,最准确

FLAT 可以理解成精确搜索。

它不做复杂近似结构,而是把查询向量和所有向量都比较一遍。

特点:

text 复制代码
最准
简单
小数据很方便
大数据会慢

FLAT 很适合做基准。

比如你想知道 HNSW 参数调得准不准,可以用 FLAT 跑标准答案,再看 HNSW 返回结果和 FLAT 有多少重合。

HNSW:图导航

HNSW 是一种基于图结构的近似最近邻索引。

可以把它想象成:

text 复制代码
提前把相似向量点之间修路。
查询时沿着路一步步走向更相似的区域。

HNSW 不看文本关键词,它只看向量之间的距离或相似度。

建索引时,新点会在已有图中寻找候选邻居,然后根据向量相似度选择要连接的点。它通常不是简单地连接最近的 M 个点,还会尽量让图结构保持一定的导航能力,避免所有边都挤在一个小区域。

查询时,用户问题先变成查询向量 Q。HNSW 从入口点开始,看当前点的邻居谁更接近 Q,然后不断向更接近的方向移动。

HNSW 的三个核心参数:

text 复制代码
M:每个点最多保留多少邻居连接。
efConstruction:建图时寻找候选邻居有多认真。
ef:查询时探索候选点有多认真。

直觉版:

text 复制代码
M:路网密度。
efConstruction:修路质量。
ef:查询耐心。

调参影响:

text 复制代码
M 越大,图越密,召回通常更好,但内存更高,构建更慢。
efConstruction 越大,建图质量更好,但建索引更慢。
ef 越大,查询更充分,召回更高,但延迟更高。

一个常见起步值:

python 复制代码
M = 16
efConstruction = 200
ef = 64

如果搜索不够准,通常先调大 ef,因为它是查询参数,不需要重建索引。

IVF_FLAT:先分簇,再搜部分簇

IVF_FLAT 的核心思想是:

text 复制代码
先把向量空间分成多个簇。
查询时只搜索最可能相关的几个簇。
进入簇以后再做精确比较。

它像是把城市划成很多片区。查询时先判断用户要找的地方大概在哪几个片区,再去这些片区里找。

核心参数:

text 复制代码
nlist:建索引时分成多少个簇。
nprobe:查询时搜索多少个簇。

直觉版:

text 复制代码
nlist:城市划成多少片区。
nprobe:查询时去几个片区找。

调参影响:

text 复制代码
nlist 越大,分得越细。
nprobe 越大,搜索的簇越多,召回更高,但查询更慢。

HNSW 和 IVF_FLAT 的区别:

text 复制代码
HNSW:图导航。
IVF_FLAT:聚类分桶。

七、Recall、TopK 和 Score

向量检索里经常会看 Recall。

Recall 表示:

text 复制代码
真正应该找回来的结果里,实际找回了多少。

通常用 FLAT 当标准答案。

比如:

text 复制代码
FLAT Top 5: A, B, C, D, E
HNSW Top 5: A, B, D, X, Y

重合的是:

text 复制代码
A, B, D

所以:

text 复制代码
Recall@5 = 3 / 5 = 60%

HNSW 的 ef、IVF_FLAT 的 nprobe 调大,Recall 通常会上升,但延迟也会上升。

limittop_k 表示最终返回多少条结果。

注意:

text 复制代码
limit 不是搜索范围,而是最终返回数量。

真正影响搜索探索范围的是:

text 复制代码
HNSW 的 ef
IVF_FLAT 的 nprobe

hit["distance"] 表示相似度分数或距离,含义取决于 metric:

text 复制代码
COSINE:通常越大越相似。
IP:通常越大越相似。
L2:越小越相似。

不要拿固定阈值乱套所有数据。比如 score > 0.7 才相关,这种阈值必须根据自己的模型、数据和真实问题校准。

八、Partition、Segment、Load:Milvus 怎么组织和查询数据

Partition

Partition 是 collection 下的数据分区。

它适合:

text 复制代码
分组边界稳定
查询经常只搜某些分组
数据生命周期不同
希望减少加载或搜索范围

但不要滥用 partition。

比如不要每个用户一个 partition。如果有 100 万用户,partition 数量会爆炸。更常见做法是用字段过滤:

python 复制代码
filter='user_id == "u_123"'

Segment

Segment 是 Milvus 内部的数据块。

新数据写入时通常先进入:

text 复制代码
growing segment

稳定后变成:

text 复制代码
sealed segment

sealed segment 更适合构建索引和稳定查询。

Load

Load 表示把 collection 或 partition 加载到可查询状态。

如果你遇到:

text 复制代码
Collection is in state released; call load() before search/get/query

说明数据还在,但没有被加载到查询服务里,需要:

python 复制代码
client.load_collection(collection_name=COLLECTION_NAME)

Release 则是释放查询资源,数据不会被删除。

九、写入、删除、Upsert 和 Compaction

向量数据库里的写入和删除,不是简单地"改一行文件"。

Insert

插入数据时,通常包括:

text 复制代码
主键
原文
元数据
向量

文本必须先生成 embedding,才能用于向量搜索。

Delete

删除通常先是逻辑删除:

text 复制代码
查询时不可见。
底层空间不一定马上回收。

这和很多存储系统类似。立即改大文件或更新复杂索引成本很高,所以先记录删除标记,后续通过 compaction 清理。

Upsert

Upsert 表示:

text 复制代码
有则更新,无则插入。

如果文本更新了,embedding 必须重新生成。

否则会出现很诡异的状态:

text 复制代码
text 是新的
vector 还是旧的

搜索时 Milvus 看的是 vector,不是 text。向量没更新,搜索结果就会和新文本不匹配。

Compaction

Compaction 可以理解成后台整理:

text 复制代码
合并小 segment
清理已删除数据
减少碎片
回收部分空间
优化查询效率

所以删除后磁盘空间没有立刻下降,不一定是 delete 没生效。判断删除是否生效,应该看 get/query/search 是否还能看到数据。

十、Search、Query、Get 的区别

Milvus 不只有向量搜索。

Search 是向量相似搜索:

text 复制代码
给一个 query vector,找最相似的 Top K。

Query

Query 是标量条件查询:

python 复制代码
filter='chapter == "index"'

它不是按语义相似找,而是按字段条件找。

Get

Get 是按主键精确获取:

python 复制代码
client.get(collection_name=COLLECTION_NAME, ids=[5])

三者区别:

text 复制代码
search:向量相似。
query:字段条件。
get:主键精确获取。

十一、字段过滤:RAG 的安全边界

字段过滤用于限制候选集合。

比如:

python 复制代码
filter='chapter == "index"'

真实企业 RAG 中,filter 更常用于权限控制:

text 复制代码
tenant_id
permission_group
status
doc_id
language
created_at

一个典型过滤条件:

python 复制代码
filter = (
    'tenant_id == "company_a" '
    'and status == "published" '
    'and permission_group in ["public", "engineering"]'
)

这表示只在当前用户有权限访问的文档里搜索。

权限过滤应该放在 Milvus search 阶段,而不是搜索完以后再过滤展示。

如果先全库搜索,再把无权限结果丢掉,会有几个问题:

text 复制代码
无权限内容可能影响排序。
过滤后剩下结果太少甚至为空。
中间日志或调试输出可能泄露敏感内容。

正确姿势是:

text 复制代码
先权限过滤,再向量搜索。

十二、RAG:Milvus 在大模型应用里的位置

RAG 是 Retrieval-Augmented Generation,检索增强生成。

它的核心思想是:

text 复制代码
大模型回答问题之前,先去知识库检索相关资料,再基于资料回答。

为什么需要 RAG?

因为大模型直接回答有几个问题:

text 复制代码
不知道你的私有文档。
知识可能不是最新。
容易编造。
上下文窗口有限。
缺少来源引用。

RAG 的典型流程:

text 复制代码
文档
→ 解析
→ 切块
→ 生成 embedding
→ 写入 Milvus

查询时:

text 复制代码
用户问题
→ 生成 query embedding
→ Milvus 搜索相关 chunks
→ 构造上下文
→ LLM 基于上下文回答

Milvus 在这里负责:

text 复制代码
找资料。

LLM 负责:

text 复制代码
组织答案。

Embedding 模型负责:

text 复制代码
把问题和文档放进同一个向量空间。

十三、Chunking:RAG 效果的地基

Milvus 搜的是 chunk 向量,不是整篇文档本身。

如果整篇 30 页 PDF 只生成一个向量,很多主题会混在一起,embedding 会被"平均掉"。用户问 HNSW,文档里也许确实有 HNSW,但整篇向量可能同时表达了 Collection、Schema、Partition、RAG,结果就不够精准。

所以要切块。

chunk 太大:

text 复制代码
主题混杂
embedding 被稀释
噪音多
LLM 容易被干扰

chunk 太小:

text 复制代码
上下文不足
指代不清
语义不完整
答案缺信息

好的 chunk 应该:

text 复制代码
主题单一
语义完整
长度适中
保留标题和来源信息
不要切碎表格、代码块、列表

常见起步:

text 复制代码
chunk_size = 300 到 800 tokens
overlap = 50 tokens

标题通常应该放进 chunk。

比如不要只存:

text 复制代码
M 控制每个点保留的邻居数量。

更好是:

text 复制代码
Milvus > Index > HNSW 参数
M 控制每个点保留的邻居数量。

这样 embedding 更明确,LLM 回答也更清楚。

十四、RAG Collection 应该怎么建模

一个真实 RAG collection 通常可以叫:

text 复制代码
knowledge_chunks

推荐字段:

text 复制代码
id
doc_id
chunk_id
text
tenant_id
permission_group
status
source
page
section
created_at
metadata
vector

为什么需要这些字段?

doc_id 用于文档级删除和更新。

如果一篇文档被删除:

python 复制代码
client.delete(
    collection_name="knowledge_chunks",
    filter='doc_id == "doc_123"',
)

chunk_id 用于追踪 chunk 在原文里的顺序。

sourcepagesection 用于引用来源。

tenant_idpermission_groupstatus 用于权限过滤。

metadata 用于灵活附加信息,比如:

json 复制代码
{
  "file_type": "pdf",
  "language": "zh",
  "parser": "marker",
  "tags": ["milvus", "index"]
}

字段设计的原则:

text 复制代码
稳定且常过滤的字段,放顶层。
变化多、展示用、追踪用的信息,放 metadata JSON。

十五、文档更新与增量同步

真实知识库不是一次性导入就结束。

文档会:

text 复制代码
新增
修改
删除
权限变化
状态变化
重新解析

新增文档:

text 复制代码
读取文档
→ 解析文本
→ 切成 chunks
→ 每个 chunk 生成 embedding
→ 插入 Milvus

删除文档:

python 复制代码
client.delete(
    collection_name="knowledge_chunks",
    filter='doc_id == "doc_123"',
)

修改文档时,通常不要只更新某一条 chunk。

因为文档一改:

text 复制代码
chunk 边界可能变
chunk 数量可能变
页码可能变
section 可能变
embedding 必须重算

更可靠的做法是文档级重建:

text 复制代码
删除旧 doc_id 的所有 chunks
重新解析新文档
重新切块
重新生成 embedding
重新插入

如果只是元数据变化,比如状态从 draft 变成 published,文本没变,则 embedding 可以复用。

关键判断:

text 复制代码
文本变了,embedding 必须重算。
只有元数据变了,向量可以复用。

十六、引用来源:让 RAG 答案可追溯

RAG 不应该只给答案,还应该告诉用户答案来自哪里。

为了支持引用,Milvus 里的 chunk 应该存:

text 复制代码
source
doc_id
page
section
chunk_id
url
title

构造上下文时,可以这样:

text 复制代码
[参考资料 1]
来源: Milvus Index Guide.pdf
页码: 8
章节: HNSW 参数
内容: M 控制每个点保留的邻居数量...

Prompt 中要求:

text 复制代码
回答时请引用使用到的参考资料编号。

最终答案:

text 复制代码
HNSW 中,M 控制每个点保留的邻居连接数;ef 控制查询时探索的候选范围。[参考资料 1][参考资料 2]

关键点:

text 复制代码
不要让 LLM 自己编来源。
来源应该来自 Milvus 返回的 metadata。

十七、答案边界:RAG 不是让模型自由发挥

RAG 的目标是:

text 复制代码
基于检索资料回答。

不是:

text 复制代码
检索一些资料后,让模型凭常识补全。

Prompt 里应该明确:

text 复制代码
只使用参考资料中的信息回答。
如果参考资料不足,请说明无法从知识库中确定。
不要编造参考资料外的信息。
回答时引用参考资料编号。

一个简单模板:

text 复制代码
请基于下面的参考资料回答用户问题。

要求:
1. 只使用参考资料中的信息回答。
2. 如果参考资料不足,请明确说明"无法从知识库中确定"。
3. 不要编造参考资料外的事实。
4. 回答要简洁、准确。
5. 如果使用了参考资料,请标注对应编号。

参考资料:
{context}

用户问题:
{question}

允许模型说"不知道"非常重要。

如果资料里没有答案,正确回答应该是:

text 复制代码
根据当前知识库资料,无法确定答案。

这比编一个看似合理的答案更可靠。

十八、上下文窗口:不是塞得越多越好

Milvus 可以召回 Top 20、Top 50,甚至更多。

但 LLM 不一定适合读这么多。

原因:

text 复制代码
上下文窗口有限。
无关内容会干扰模型。
输入 token 越多,延迟和成本越高。

常见策略:

text 复制代码
限制 limit,比如 Top 3 或 Top 5。
先多召回,再 rerank。
按分数阈值过滤。
去重。
摘要压缩。

目标不是"多",而是:

text 复制代码
相关、完整、低噪音。

十九、Hybrid Search:Dense + Sparse

普通向量检索通常是 dense vector search。

Dense vector 的特点:

text 复制代码
维度固定
大多数位置都有值
擅长语义相似

它适合处理:

text 复制代码
"如何降低查询延迟"
≈
"怎么让检索更快"

但 dense 有时对专有名词、参数名、API 名不够稳。

比如用户问:

text 复制代码
nprobe 变大会怎么样?

Dense 可能知道这是"索引调参、召回、延迟"的问题,但如果知识库里有很多类似参数:

text 复制代码
nprobe
ef
M
efConstruction
nlist

它可能把相关但不是目标的内容也排上来。

Sparse vector 更接近关键词权重。

它擅长:

text 复制代码
专有名词
缩写
参数名
API 名
错误码
产品型号

Hybrid Search 就是:

text 复制代码
Dense Vector Search
+
Sparse Vector Search
+
结果融合

Dense 负责语义。

Sparse 负责关键词和术语。

融合负责把两路结果合并成最终排序。

二十、Milvus 原生 Hybrid:dense_vector + sparse_vector

在 Milvus 里,真正的 hybrid schema 可以是:

text 复制代码
id
text
chapter
dense_vector    FLOAT_VECTOR
sparse_vector   SPARSE_FLOAT_VECTOR

dense_vector 存稠密向量。

sparse_vector 存稀疏向量。

稀疏向量不是普通 list,因为维度可能极高,大部分都是 0。更合理的表示是只存非零项:

python 复制代码
{
    102: 0.8,
    9012: 1.4,
    50123: 0.6,
}

意思是:

text 复制代码
第 102 维权重 0.8
第 9012 维权重 1.4
第 50123 维权重 0.6
其他维度都是 0

如果使用阿里百炼的 text-embedding-v4,可以让它输出:

text 复制代码
dense
sparse
dense&sparse

这样就能统一得到 dense 和 sparse。

入库时:

text 复制代码
text_type = document

查询时:

text 复制代码
text_type = query

一个关键点:

text 复制代码
真正难点不只是 Milvus 字段,而是 sparse vector 从哪里生成,以及格式是否符合 Milvus 要求。

DashScope 返回的 sparse 可能是 index/value 列表,也可能是类似字典的结构。最终需要转换成 Milvus 可接受的:

python 复制代码
dict[int, float]

二十一、RRF:多路检索怎么融合

Hybrid Search 有两路结果:

text 复制代码
dense results
sparse results

这两路分数往往不可直接相加。

Dense 的 COSINE 分数可能是:

text 复制代码
0.82
0.77
0.71

Sparse 的 IP 或 BM25 分数可能是:

text 复制代码
13.5
7.2
2.1

尺度完全不同。

RRF 是 Reciprocal Rank Fusion,倒数排名融合。

它不直接看原始分数,而看排名。

公式:

text 复制代码
score = 1 / (k + rank)

其中:

text 复制代码
rank 从 1 开始
k 常见取 60

如果一条文档同时出现在 dense 和 sparse 里,就把两路贡献相加。

RRF 偏爱两类结果:

text 复制代码
在某一路排得特别靠前。
在多路结果里都出现。

所以它很适合 hybrid search。

一句话:

text 复制代码
同时语义相关、关键词也命中的 chunk,会被 RRF 推到更前面。

二十二、Milvus hybrid_search、RRFRanker 和 WeightedRanker

学习时可以先分开做:

text 复制代码
dense_search 一次
sparse_search 一次
Python RRF 融合

这样最透明,便于调试。

Milvus 也支持更原生的 hybrid search 思路:

text 复制代码
多个 AnnSearchRequest
+
Ranker

可以把 AnnSearchRequest 理解成一路向量检索请求。

Dense 一路:

text 复制代码
在 dense_vector 字段上,用 COSINE 搜 dense_query。

Sparse 一路:

text 复制代码
在 sparse_vector 字段上,用 IP 搜 sparse_query。

Ranker 常见有:

text 复制代码
RRFRanker
WeightedRanker

RRFRanker 按排名融合,不太依赖原始分数尺度,适合 dense/sparse 分数差异很大的情况。

WeightedRanker 按分数加权,例如:

text 复制代码
final_score = 0.7 * dense_score + 0.3 * sparse_score

它适合你已经知道两路分数怎么比较,并且有评测集能调权重。

默认建议:

text 复制代码
不确定时先用 RRF。
有评测集后再尝试 WeightedRanker。

二十三、Reranker:为什么 Milvus 搜完还要重排

Milvus 的向量搜索适合从海量数据中快速召回候选。

但召回结果不一定排序完美。

Reranker 的作用是:

text 复制代码
对 Milvus 初步召回的一批候选重新排序。

典型流程:

text 复制代码
用户问题
→ Milvus / Hybrid 召回 Top 20 或 Top 50
→ Reranker 判断每个 chunk 和问题的真实相关性
→ 重排后取 Top 5
→ LLM 基于 Top 5 生成答案

为什么 reranker 更精细?

Embedding 检索通常是双塔结构:

text 复制代码
query → query embedding
doc → doc embedding
similarity(query_embedding, doc_embedding)

它的优点是快,因为文档向量可以提前算好。

缺点是:

text 复制代码
query 和 document 在编码时没有直接互相看到。

Reranker 通常是 cross-encoder 思路:

text 复制代码
query + document → relevance score

模型同时阅读问题和候选文本,所以能更细地判断:

text 复制代码
这个 chunk 是否真的能回答问题?
是否只是主题相关?
是否包含关键术语?
是否满足问题里的条件和范围?

比如用户问:

text 复制代码
efConstruction 和 ef 有什么区别?

候选 A:

text 复制代码
efConstruction 控制 HNSW 建图时的候选范围,ef 控制查询时的候选范围。

候选 B:

text 复制代码
HNSW 是一种基于图结构的近似最近邻搜索索引。

Dense embedding 可能觉得 B 也相关,因为都在讲 HNSW。

Reranker 更容易判断 A 直接回答了问题,B 只是主题相关。

二十四、qwen3-rerank 怎么接入

如果使用阿里百炼的 qwen3-rerank,它适合放在 Milvus 召回之后。

流程:

text 复制代码
用户问题
→ Milvus dense/hybrid 召回 Top 20
→ qwen3-rerank 重排
→ 取 Top 5
→ LLM 生成答案

rerank 请求本质上是:

text 复制代码
query + documents

返回通常包含:

text 复制代码
index
relevance_score

index 表示原 documents 数组里的位置。

所以要用 index 映射回原始 Milvus hit,保留:

text 复制代码
id
text
chapter
source
milvus_score

接入 reranker 后,上下文构造也要变化。

以前:

text 复制代码
Milvus search Top K
→ build_context
→ LLM

现在:

text 复制代码
Milvus / Hybrid 先召回更多
→ qwen3-rerank 重排
→ 只取 rerank 后前几条
→ build_context
→ LLM

建议:

text 复制代码
Milvus retrieve_limit = 20
Reranker top_n = 5

如果 Milvus 只召回 5 条,reranker 没有发挥空间。

二十五、RAG 常见失败原因

当出现:

text 复制代码
文档里明明有答案,但系统答不出来。

不要第一反应怪 LLM。

RAG 是一条链路,任何一环出问题都会导致失败。

常见原因:

text 复制代码
文档没有正确入库。
chunk 切得不好。
入库和查询用了不同 embedding 模型。
向量维度或 metric 配错。
filter 过严。
top_k / limit 太小。
HNSW ef 或 IVF nprobe 太小。
召回到了,但排序不理想。
Prompt 没约束好。
LLM 上下文被噪音干扰。

推荐排查顺序:

text 复制代码
1. count 看数据是否入库。
2. get/query 看原文和字段是否正确。
3. 不加 filter 搜一次。
4. 调大 limit 搜一次。
5. 调大 ef / nprobe 搜一次。
6. 用 FLAT 对照近似索引。
7. 检查 chunk 和 embedding。
8. 再看 reranker 和 prompt。

一句话:

text 复制代码
RAG 失败不一定是 LLM 笨。先查数据,再查 chunk,再查 embedding,再查 filter/top_k/index,最后再看 prompt 和模型。

二十六、怎么评估 Hybrid 和 Reranker 是否真的有用

不能只靠感觉。

你需要准备测试问题集:

text 复制代码
HNSW 的 M 是什么?
efConstruction 和 ef 有什么区别?
nprobe 变大会怎样?
什么是 SPARSE_FLOAT_VECTOR?
RAG 里 Milvus 负责什么?
为什么 collection 要 load?
delete 后数据为什么不立刻物理消失?

然后给每个问题标注相关 chunk。

这叫 ground truth。

常见指标:

Recall@K

标准答案里有多少被 Top K 找回来。

Hit@K

Top K 里有没有至少一个正确结果。

RAG 很常看 Hit@K,因为只要关键 chunk 被召回,LLM 可能就能回答。

MRR

MRR 看第一个正确结果排第几。

如果第一个正确结果排第 1:

text 复制代码
1 / 1 = 1.0

如果排第 3:

text 复制代码
1 / 3 = 0.333

同时还要看性能:

text 复制代码
平均延迟
P95 延迟
QPS
内存
成本

例如:

text 复制代码
dense only:Recall@5 = 0.82,延迟 40ms
hybrid:Recall@5 = 0.90,延迟 85ms
hybrid + reranker:Recall@5 = 0.94,延迟 400ms

这时要看业务是否愿意用额外延迟换更高准确率。

二十七、部署形态:Lite、Standalone、Cluster、Cloud

Milvus 有多种使用形态。

Milvus Lite

适合:

text 复制代码
学习
本地 demo
小规模原型

它轻量、简单,不需要单独运维服务。

但不适合高并发和生产大规模。

Milvus Standalone

单机服务版,更接近真实 Milvus 服务。

适合:

text 复制代码
开发环境
测试环境
中小规模项目

通常用 Docker Compose 部署。

Milvus Cluster

分布式集群版。

适合:

text 复制代码
大规模数据
高并发查询
高可用
组件独立扩展

Cluster 会把职责拆开:

text 复制代码
Proxy:请求入口
DataNode:写入数据
QueryNode:执行搜索
IndexNode:构建索引
Coord:协调调度
Storage:保存数据和索引

Zilliz Cloud

托管版向量数据库服务。

适合:

text 复制代码
想快速上线
不想自己运维
需要托管监控、扩缩容、备份

选择建议:

text 复制代码
Lite:学习和本地原型。
Standalone:单机真实服务,适合开发测试和中小规模。
Cluster:大规模生产,自运维复杂。
Cloud:托管生产,少运维。

二十八、性能指标和调优思路

从 demo 到生产,不能只看"搜得准不准"。

还要看:

text 复制代码
Latency:单次查询延迟。
QPS:每秒查询数。
Throughput:批量写入或处理能力。
Memory:内存占用。
Disk:磁盘占用。
Recall:召回质量。

RAG 的总延迟通常包括:

text 复制代码
query embedding 时间
Milvus search 时间
reranker 时间
LLM 生成时间

用户觉得慢,不代表 Milvus 慢。要分段计时。

如果 Milvus search 慢,可以考虑:

text 复制代码
调小 ef / nprobe
降低 limit
检查 filter 是否复杂
确认索引是否建好
减少搜索范围
增加 QueryNode 资源

如果结果不准,可以考虑:

text 复制代码
检查 embedding 模型
优化 chunk
调大 ef / nprobe
增大 limit
加入 hybrid search
加入 reranker
检查 filter 是否误杀数据

调优的本质:

text 复制代码
在 Recall、Latency、QPS、Memory 之间做取舍。

二十九、一个推荐的学习和落地路线

如果从零学 Milvus,我建议按这个顺序:

text 复制代码
1. 先理解向量、embedding、metric。
2. 用 Milvus Lite 跑通 insert/search/filter。
3. 学 Collection、Schema、Field。
4. 学 FLAT、HNSW、IVF_FLAT。
5. 学 Load、Segment、Delete、Upsert、Compaction。
6. 做一个最小 RAG demo。
7. 学 chunking、metadata、权限过滤和 citation。
8. 学 hybrid search:dense + sparse + RRF。
9. 接 reranker,例如 qwen3-rerank。
10. 最后再考虑 Standalone、Cluster、监控和压测。

不要一开始就上最复杂的架构。

Milvus 学习最好的方式是:

text 复制代码
先让一条数据能被搜到。
再让一批数据能被搜准。
再让搜索变快。
最后再考虑生产规模和复杂工程。

结语

Milvus 的核心其实很朴素:

text 复制代码
把向量存好。
把相似向量找快。
让应用能用这些结果做搜索、推荐或 RAG。

但一旦进入真实系统,它就会牵出一整套工程问题:

text 复制代码
怎么切 chunk
怎么建 schema
怎么选 metric
怎么选索引
怎么调 ef / nprobe
怎么做权限过滤
怎么做 hybrid search
怎么做 reranker
怎么引用来源
怎么更新文档
怎么评估效果
怎么控制延迟和成本

如果只记一句话,那就是:

text 复制代码
Embedding 决定内容能不能被表示好,Milvus 决定相似内容能不能被高效找出来,RAG 应用层决定这些资料能不能被安全、准确地交给大模型使用。

这也是学习 Milvus 最重要的一张地图。

相关推荐
番茄不是西红柿kk1 小时前
什么是Token?
人工智能·ai·chatgpt·agent·token·codex·deepseek
代码简单说1 小时前
Codex 常见错误排查指南:Stream disconnected、400、401、403、429、502、503 解决方法
人工智能
Databuff1 小时前
workbuddy 企业版与 openocta 企业版 功能对比
人工智能
xqqxqxxq1 小时前
AI Agent学习:MCP与工具生态:工具选择的挑战(李博杰《深入理解 AI Agent》4.3观后总结)
人工智能·学习
疯狂的金桔2 小时前
不要再把 Agent Memory 当成聊天记录:从 LangGraph 到 Deep Agents 的完整记忆架构
人工智能
腾讯数据架构师2 小时前
壁仞 GPU 怎么接入 Kubernetes 和 AI 平台?CubeStudio 壁仞算力适配实操
人工智能·容器·kubernetes·cube-studio·ai平台
有脚就行2 小时前
第28篇-Kubernetes-GPU调度机制-Device-Plugin与GPU-Operator
人工智能·容器
IvanCodes2 小时前
RAG 实战教程(二):向量相似度、向量数据库与 Chroma 实战
人工智能·agent
阿维的博客日记2 小时前
什么是unigram语言模型
人工智能·语言模型·自然语言处理