RAG 检索增强生成 完整技术指南


一、什么是 RAG?

RAG = Retrieval Augmented Generation
      检索    +    增强    +    生成

核心思想:
  不把知识"硬编码"进模型参数
  而是在回答时实时"查阅外部资料"再作答

类比:
  ❌ 纯 LLM   = 闭卷考试(只凭记忆)
  ✅ RAG       = 开卷考试(可以翻资料)

1.1 为什么需要 RAG?

纯 LLM 的痛点

RAG 的解决方式

知识截止日期,无法获取最新信息

实时检索最新文档

私有领域知识缺失

接入私有知识库

容易产生幻觉,编造事实

基于真实文档生成,可溯源

无法引用具体来源

每条答案附带文档来源

更新知识需重新训练

更新文档库即可,无需训练

1.2 RAG vs 微调 vs 纯提示

三种方案对比

                    知识时效性   私有知识   成本    幻觉控制  溯源性
纯 Prompt 工程         ❌          ❌       低      ❌        ❌
RAG                   ✅          ✅       中      ✅        ✅
微调 Fine-tuning       ❌          ✅       高      ❌        ❌
RAG + 微调(组合)      ✅          ✅       高      ✅✅       ✅

结论:
  知识频繁更新、需要溯源   →  RAG
  固定领域风格/格式定制   →  微调
  最优效果               →  RAG + 微调组合

二、RAG 核心原理

2.1 基础 RAG 三步流程

┌─────────────────────────────────────────────────────────────┐
│                      RAG 基础流程                            │
│                                                             │
│   用户提问                                                   │
│      │                                                      │
│      ▼                                                      │
│  ┌───────────┐   相似度搜索    ┌─────────────┐              │
│  │  问题向量化  │ ────────────▶ │  向量数据库  │              │
│  │ Embedding │               │  知识库文档  │              │
│  └───────────┘               └──────┬──────┘              │
│                                     │ 召回相关文档           │
│                                     ▼                      │
│                              ┌─────────────┐               │
│                              │  上下文组装  │               │
│                              │ 问题+文档片段│               │
│                              └──────┬──────┘               │
│                                     │                      │
│                                     ▼                      │
│                              ┌─────────────┐               │
│                              │     LLM      │               │
│                              │  基于文档生成 │               │
│                              └──────┬──────┘               │
│                                     │                      │
│                                     ▼                      │
│                               最终答案(含来源)              │
└─────────────────────────────────────────────────────────────┘

2.2 离线索引 + 在线检索 双阶段

━━━━━━━━━━━━━━  离线阶段(构建索引)  ━━━━━━━━━━━━━━

原始文档(PDF / Word / 网页 / 数据库)
         │
         ▼
    ① 文档加载      解析各类格式
         │
         ▼
    ② 文本切块      按语义/固定长度切分
         │
         ▼
    ③ 向量化        Embedding 模型编码
         │
         ▼
    ④ 存储索引      写入向量数据库
                   (Milvus / Chroma / Pinecone)

━━━━━━━━━━━━━━  在线阶段(实时检索)  ━━━━━━━━━━━━━━

用户查询
    │
    ▼
① 查询向量化     与文档使用同一 Embedding 模型
    │
    ▼
② 相似度检索     ANN 近似最近邻搜索
    │
    ▼
③ 结果重排序     精排召回内容
    │
    ▼
④ Prompt 组装   将文档片段注入 Prompt
    │
    ▼
⑤ LLM 生成      基于上下文生成答案

2.3 相似度计算原理

import numpy as np

# 向量化示例
text_a = "信号机故障处置流程"
text_b = "信号设备出现问题如何应对"
text_c = "今天天气怎么样"

# Embedding 后变成高维向量(示例用低维演示)
vec_a = embed(text_a)  # [0.8, 0.2, 0.9, ...]
vec_b = embed(text_b)  # [0.7, 0.3, 0.8, ...]  ← 语义相近
vec_c = embed(text_c)  # [0.1, 0.9, 0.2, ...]  ← 语义不相关

# 余弦相似度:向量夹角越小,语义越相近
def cosine_similarity(a, b):
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

print(cosine_similarity(vec_a, vec_b))  # 0.95 ← 高度相似
print(cosine_similarity(vec_a, vec_c))  # 0.12 ← 不相关

三、RAG 技术演进路线

RAG 发展三代

