如果你刚开始接触 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 通常会上升,但延迟也会上升。
limit 或 top_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
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 在原文里的顺序。
source、page、section 用于引用来源。
tenant_id、permission_group、status 用于权限过滤。
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 最重要的一张地图。