本文是 RAG 系列第二篇,适合已阅读基础篇的开发者。
重点:Milvus/Chroma/FAISS/Pinecone 深度对比、Milvus 5 种查询方式详解、生产级系统设计、Chroma/FAISS 参考代码、故障排查指南。
前期回顾
1. 引言:何时需要关心向量库选型
不是所有项目都需要纠结选型。先看一个决策树:
lkkkk
三类典型场景对应选择:
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 个人项目/课程学习 | Milvus Lite | 全平台,零依赖,可无缝升级 |
| 团队中小规模生产 | Milvus Standalone | Docker 一键启动,中等规模够用 |
| 企业级 / 不想运维 | Zilliz Cloud 或 Pinecone | 全托管,SLA 保障,按量计费 |
| 单机超高性能批处理 | FAISS | 纯内存索引,检索速度极快 |
第一部分:四大向量库全面对比
1.1 完整对比表
| 维度 | Milvus | Chroma | FAISS | Pinecone |
|---|---|---|---|---|
| 平台支持 | ✅ 全平台含旧 macOS Intel | ⚠️ 不支持旧 macOS Intel | ❌ 不支持旧 macOS Intel | ✅ 云服务,全平台 |
| 部署方式 | Lite(本地文件)/ Standalone(Docker)/ Distributed(K8s)/ Cloud | 本地进程或 Docker | 纯 Python 库,无服务 | 云托管,无需自部署 |
| 扩展性 | ✅ 亿级向量,分布式扩展 | ⚠️ 百万级,单机为主 | ⚠️ 单机内存限制 | ✅ 云端弹性扩展 |
| 实时增删 | ✅ 原生支持 add_documents() | ✅ 支持 | ❌ 不支持,需完整重建 | ✅ 支持 |
| 元数据过滤 | ✅ SQL-like 表达式,功能完整 | ✅ 基础过滤 | ❌ 基本不支持 | ✅ 支持 |
| 费用 | 开源免费(Lite/Standalone)/ Cloud 按量计费 | 开源免费 | 开源免费 | 按向量数量+查询量计费 |
| 主要语言 | Go(服务)+ Python SDK | Python | C++(库)+ Python SDK | SaaS(云 API) |
| 生产就绪 | ✅ 企业级,Zilliz 商业支持 | ⚠️ 适合小规模 | ⚠️ 适合特定场景 | ✅ 企业级,全托管 |
1.2 各数据库的适用分析
Milvus —— 本课程主推方案
适合场景:
- 需要全平台支持(特别是 macOS Intel 旧机器)
- 项目可能从原型扩展到生产(Lite → Standalone → Cloud 无缝迁移)
- 需要元数据过滤(按文档来源、类别、时间范围限定检索范围)
- 团队需要共享同一个向量库实例
不适合场景:
- 只需要极简单的一次性脚本(Chroma 可能更快上手)
- 已有成熟的 Pinecone 使用习惯且无迁移需求
Chroma —— 上手最快
适合场景:
- 快速原型,3 行代码就能跑起来
- Linux 或 macOS 14+开发者
- 小规模(百万条以内)且不需要复杂元数据过滤
不适合场景:
- macOS Intel:安装会失败
- 数据规模超过百万条
- 需要生产级高可用
FAISS —— 极速离线检索
适合场景:
- 离线批处理,检索性能要求极高
- 数据固定不变(不需要增量更新)
- 内存充足的单机环境
不适合场景:
- 需要实时增量入库(每次更新都要重建索引,代价很高)
- 旧版 macOS Intel(无预编译 wheel)
- 需要元数据过滤
Pinecone —— 零运维云服务
适合场景:
- 团队没有基础设施运维能力
- 数据规模大(亿级)且需要高可用 SLA
- 对响应速度要求极高且愿意为托管服务付费
不适合场景:
- 数据保密要求高(数据必须上传到第三方)
- 预算有限(价格相比自托管高很多)
- 需要离线/私有部署
第二部分:Milvus 深度解析
2.1 五种查询方式详解
05_milvus_advanced_rag.py 完整演示了 Milvus 的所有查询方式:
方式一:基础相似度搜索
# 来自 05_milvus_advanced_rag.py — demo_similarity_search
query = "Milvus 支持哪些部署模式?"
docs = vectorstore.similarity_search(query, k=3)
# 返回:最相似的 k 个 Document 对象列表
for doc in docs:
print(doc.page_content)
print(doc.metadata) # {'source': 'milvus_docs', 'category': 'database', 'year': 2023}
返回值:list[Document],直接可用。最常见的使用方式,as_retriever() 内部也是调用它。
适用场景:绝大多数 RAG 场景的默认选择。
方式二:带距离分数搜索
# 来自 05_milvus_advanced_rag.py — demo_similarity_search_with_score
query = "如何减少 AI 模型的幻觉问题?"
docs_and_scores = vectorstore.similarity_search_with_score(query, k=3)
for doc, score in docs_and_scores:
print(f"距离分数:{score:.4f}(越小越相似)")
print(f"内容:{doc.page_content[:60]}...")
返回值:list[tuple[Document, float]],包含每个文档的距离分数。
分数含义:Milvus 默认使用 IP(内积)距离,分数越小表示向量距离越近,即越相似。
适用场景:需要根据置信度过滤低质量结果时。例如,设定阈值 score < 0.5 才使用该文档:
# 过滤低置信度结果
SCORE_THRESHOLD = 0.5
docs_and_scores = vectorstore.similarity_search_with_score(query, k=5)
reliable_docs = [doc for doc, score in docs_and_scores if score < SCORE_THRESHOLD]
方式三:MMR 多样性搜索
# 来自 05_milvus_advanced_rag.py — demo_mmr_search
query = "向量数据库有哪些选择?"
docs = vectorstore.max_marginal_relevance_search(
query,
k=3, # 最终返回 3 篇
fetch_k=6, # 先取 6 篇候选,从中选最多样的 3 篇
lambda_mult=0.5, # 平衡相关性与多样性(0=纯多样性,1=纯相关性)
)
MMR 的工作原理:
- 先用普通相似度检索,召回
fetch_k=6篇候选文档 - 从候选中迭代选择:每次选"与查询相关,但与已选文档最不相似"的文档
- 最终返回
k=3篇,兼顾相关性与多样性
为什么需要 MMR?
假设知识库里有 10 段都在讲"Milvus 的部署模式",普通搜索会返回这 10 段中最相似的 3 段,但这 3 段内容几乎相同,没有增量信息。MMR 会确保这 3 段来自不同的语义角度,覆盖更多知识面。
生产推荐参数:
# 来自 09_milvus_production_rag.py 的生产配置
retriever = vectorstore.as_retriever(
search_type="mmr",
search_kwargs={
"k": 3, # 返回文档数
"fetch_k": 10, # 候选数,通常是 k 的 3-5 倍
"lambda_mult": 0.6, # 0.6 是实践中的均衡值
},
)
方式四:元数据过滤搜索
# 来自 05_milvus_advanced_rag.py — demo_metadata_filter_search
query = "向量检索的技术原理是什么?"
# 只搜索 category 为 "database" 的文档
docs = vectorstore.similarity_search(
query,
k=3,
expr='category == "database"', # Milvus 过滤表达式
)
# 组合过滤:year >= 2024 AND category == "technique"
docs2 = vectorstore.similarity_search(
query,
k=3,
expr='year >= 2024 and category == "technique"',
)
Milvus 过滤语法完整速查:
# 精确匹配
expr='category == "database"'
# 不等于
expr='year != 2022'
# 数值范围
expr='year >= 2024'
# IN 多值匹配
expr='category in ["database", "framework"]'
# AND 组合
expr='year >= 2023 and category == "rag"'
# OR 组合
expr='category == "rag" or category == "embedding"'
# LIKE 模糊匹配(字符串)
expr='source like "milvus%"'
注意:过滤字段必须在建索引时作为 metadata 存入,Milvus 会自动为标量字段建立索引。
方式五:增量入库
# 来自 05_milvus_advanced_rag.py — demo_add_documents
new_docs = [
Document(
page_content="Pinecone 是全托管的云向量数据库,无需运维,按用量计费,适合快速上线。",
metadata={"source": "pinecone_docs", "category": "database", "year": 2024},
),
Document(
page_content="向量量化(Product Quantization)可将向量压缩存储,大幅降低内存占用但会损失精度。",
metadata={"source": "ml_tutorial", "category": "technique", "year": 2024},
),
]
# add_documents 自动计算嵌入并写入,不影响已有数据
ids = vectorstore.add_documents(new_docs)
print(f"✅ 成功添加 {len(new_docs)} 篇新文档,生成 ID:{ids}")
增量入库 vs 重建索引:
| 操作 | 使用场景 | 代码 |
|---|---|---|
| 增量入库 | 每天新增文档,保留历史数据 | vectorstore.add_documents(new_docs) |
| 重建索引 | 更换嵌入模型、全量数据刷新 | Milvus.from_documents(..., drop_old=True) |
Milvus 与 FAISS 最大的差异就在这里:FAISS 不支持原生增量更新,每次新增数据都需要把所有向量重新加载进内存、重建索引、再 save 到磁盘,代价极高。
2.2 三种部署模式对比
# 来自 05_milvus_advanced_rag.py — demo_connection_modes
# ── Milvus Lite(本地文件,开发/学习) ──────────────────────────────
vectorstore = Milvus(
embedding_function=embeddings,
collection_name="my_collection",
connection_args={"uri": "./milvus_data.db"}, # 本地文件
)
# ── Milvus Standalone(Docker 服务,测试/中小规模生产) ──────────────
# 先启动:docker run -d --name milvus -p 19530:19530 milvusdb/milvus:latest
vectorstore = Milvus(
embedding_function=embeddings,
collection_name="my_collection",
connection_args={"uri": "http://127.0.0.1:19530"},
)
# ── Zilliz Cloud(全托管,大规模生产) ───────────────────────────────
vectorstore = Milvus(
embedding_function=embeddings,
collection_name="my_collection",
connection_args={
"uri": "https://your-cluster.zillizcloud.com",
"token": "your_api_key_or_token",
},
)
| 模式 | 适用场景 | 数据量 | 运维成本 | 费用 |
|---|---|---|---|---|
| Milvus Lite | 开发/学习/小型 POC | < 100 万条 | 零 | 免费 |
| Milvus Standalone | 团队共享/中小规模生产 | < 1 亿条 | 低(Docker) | 免费 |
| Milvus Distributed | 大规模生产 | 无上限 | 高(K8s) | 免费 |
| Zilliz Cloud | 不想自运维的生产 | 无上限 | 零 | 按量计费 |
2.3 Collection 生命周期管理
生产系统中,Collection(集合)是核心资源,需要合理管理其生命周期:
# 来自 09_milvus_production_rag.py — load_or_create_vectorstore
from pathlib import Path
def load_or_create_vectorstore(embeddings, documents=None, reset=False):
db_path = Path(MILVUS_URI)
collection_exists = db_path.exists() and db_path.stat().st_size > 0
# 情况一:强制重建(reset=True),谨慎使用!
if reset:
if db_path.exists():
db_path.unlink()
collection_exists = False
# 情况二:集合已存在,直接复用
if collection_exists:
vectorstore = Milvus(
embedding_function=embeddings,
collection_name=COLLECTION_NAME,
connection_args={"uri": MILVUS_URI},
)
return vectorstore
# 情况三:首次创建
return _build_vectorstore(documents, embeddings, drop_old=True)
关键操作速查:
# 创建并写入数据(首次建库)
vectorstore = Milvus.from_documents(
documents=chunks,
embedding=embeddings,
collection_name="my_kb",
connection_args={"uri": "my.db"},
)
# 连接已有集合(生产查询)
vectorstore = Milvus(
embedding_function=embeddings,
collection_name="my_kb",
connection_args={"uri": "my.db"},
)
# 增量添加文档
vectorstore.add_documents(new_docs)
# 强制重建(清空所有数据)
Milvus.from_documents(..., drop_old=True)
# 删除本地文件(Lite 模式)
from pathlib import Path
Path("my.db").unlink()
2.4 元数据设计最佳实践
好的元数据设计让检索精度大幅提升。推荐字段:
# 推荐的元数据结构
Document(
page_content="...",
metadata={
"source": "employee_handbook_v3.pdf", # 来源文件,方便溯源
"category": "hr_policy", # 文档类别,支持按类过滤
"year": 2024, # 年份,支持时间范围过滤
"author": "HR Department", # 作者
"chapter": "第3章 薪酬福利", # 章节,方便定位
"page": 42, # 页码
},
)
元数据设计原则:
- 字段类型要统一:同一字段在所有文档中类型一致(都是字符串或都是数字),否则过滤会出错
- 粒度要合理:不要把一段话的所有属性都塞进元数据,只保留会用于过滤的字段
- year 用 int 而非 str:
year=2024(int)比year="2024"(str)更方便做范围过滤
第三部分:生产 RAG 系统设计
3.1 系统架构图
3.2 九大生产模式详解
以下模式全部来自 09_milvus_production_rag.py,可直接用于生产:
模式一:环境与依赖校验
# 来自 09_milvus_production_rag.py
def validate_environment() -> str:
api_key = os.getenv("DASHSCOPE_API_KEY")
if not api_key:
print("❌ 环境变量未设置:DASHSCOPE_API_KEY")
print(" export DASHSCOPE_API_KEY='your_api_key_here'")
sys.exit(1)
print(f"✅ DASHSCOPE_API_KEY 已设置(前8位:{api_key[:8]}...)")
return api_key
原则:在最早的时刻验证所有外部依赖(API Key、数据库连接、必要文件),宁可在启动时清晰报错,不要等到运行中途才失败。
模式二:配置常量集中管理
# 来自 09_milvus_production_rag.py
MILVUS_URI = "milvus_production_rag.db" # 只改这里就能切换部署模式
COLLECTION_NAME = "chapter06_production_rag"
CHUNK_SIZE = 500
CHUNK_OVERLAP = 50
TOP_K = 3
MMR_FETCH_K = 10
MMR_LAMBDA = 0.6
INGEST_BATCH_SIZE = 50
EMBEDDING_DIMENSIONS = 1024
LLM_TEMPERATURE = 0.3
原则:所有"可能会调整"的参数统一放在文件顶部,不要散落在代码各处。这样调参时只需修改一个地方。
模式三:集合生命周期管理(已在 2.3 节展示)
模式四:三层文档入库
# 来自 09_milvus_production_rag.py — ingest_documents
def ingest_documents(vectorstore, documents, drop_old=False):
"""
切分、嵌入、存储文档。
drop_old=False(默认):增量追加,生产环境常用
drop_old=True:清空重建,更换嵌入模型时使用
"""
splitter = RecursiveCharacterTextSplitter(
chunk_size=CHUNK_SIZE,
chunk_overlap=CHUNK_OVERLAP,
separators=["\n\n", "\n", "。", ",", " ", ""],
)
chunks = splitter.split_documents(documents)
if drop_old:
Milvus.from_documents(
documents=chunks,
embedding=vectorstore.embedding_func,
collection_name=COLLECTION_NAME,
connection_args={"uri": MILVUS_URI},
drop_old=True,
)
else:
vectorstore.add_documents(chunks) # 增量追加
return len(chunks)
模式五:生产 RAG 链(带来源引用 + MMR)
# 来自 09_milvus_production_rag.py — build_production_rag_chain
def build_production_rag_chain(vectorstore, llm, expr=None):
"""
生产级 RAG 链:MMR 多样性检索 + 来源引用 + 可选元数据过滤。
"""
retriever_kwargs = {
"k": TOP_K,
"fetch_k": MMR_FETCH_K,
"lambda_mult": MMR_LAMBDA,
}
if expr:
retriever_kwargs["expr"] = expr # 可选元数据过滤
retriever = vectorstore.as_retriever(
search_type="mmr",
search_kwargs=retriever_kwargs,
)
rag_prompt = ChatPromptTemplate.from_messages([
(
"system",
"""你是一名专业的 AI 技术顾问,请严格基于以下检索文档回答用户问题。
回答规范:
1. 只使用检索文档中的信息,不添加文档之外的内容
2. 如果文档中没有相关信息,请明确说明"根据现有文档,暂无相关记录"
3. 可以在回答末尾注明"参考来源:xxx"
===== 检索文档 =====
{context}
====================""",
),
("human", "{question}"),
])
def format_docs_with_source(docs):
"""每段标注来源,让答案可追溯。"""
parts = []
for doc in docs:
source = doc.metadata.get("source", "未知来源")
category = doc.metadata.get("category", "")
label = f"[来源: {source}]" + (f" [{category}]" if category else "")
parts.append(f"{label}\n{doc.page_content.strip()}")
return "\n\n".join(parts)
return (
{"context": retriever | format_docs_with_source, "question": RunnablePassthrough()}
| rag_prompt | llm | StrOutputParser()
)
模式六:批量入库
# 来自 09_milvus_production_rag.py — ingest_batch
def ingest_batch(vectorstore, documents, batch_size=INGEST_BATCH_SIZE):
"""
分批入库,避免大数据量时内存溢出。
适合首次入库数万篇文档,或定期全量刷新。
"""
total_chunks = 0
total_batches = (len(documents) + batch_size - 1) // batch_size
for i in range(0, len(documents), batch_size):
batch = documents[i:i + batch_size]
batch_num = i // batch_size + 1
print(f" 处理第 {batch_num}/{total_batches} 批({len(batch)} 篇文档)...")
total_chunks += ingest_documents(vectorstore, batch, drop_old=False)
return total_chunks
模式七:元数据过滤 RAG
# 来自 09_milvus_production_rag.py — demo_filtered_rag
# 只在 category == "rag" 的文档中检索,大幅提升针对性问题的精度
filtered_chain = build_production_rag_chain(
vectorstore, llm, expr='category == "rag"'
)
answer = filtered_chain.invoke("增量入库和重建索引有什么区别?")
模式八:MMR 与普通搜索对比
# 来自 09_milvus_production_rag.py — demo_mmr_search
query = "RAG 系统的关键技术"
# 普通搜索(可能有冗余)
plain_results = vectorstore.similarity_search(query, k=3)
# MMR 多样性搜索(结果覆盖更多方面)
mmr_results = vectorstore.max_marginal_relevance_search(
query, k=TOP_K, fetch_k=MMR_FETCH_K, lambda_mult=MMR_LAMBDA,
)
模式九:RAG 质量评估
# 来自 09_milvus_production_rag.py — evaluate_rag
TEST_CASES = [
{
"question": "什么是 RAG?",
"expected_keywords": ["检索", "生成", "幻觉"],
"description": "基础概念测试",
},
{
"question": "Milvus 有哪些部署模式?",
"expected_keywords": ["Lite", "Standalone", "Distributed"],
"description": "Milvus 部署模式测试",
},
]
report = evaluate_rag(rag_chain, TEST_CASES)
# 返回:{"total": 2, "passed": 2, "pass_rate": 1.0}
# pass_rate >= 0.8:质量良好
# pass_rate < 0.5:需要优化检索参数或扩充知识库
3.3 RAG 质量评估方法
生产 RAG 系统需要持续评估质量,三种层次的评估方法:
方法一:关键词命中率(自动化,轻量)
最轻量的评估方式,适合 CI/CD 集成:
# 来自 09_milvus_production_rag.py — evaluate_rag(核心逻辑)
for case in test_cases:
answer = rag_chain.invoke(case["question"])
hit = any(kw.lower() in answer.lower() for kw in case["expected_keywords"])
# hit=True:通过;hit=False:失败
局限性:只检查关键词是否存在,不评估语义准确性和表达质量。
方法二:语义相似度(自动化,较准确)
用嵌入模型计算"标准答案"和"RAG 答案"的余弦相似度:
# 概念示例(非脚本原始代码)
from sklearn.metrics.pairwise import cosine_similarity
reference_answer = "RAG 通过检索相关文档来减少 LLM 幻觉,解决知识截止问题。"
rag_answer = rag_chain.invoke("RAG 有什么作用?")
ref_vec = embeddings.embed_query(reference_answer)
rag_vec = embeddings.embed_query(rag_answer)
similarity = cosine_similarity([ref_vec], [rag_vec])[0][0]
# similarity >= 0.85:答案语义接近,质量良好
方法三:人工评估(最准确,成本高)
对于关键业务场景,定期安排领域专家评估样本:
- 准确性:答案是否正确,有无错误信息
- 完整性:是否覆盖了问题的所有要点
- 来源可信度:检索到的文档是否确实与问题相关
- 拒绝率:当问题超出知识库范围时,是否正确说"不知道"而不是乱答
3.4 监控与告警要点
生产系统上线后,关键监控指标:
| 指标 | 阈值参考 | 告警策略 |
|---|---|---|
| 检索延迟 P99 | < 100ms | > 200ms 告警 |
| LLM 响应时间 P50 | < 3s | > 10s 告警 |
| 嵌入 API 错误率 | < 0.1% | > 1% 告警 |
| 空检索率(k 个结果均低于阈值) | < 5% | > 15% 需扩充知识库 |
| 用户满意度(👍/👎) | > 80% 正向 | < 70% 触发审查 |
第四部分:Chroma 与 FAISS 参考
4.1 Chroma 使用场景与代码
适合场景:macOS Apple Silicon / Linux,快速原型,不需要生产级扩展。
以下代码来自 02_vector_store_rag.py 的设计风格(Chroma 版):
# Chroma 建库(与 Milvus 用法几乎相同)
from langchain_chroma import Chroma
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./chroma_db", # 持久化目录(不指定则在内存)
collection_name="my_kb",
)
# 检索(API 与 Milvus 完全相同)
docs = vectorstore.similarity_search(query, k=3)
docs_scores = vectorstore.similarity_search_with_score(query, k=3)
mmr_docs = vectorstore.max_marginal_relevance_search(query, k=3, fetch_k=10)
# 连接已有集合
vectorstore = Chroma(
persist_directory="./chroma_db",
embedding_function=embeddings,
collection_name="my_kb",
)
Chroma 的元数据过滤语法(与 Milvus 不同):
# Chroma 使用字典格式过滤(不是 SQL-like 表达式)
docs = vectorstore.similarity_search(
query,
k=3,
filter={"topic": "RAG技术"} # 精确匹配
)
# 不等于
docs = vectorstore.similarity_search(
query, k=3,
filter={"year": {"$ne": 2022}} # 使用 MongoDB 风格操作符
)
4.2 FAISS 使用场景与代码
适合场景:离线批处理,性能要求极高,数据不需要实时更新。
# 来自 06_faiss_rag.py 的设计风格
from langchain_community.vectorstores import FAISS
# 建库(纯内存,速度极快)
vectorstore = FAISS.from_documents(
documents=chunks,
embedding=embeddings,
)
# 保存到磁盘(必须手动!不同于 Milvus 的自动持久化)
vectorstore.save_local("./faiss_index")
# 从磁盘加载
vectorstore = FAISS.load_local(
"./faiss_index",
embeddings,
allow_dangerous_deserialization=True, # 必须显式允许
)
# 合并两个 FAISS 索引
vectorstore1.merge_from(vectorstore2)
# 检索(API 与 Milvus/Chroma 相同)
docs = vectorstore.similarity_search(query, k=3)
docs_scores = vectorstore.similarity_search_with_score(query, k=3)
FAISS 的注意事项:
# ⚠️ FAISS 不支持实时增量更新!
# 添加新文档后,必须重新建库并手动 save
new_vectorstore = FAISS.from_documents(all_docs + new_docs, embeddings)
new_vectorstore.save_local("./faiss_index")
# ✅ Milvus 的等价操作(无需重建)
vectorstore.add_documents(new_docs) # 直接追加,自动持久化
4.3 从 Chroma 迁移到 Milvus:代码对比
迁移只需修改 3 处,业务代码完全不变:
# ───── Chroma 版(02_vector_store_rag.py 风格)────────────────────────────
from langchain_chroma import Chroma
# 建库
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./chroma_db",
collection_name="my_kb",
)
# 连接已有集合
vectorstore = Chroma(
persist_directory="./chroma_db",
embedding_function=embeddings,
collection_name="my_kb",
)
# 过滤语法
docs = vectorstore.similarity_search(query, k=3, filter={"category": "rag"})
# ───── Milvus 版(04/07 脚本风格)────────────────────────────────────────
from langchain_milvus import Milvus
# 建库(差异1:connection_args 替代 persist_directory)
vectorstore = Milvus.from_documents(
documents=chunks,
embedding=embeddings,
connection_args={"uri": "./milvus.db"}, # ← 改这里
collection_name="my_kb",
)
# 连接已有集合(差异2:embedding_function 替代 embedding_function)
vectorstore = Milvus(
embedding_function=embeddings, # ← 参数名改了
collection_name="my_kb",
connection_args={"uri": "./milvus.db"}, # ← 改这里
)
# 过滤语法(差异3:expr 字符串替代 filter 字典)
docs = vectorstore.similarity_search(query, k=3, expr='category == "rag"') # ← 改这里
# ✅ 以下代码两者完全相同,零改动:
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
docs = vectorstore.similarity_search(query, k=3)
docs_scores = vectorstore.similarity_search_with_score(query, k=3)
mmr_docs = vectorstore.max_marginal_relevance_search(query, k=3, fetch_k=10)
vectorstore.add_documents(new_docs)
6. 常见问题与故障排查
Q1:ModuleNotFoundError: No module named 'pkg_resources'
原因:uv 创建的精简虚拟环境不包含 setuptools(提供 pkg_resources 模块),而旧版 milvus-lite 依赖它读取自身版本号。
修复代码(脚本内已内置,正常情况下自动处理):
# 来自所有 milvus 脚本的兼容垫片
try:
import pkg_resources # noqa: F401
except ImportError:
import types as _types
import importlib.metadata as _meta
_pkg = _types.ModuleType("pkg_resources")
_pkg.DistributionNotFound = _meta.PackageNotFoundError
class _Distribution:
__slots__ = ("version",)
def __init__(self, name: str) -> None:
self.version = _meta.version(name)
def _get_distribution(name: str) -> _Distribution:
try:
return _Distribution(name)
except _meta.PackageNotFoundError:
raise _meta.PackageNotFoundError(name)
_pkg.get_distribution = _get_distribution
sys.modules["pkg_resources"] = _pkg
如果脚本仍然报错:依赖版本可能混乱,重新同步:
cd ai-agent-test && git pull && uv sync --extra milvus
Q2:grpc.RpcError 或空错误信息
原因:pymilvus 与 milvus-lite 版本不兼容,gRPC 协议握手失败。
现象:str(e) 为空,错误类型为 grpc.RpcError 或 MilvusException。
# 来自各脚本的错误处理模式
except Exception as e:
err = str(e)
err_repr = repr(e)
if not err:
# err 为空通常是 gRPC 版本不兼容
print(" 可能原因:pymilvus 与 milvus-lite 版本不兼容,或 gRPC 协议错误。")
print(" 解决方法:cd ai-agent-test && uv sync --extra milvus")
版本固定修复(项目 pyproject.toml 中已锁定兼容版本):
# 确保使用项目锁定的依赖版本
cd ai-agent-test && uv sync --extra milvus
Q3:重建索引 vs 增量更新,何时选哪个?
# 场景一:更换了嵌入模型(维度变化)→ 必须重建
# 旧:dimensions=1024,新:dimensions=1536
# 维度不同,旧向量与新向量不兼容,必须删除所有数据重建
vectorstore = Milvus.from_documents(all_docs, new_embeddings, ..., drop_old=True)
# 场景二:只是新增了几篇文档 → 增量追加
vectorstore.add_documents(new_docs)
# 场景三:文档内容大规模改版(超过30%更新)→ 可以考虑重建
# 重建的好处:清理了旧版本文档,避免检索到过时信息
vectorstore = Milvus.from_documents(all_new_docs, embeddings, ..., drop_old=True)
# 场景四:每天定期新增文档 → 一定用增量,不要重建
# 重建会有一段时间窗口无法提供服务(索引不可用)
vectorstore.add_documents(daily_new_docs)
Q4:macOS Intel 用户 FAISS 安装失败
FAISS 在 macOS 13(Ventura)及以下的 Intel 机器上没有预编译 wheel:
# ❌ 不要尝试这个(会失败)
uv sync --extra faiss # macOS Intel Ventura 及以下
# ✅ 改用 Milvus,功能完全等效
uv sync --extra milvus
uv run python lessons/06_rag/04_milvus_rag.py
Q5:Milvus 相似度分数越小越相似?
使用 similarity_search_with_score() 返回的分数含义取决于索引配置的距离度量:
| 距离度量 | 默认配置 | 分数越小 |
|---|---|---|
| IP(内积) | Milvus 默认 | 越相似(常见) |
| L2(欧氏) | 另一种配置 | 越相似 |
| COSINE | 需显式指定 | 分数含义不同 |
建议:运行时打印几条结果,根据实际输出来判断阈值方向:
docs_scores = vectorstore.similarity_search_with_score("测试问题", k=5)
for doc, score in docs_scores:
print(f"score={score:.4f}: {doc.page_content[:40]}...")
# 观察:分数最小的是否最相关
生产环境上线前 Checklist
环境检查:
□ DASHSCOPE_API_KEY 已设置且有效
□ uv sync --extra milvus 已运行
□ Milvus 连接方式已确认(Lite/Standalone/Cloud)
数据质量检查:
□ 文本切分参数合理(chunk_size/overlap 已根据文档类型调整)
□ 元数据字段类型统一(所有文档的 year 都是 int,不是混合类型)
□ 知识库文档已清洗(去除乱码、重复内容)
功能测试:
□ evaluate_rag() 通过率 >= 80%
□ 多轮对话问题重构工作正常
□ 超出知识库范围的问题返回"暂无相关记录"而非编造答案
生产配置:
□ LLM_TEMPERATURE <= 0.3(RAG 场景低温度)
□ 已切换到 Milvus Standalone 或 Cloud(生产不用 Lite)
□ 来源引用已开启(format_docs_with_source)
□ 批量入库逻辑已测试(ingest_batch)
监控配置:
□ 检索延迟监控已上线
□ API 错误率告警已配置
□ 定期 evaluate_rag 已加入 CI/CD
📌 下一章预告:RAG 让 AI 能读文档,但如果需要更复杂的工作流(多个 AI 节点协作、带条件分支的任务)?第07章学 LangGraph,用图结构描述 AI 工作流,实现任意复杂度的 AI 系统
作者:阿聪谈架构
公众号:阿聪谈架构(分享后端架构 / AI / Java 技术文章)
相关代码关注公众号:【阿聪谈架构】 回复:AI专栏代码