第一代:Naive RAG(朴素 RAG)
  2020~2022  固定切块 → 单路检索 → 直接生成
  问题:检索不准、切块破坏语义、无法处理复杂问题

第二代:Advanced RAG(高级 RAG)
  2022~2023  优化切块 + 查询改写 + 重排序
  改进:显著提升召回质量,但流程仍是线性

第三代:Modular RAG(模块化 RAG)
  2023~今   多路检索 + 动态路由 + Agent 驱动
  特点:灵活组合,解决复杂多步推理问题

四、Naive RAG(朴素 RAG)

4.1 实现代码

from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough

# ── Step 1:文档切块 ───────────────────────────────────────────────
splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,           # 每块 500 字符
    chunk_overlap=50,         # 相邻块重叠 50 字符(防信息割裂)
    separators=["\n\n", "\n", "。", "!", "?", " "]
)
chunks = splitter.split_documents(docs)

# ── Step 2:向量化 + 存储 ──────────────────────────────────────────
vectorstore = Chroma.from_documents(
    documents=chunks,
    embedding=OpenAIEmbeddings()
)
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})

# ── Step 3:构建 RAG Chain ─────────────────────────────────────────
prompt = ChatPromptTemplate.from_template("""
基于以下上下文回答问题,若上下文中没有答案请如实说明:

上下文:
{context}

问题:{question}
""")

rag_chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt
    | ChatOpenAI(model="gpt-4o")
)

answer = rag_chain.invoke("信号机故障如何处置?")

4.2 朴素 RAG 的典型问题

❌ 问题一:切块破坏语义
   "第三条规定员工必须..."  被切成  "第三条规定员" + "工必须..."
   解决:语义感知切块

❌ 问题二:查询与文档表述不一致
   用户问:"信号坏了怎么办"
   文档写:"信号设备故障处置规程"
   → 向量相似度低,召回失败
   解决:查询改写 / HyDE

❌ 问题三:Top-K 召回质量不稳定
   相关文档在第 6 位,但只取 Top-5
   解决:增大 K + 重排序

❌ 问题四:无法处理多跳问题
   "A 站故障会影响哪些下游设备的运行?"
   需要多步推理,单次检索不够
   解决:Multi-hop / 迭代检索

五、Advanced RAG(高级 RAG)

5.1 高级 RAG 全链路

查询输入
   │
   ▼
┌──────────────────────────────────────────────────────┐
│               Pre-Retrieval(检索前优化)              │
│  ① 查询分类    判断问题类型,选择检索策略               │
│  ② 查询改写    同义扩展 / 纠错 / 拆解子问题             │
│  ③ HyDE       生成假设文档,增强语义匹配               │
└──────────────────────┬───────────────────────────────┘
                       │
                       ▼
┌──────────────────────────────────────────────────────┐
│               Retrieval(混合检索)                    │
│  ④ 稠密检索    向量 ANN 语义搜索                       │
│  ⑤ 稀疏检索    BM25 关键词搜索                        │
│  ⑥ RRF 融合   倒排排名融合,综合两路结果               │
└──────────────────────┬───────────────────────────────┘
                       │
                       ▼
┌──────────────────────────────────────────────────────┐
│               Post-Retrieval(检索后优化)             │
│  ⑦ 重排序      Cross-Encoder 精排                    │
│  ⑧ 上下文压缩  去除冗余,保留核心信息                  │
│  ⑨ 来源标注   每段内容附带文档来源                     │
└──────────────────────┬───────────────────────────────┘
                       │
                       ▼
                  LLM 生成答案

5.2 查询改写(Query Rewriting)

from langchain_core.output_parsers import StrOutputParser

# ── 方法一:同义改写 ────────────────────────────────────────────────
rewrite_prompt = ChatPromptTemplate.from_template("""
将以下问题改写为更适合文档检索的形式:
- 展开缩写和口语化表达
- 补充领域专业术语
- 保持原意不变

原始问题:{question}
改写后:
""")

rewrite_chain = rewrite_prompt | llm | StrOutputParser()

# ── 方法二:Multi-Query(生成多路查询)──────────────────────────────
multi_query_prompt = ChatPromptTemplate.from_template("""
为以下问题生成 3 个不同角度的检索查询,每行一个:
问题:{question}

3个查询:
""")

