第06章:AI RAG 检索增强生成 — 从零到生产(下)

0 阅读19分钟

本文是 RAG 系列第二篇,适合已阅读基础篇的开发者。
重点:Milvus/Chroma/FAISS/Pinecone 深度对比、Milvus 5 种查询方式详解、生产级系统设计、Chroma/FAISS 参考代码、故障排查指南。

前期回顾



1. 引言:何时需要关心向量库选型

不是所有项目都需要纠结选型。先看一个决策树: lkkkk

三类典型场景对应选择

场景推荐方案理由
个人项目/课程学习Milvus Lite全平台,零依赖,可无缝升级
团队中小规模生产Milvus StandaloneDocker 一键启动,中等规模够用
企业级 / 不想运维Zilliz Cloud 或 Pinecone全托管,SLA 保障,按量计费
单机超高性能批处理FAISS纯内存索引,检索速度极快

第一部分:四大向量库全面对比

1.1 完整对比表

维度MilvusChromaFAISSPinecone
平台支持✅ 全平台含旧 macOS Intel⚠️ 不支持旧 macOS Intel❌ 不支持旧 macOS Intel✅ 云服务,全平台
部署方式Lite(本地文件)/ Standalone(Docker)/ Distributed(K8s)/ Cloud本地进程或 Docker纯 Python 库,无服务云托管,无需自部署
扩展性✅ 亿级向量,分布式扩展⚠️ 百万级,单机为主⚠️ 单机内存限制✅ 云端弹性扩展
实时增删✅ 原生支持 add_documents()✅ 支持❌ 不支持,需完整重建✅ 支持
元数据过滤✅ SQL-like 表达式,功能完整✅ 基础过滤❌ 基本不支持✅ 支持
费用开源免费(Lite/Standalone)/ Cloud 按量计费开源免费开源免费按向量数量+查询量计费
主要语言Go(服务)+ Python SDKPythonC++(库)+ Python SDKSaaS(云 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 的工作原理

  1. 先用普通相似度检索,召回 fetch_k=6 篇候选文档
  2. 从候选中迭代选择:每次选"与查询相关,但与已选文档最不相似"的文档
  3. 最终返回 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,                            # 页码
    },
)

元数据设计原则

  1. 字段类型要统一:同一字段在所有文档中类型一致(都是字符串或都是数字),否则过滤会出错
  2. 粒度要合理:不要把一段话的所有属性都塞进元数据,只保留会用于过滤的字段
  3. year 用 int 而非 stryear=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 或空错误信息

原因pymilvusmilvus-lite 版本不兼容,gRPC 协议握手失败。

现象str(e) 为空,错误类型为 grpc.RpcErrorMilvusException

# 来自各脚本的错误处理模式
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专栏代码