def multi_query_retrieve(question: str) -> list:
    """多路查询 → 并行检索 → 结果去重合并"""
    queries_text = (multi_query_prompt | llm | StrOutputParser()).invoke(
        {"question": question}
    )
    queries = [q.strip() for q in queries_text.split("\n") if q.strip()]

    # 并行检索所有子查询
    all_docs = []
    for query in queries:
        docs = retriever.invoke(query)
        all_docs.extend(docs)

    # 按 doc_id 去重
    seen = set()
    unique_docs = []
    for doc in all_docs:
        doc_id = doc.metadata.get("id", doc.page_content[:50])
        if doc_id not in seen:
            seen.add(doc_id)
            unique_docs.append(doc)

    return unique_docs

5.3 HyDE(假设文档嵌入)

def hyde_retrieve(question: str) -> list:
    """
    HyDE 核心思想:
    直接检索:question_vec → 匹配 doc_vec  (query 和 doc 在不同分布)
    HyDE 检索:生成假设文档 → hypo_doc_vec → 匹配 doc_vec(同分布,效果更好)
    """
    # Step1:让 LLM 生成一段假设性的答案文档
    hypo_prompt = ChatPromptTemplate.from_template("""
    请针对以下问题,生成一段可能包含答案的文档片段(约200字):
    问题:{question}
    文档片段:
    """)
    hypothesis_doc = (hypo_prompt | llm | StrOutputParser()).invoke(
        {"question": question}
    )

    # Step2:用假设文档的向量去检索,而不是用问题向量
    docs = vectorstore.similarity_search(
        hypothesis_doc,   # ← 关键:用假设文档而非原始问题
        k=5
    )
    return docs

# 效果对比:
# 原始问题:  "信号坏了咋整"       → 口语化,向量偏移
# 假设文档:  "信号设备故障处置规程第X条规定,当信号机出现..." → 专业化,向量对齐

5.4 混合检索 + RRF 融合

from pymilvus import AnnSearchRequest, WeightedRanker

class HybridRetriever:
    """稠密 + 稀疏混合检索,RRF 融合"""

    def retrieve(self, query: str, top_k: int = 10) -> list:

        # ① 稠密检索(语义理解强)
        dense_results = self._dense_search(
            query_vec=self.embedder.embed(query),
            top_k=top_k * 2
        )

        # ② 稀疏检索(关键词精确匹配强)
        sparse_results = self._sparse_search(
            query=query,
            top_k=top_k * 2
        )

        # ③ RRF 倒排融合
        return self._rrf_fusion(
            [dense_results, sparse_results],
            k=60,             # RRF 平滑系数
            top_k=top_k
        )

    def _rrf_fusion(self, result_lists: list,
                     k: int = 60, top_k: int = 10) -> list:
        """
        RRF 公式:score(d) = Σ 1/(k + rank(d))
        某文档在多个列表中排名越靠前,综合分越高
        """
        scores: dict[str, float] = {}
        doc_map: dict[str, object] = {}

        for result_list in result_lists:
            for rank, doc in enumerate(result_list):
                doc_id = doc.metadata["id"]
                scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank + 1)
                doc_map[doc_id] = doc

        # 按融合分排序
        sorted_ids = sorted(scores, key=lambda x: scores[x], reverse=True)
        return [doc_map[doc_id] for doc_id in sorted_ids[:top_k]]

5.5 重排序(Reranking)

from sentence_transformers import CrossEncoder

class BGEReranker:
    """
    两阶段检索:
    粗排(向量 ANN):速度快,召回 Top-20~50
    精排(CrossEncoder):精度高,从中选 Top-5
    
    CrossEncoder 与 Bi-Encoder 区别:
    Bi-Encoder:query 和 doc 分别编码,计算向量相似度(快,但精度略低)
    CrossEncoder:query + doc 拼接输入,直接输出相关性分数(慢,但精度高)
    """

    def __init__(self):
        self.model = CrossEncoder("BAAI/bge-reranker-v2-m3")

    def rerank(self, query: str,
                candidates: list, top_k: int = 5) -> list:
        # 构造 (query, doc) 配对
        pairs = [(query, doc.page_content) for doc in candidates]

        # CrossEncoder 计算每对的相关性分数
        scores = self.model.predict(pairs)

        # 按分数降序排列
        scored_docs = sorted(
            zip(scores, candidates),
            key=lambda x: x[0],
            reverse=True
        )
        return [doc for _, doc in scored_docs[:top_k]]

5.6 上下文压缩

from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor

# 上下文压缩:从召回文档中只提取与问题相关的部分
# 避免无关内容干扰 LLM,节省 Token
compressor = LLMChainExtractor.from_llm(llm)

compression_retriever = ContextualCompressionRetriever(
    base_compressor=compressor,
    base_retriever=retriever
)

# 原始召回:500字完整文档片段
# 压缩后:  只保留与问题直接相关的 2~3 句话
compressed_docs = compression_retriever.invoke("信号故障处置流程")

六、Advanced RAG 切块策略

切块策略选型

┌─────────────────────────────────────────────────────┐
│  策略一:固定大小切块(最简单)                        │
│  chunk_size=500, overlap=50                         │
│  优点:实现简单   缺点:破坏语义边界                  │
├─────────────────────────────────────────────────────┤
│  策略二:递归字符切块(推荐默认)                      │
│  按段落→句子→词语优先级递归切分                       │
│  优点:尽量保持语义完整性                            │
├─────────────────────────────────────────────────────┤
│  策略三:语义切块(效果最好)                          │
│  相邻句子向量相似度低于阈值时切割                     │
│  优点:切块边界完全语义对齐                          │
├─────────────────────────────────────────────────────┤
│  策略四:父文档检索(Parent Document)                │
│  小块检索 + 大块生成                                │
│  索引用小块(精准召回)                              │
│  送给LLM用大块(完整上下文)                         │
└─────────────────────────────────────────────────────┘

6.1 语义切块实现

from sklearn.metrics.pairwise import cosine_similarity
import numpy as np

class SemanticChunker:
    """语义感知切块:相邻句子语义差异大时切割"""

    def __init__(self, embedder, threshold: float = 0.3):
        self.embedder = embedder
        self.threshold = threshold      # 相似度差异阈值

    def chunk(self, text: str) -> list[str]:
        # ① 按句子切分
        sentences = self._split_sentences(text)
        if len(sentences) <= 1:
            return sentences

        # ② 计算相邻句子的向量距离
        embeddings = self.embedder.embed_documents(sentences)
        breakpoints = []

        for i in range(len(embeddings) - 1):
            sim = cosine_similarity(
                [embeddings[i]], [embeddings[i + 1]]
            )[0][0]
            distance = 1 - sim      # 距离 = 1 - 相似度

            # 距离超过阈值 → 语义发生跳变 → 切割点
            if distance > self.threshold:
                breakpoints.append(i)

        # ③ 按切割点合并句子
        return self._merge_by_breakpoints(sentences, breakpoints)

6.2 父文档检索(Parent Document Retriever)

from langchain.retrievers import ParentDocumentRetriever
from langchain.storage import InMemoryStore

# 小块:用于索引检索(chunk_size=200,精准匹配)
child_splitter = RecursiveCharacterTextSplitter(chunk_size=200)

# 大块:检索命中后返回给 LLM(chunk_size=1000,完整上下文)
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=1000)

# 构建父文档检索器
parent_retriever = ParentDocumentRetriever(
    vectorstore=vectorstore,             # 存储小块向量
    docstore=InMemoryStore(),            # 存储大块原文
    child_splitter=child_splitter,
    parent_splitter=parent_splitter,
)

parent_retriever.add_documents(docs)

# 检索流程:
# 1. 用小块 (200字) 向量检索 → 精准定位相关位置
# 2. 找到对应父块 (1000字)   → 返回完整上下文给 LLM
result = parent_retriever.invoke("信号故障处置")

七、Modular RAG(模块化 RAG)

7.1 整体架构

Modular RAG:将 RAG 各环节解耦为独立模块,按需组合

┌──────────────────────────────────────────────────────┐
│                   路由模块(Router)                   │
│  根据问题类型动态选择检索策略                           │
│  简单事实查询 → 向量检索                               │
│  复杂推理问题 → Multi-hop 检索                        │
│  实时信息需求 → 网络搜索 Tool                         │
└────────────────────────┬─────────────────────────────┘
                         │
         ┌───────────────┼───────────────┐
         ▼               ▼               ▼
    向量检索         网络搜索         数据库查询
    (知识库)        (实时信息)      (结构化数据)
         │               │               │
         └───────────────┴───────────────┘
                         │ 多路结果融合
                         ▼
                   重排序 + 压缩
                         │
                         ▼
              ┌─────────────────────┐
              │  生成模块(Generator)│
              │  引用验证 + 答案生成  │
              └─────────────────────┘

7.2 Self-RAG(自适应检索)

"""
Self-RAG 核心思想:
让 LLM 自己决定"是否需要检索"、"检索结果是否有用"

四个特殊 Token:
  [Retrieve]      → 模型判断需要检索
  [Relevant]      → 检索结果与问题相关
  [Irrelevant]    → 检索结果不相关
  [Supported]     → 答案有文档支撑
  [Unsupported]   → 答案无文档支撑(幻觉)
"""

from langgraph.graph import StateGraph, END
from typing import TypedDict, List

class SelfRAGState(TypedDict):
    question:       str
    need_retrieve:  bool        # 是否需要检索
    documents:      List[str]   # 检索到的文档
    is_relevant:    bool        # 文档是否相关
    answer:         str         # 生成的答案
    is_supported:   bool        # 答案是否有依据
    iteration:      int         # 迭代次数

def check_retrieve_node(state: SelfRAGState) -> dict:
    """判断是否需要检索(LLM 自主决策)"""
    prompt = f"""
    问题:{state['question']}
    
    判断是否需要检索外部文档来回答这个问题?
    输出 JSON:{{"need_retrieve": true/false, "reason": "..."}}
    """
    result = json.loads(llm.invoke(prompt).content)
    return {"need_retrieve": result["need_retrieve"]}

def relevance_check_node(state: SelfRAGState) -> dict:
    """检索后判断文档是否相关"""
    prompt = f"""
    问题:{state['question']}
    检索到的文档:{state['documents'][0]}
    
    这些文档是否与问题相关?
    输出 JSON:{{"is_relevant": true/false}}
    """
    result = json.loads(llm.invoke(prompt).content)
    return {"is_relevant": result["is_relevant"]}

def support_check_node(state: SelfRAGState) -> dict:
    """验证答案是否有文档支撑(防幻觉)"""
    prompt = f"""
    文档:{state['documents']}
    生成的答案:{state['answer']}
    
    答案中的每个关键信息是否都能在文档中找到依据?
    输出 JSON:{{"is_supported": true/false, "unsupported_claims": [...]}}
    """
    result = json.loads(llm.invoke(prompt).content)
    return {"is_supported": result["is_supported"]}

# 构建 Self-RAG 图
graph = StateGraph(SelfRAGState)
graph.add_node("check_retrieve",  check_retrieve_node)
graph.add_node("retrieve",        retrieve_node)
graph.add_node("relevance_check", relevance_check_node)
graph.add_node("generate",        generate_node)
graph.add_node("support_check",   support_check_node)

graph.set_entry_point("check_retrieve")

# 条件路由
graph.add_conditional_edges("check_retrieve", 
    lambda s: "retrieve" if s["need_retrieve"] else "generate")
graph.add_conditional_edges("relevance_check",
    lambda s: "generate" if s["is_relevant"] else "retrieve")  # 不相关重新检索
graph.add_conditional_edges("support_check",
    lambda s: END if s["is_supported"] else "retrieve")        # 无依据重新检索

7.3 Multi-hop RAG(多跳推理)

"""
多跳问题示例:
"A 站信号故障会影响哪条线路的换乘,该换乘线路的备用方案是什么?"

需要:
  Hop1: 查 A 站 → 找换乘线路 B
  Hop2: 查线路 B → 找备用方案

单次检索无法完成,需要迭代推理
"""

class MultiHopRetriever:

    def retrieve(self, question: str,
                  max_hops: int = 3) -> list:
        all_docs = []
        current_query = question

        for hop in range(max_hops):
            # 检索当前查询
            docs = retriever.invoke(current_query)
            all_docs.extend(docs)

            # LLM 判断是否需要继续检索
            context = "\n".join([d.page_content for d in docs])
            check_prompt = f"""
            原始问题:{question}
            已检索内容:{context}
            
            当前信息是否足够回答原始问题?
            若不够,生成下一跳检索查询。
            输出 JSON:
            {{"sufficient": true/false, "next_query": "..."}}
            """
            result = json.loads(llm.invoke(check_prompt).content)

            if result["sufficient"] or hop == max_hops - 1:
                break

            # 更新查询,进行下一跳
            current_query = result["next_query"]
            print(f"Hop {hop+1} → 新查询: {current_query}")

        return all_docs

八、RAG 评估体系(RAGAS)

8.1 核心评估指标

RAGAS 四大核心指标

┌─────────────────────────────────────────────────────┐
│  Faithfulness(忠实度)                              │
│  答案中的每个陈述是否都能在检索文档中找到依据          │
│  目标:> 0.90   幻觉检测核心指标                     │
├─────────────────────────────────────────────────────┤
│  Answer Relevancy(答案相关性)                      │
│  生成的答案是否真正回答了用户的问题                   │
│  目标:> 0.85                                       │
├─────────────────────────────────────────────────────┤
│  Context Precision(上下文精确率)                   │
│  检索到的文档中有多少比例是真正有用的                 │
│  目标:> 0.80   衡量检索精准度                       │
├─────────────────────────────────────────────────────┤
│  Context Recall(上下文召回率)                      │
│  回答问题所需的信息有多少被成功检索到                 │
│  目标:> 0.85   衡量检索完整度                       │
└─────────────────────────────────────────────────────┘

8.2 RAGAS 评测代码

from ragas import evaluate
from ragas.metrics import (
    faithfulness,
    answer_relevancy,
    context_precision,
    context_recall,
)
from datasets import Dataset

# 构建评测数据集
eval_samples = [
    {
        "question":     "信号机故障应急处置流程是什么?",
        "answer":       rag_chain.invoke("信号机故障应急处置流程是什么?"),
        "contexts":     [doc.page_content for doc in retriever.invoke("信号机故障")],
        "ground_truth": "根据规程第X条,应立即..."  # 专家标注的标准答案
    },
    # ... 更多测试样本
]

dataset = Dataset.from_list(eval_samples)

results = evaluate(
    dataset=dataset,
    metrics=[faithfulness, answer_relevancy,
             context_precision, context_recall]
)

print(results)
# Output:
# faithfulness:       0.92
# answer_relevancy:   0.88
# context_precision:  0.85
# context_recall:     0.87

九、RAG 常见问题与优化方案

┌──────────────────────────────────────────────────────────────┐
│  问题             │  症状                │  优化方案           │
├──────────────────────────────────────────────────────────────┤
│  召回率低          │ 答非所问,信息缺失   │ HyDE + 多路查询     │
│  检索精度低        │ 召回大量无关文档     │ 重排序 + 元数据过滤  │
│  切块破坏语义      │ 答案不完整/断章取义  │ 语义切块 + 父文档   │
│  LLM 产生幻觉     │ 答案无文档支撑       │ Self-RAG 验证机制   │
│  多跳问题处理不了  │ 复杂推理失败        │ Multi-hop 迭代检索  │
│  响应速度慢        │ 用户等待时间长      │ 异步检索 + 流式输出  │
│  知识更新不及时    │ 答案基于旧版本       │ 增量索引 + 版本管理  │
└──────────────────────────────────────────────────────────────┘

十、RAG 技术栈总览

┌─────────────────────────────────────────────────────────┐
│                  RAG 完整技术栈                           │
│                                                         │
│  文档解析   MinerU / PyPDF2 / Unstructured              │
│  文本切块   LangChain Splitter / 语义切块                │
│  Embedding  BGE-M3(中英双语,稠密+稀疏)               │
│  向量数据库  Milvus(生产)/ Chroma(原型)              │
│  稀疏检索   BM25 / Elasticsearch                        │
│  重排序     BGE-Reranker-v2 / Cohere Rerank             │
│  RAG 框架   LangChain / LlamaIndex                      │
│  Agent 编排 LangGraph(复杂多跳 RAG)                   │
│  评估框架   RAGAS(全自动评测)                          │
│  链路追踪   LangSmith(可观测性)                        │
└─────────────────────────────────────────────────────────┘

十一、三代 RAG 核心差异总结

Naive RAG      固定切块 → 单路向量检索 → 直接生成
                简单,但召回质量有限

Advanced RAG   智能切块 + 查询改写 + 混合检索 + 重排序
                工程优化,显著提升召回质量

Modular RAG    路由 + 多路检索 + 迭代推理 + 自我验证
                灵活组合,解决复杂业务场景

选型建议:
  快速验证原型         →  Naive RAG
  生产环境标准场景     →  Advanced RAG(首选)
  复杂多步推理场景     →  Modular RAG + LangGraph

核心结论:RAG 本质是"让 LLM 有据可查",提升效果的关键在于 切块质量 > 检索召回 > 重排精度 > Prompt 设计,四个环节环环相扣,任何一环薄弱都会影响最终效果。