跳转至

高级 RAG 技术

⚠️ 时效性说明:本章涉及前沿模型/价格/榜单等信息,可能随版本快速变化;请以论文原文、官方发布页和 API 文档为准。

📌 定位说明:本章侧重高级 RAG 实战( GraphRAG 、 Agentic RAG 等)。 - 📖 RAG 的 NLP 理论基础(检索原理、 Embedding 技术、评估方法)请参考 自然语言处理/14-RAG 系统设计 - 📖 RAG 研究前沿与长文本技术请参考 LLM 学习/04-前沿探索/03-RAG 与长文本

📌 RAG 从简单的"检索+生成"演进为融合知识图谱、 Agent 自适应检索、多步推理的复杂系统。掌握 GraphRAG 、 Agentic RAG 、高级检索策略与评估方法,是构建生产级 RAG 系统的核心竞争力。

前置知识: RAG 系统构建(第 5 章)、向量数据库(第 6 章)、 LangChain 框架(第 8 章)

2026-03-26 复核说明: - OpenAI 当前通用旗舰模型口径以 gpt-5.4 为主,本文教学示例默认使用成本更低的 gpt-5-mini - 原生 OpenAI 新项目优先采用 Responses API;本文因聚焦 LangChain 实战,保留 ChatOpenAI / OpenAIEmbeddings 封装写法 - Chroma 的 LangChain 集成已迁移到 langchain-chroma 包,对应导入写法为 from langchain_chroma import Chroma

🎯 学习目标

  • 理解 RAG 技术从 Naive 到 Agentic 的演进脉络
  • 掌握 GraphRAG 的知识图谱构建与检索机制
  • 理解 Agentic RAG 的自适应检索策略( Self-RAG 、 CRAG )
  • 熟练使用高级检索策略(混合检索、查询转换、上下文压缩)
  • 掌握 RAG 评估框架( RAGAS 、 TruLens )的使用方法
  • 能够设计和实现生产级高级 RAG 管线
  • 掌握 RAG 相关面试高频考点

18.1 RAG 技术演进

18.1.1 四代 RAG 架构

Text Only
┌─────────────┐    ┌──────────────┐    ┌─────────────┐    ┌──────────────┐
│  Naive RAG  │───>│ Advanced RAG │───>│ Modular RAG │───>│ Agentic RAG  │
│  (基础RAG)  │    │ (高级RAG)     │    │ (模块化RAG)  │    │ (智能体RAG)   │
└─────────────┘    └──────────────┘    └─────────────┘    └──────────────┘
    2023初              2023中              2024初             2024-2025
阶段 核心特征 代表技术 局限性
Naive RAG 简单的检索-生成管线 基础向量检索 + LLM 生成 检索质量差、缺乏推理
Advanced RAG 预检索/后检索优化 查询重写、重排序、 HyDE 静态管线、缺乏自适应
Modular RAG 模块化、可组合 插件式模块、路由、融合 需人工设计管线
Agentic RAG Agent 驱动的自适应检索 Self-RAG 、 CRAG 、多步推理 延迟增加、成本上升

18.1.2 Naive RAG 的核心问题

Python
"""Naive RAG:基础检索-生成管线"""
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_chroma import Chroma
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

# 1. 文档加载与分块
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,
    chunk_overlap=50
)
# docs = text_splitter.split_documents(raw_docs)

# 2. 向量化与存储
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
# vectorstore = Chroma.from_documents(docs, embeddings)
# retriever = vectorstore.as_retriever(search_kwargs={"k": 4})

# 3. 生成
llm = ChatOpenAI(model="gpt-5-mini")
prompt = ChatPromptTemplate.from_template("""
基于以下上下文回答问题。如果上下文不包含答案,请说明。

上下文:{context}
问题:{question}
答案:""")

# chain = (
#     {"context": retriever | format_docs, "question": RunnablePassthrough()}
#     | prompt
#     | llm
#     | StrOutputParser()
# )

Naive RAG 的核心问题: 1. 检索粒度粗:固定 chunk size 丢失上下文 2. 语义鸿沟:查询和文档 embedding 不总是对齐 3. 无关信息干扰:检索到的内容可能包含噪声 4. 缺乏推理:无法处理需要多步推理的问题 5. 无自适应:不论问题复杂度都用相同管线


18.2 GraphRAG (微软)

18.2.1 GraphRAG 核心思想

GraphRAG 由微软研究院提出,将知识图谱与 RAG 结合,特别擅长处理需要全局理解的问题。

核心流程

Text Only
原始文档 → 实体提取 → 关系抽取 → 知识图谱构建 → 社区检测 → 社区摘要 → 查询
                                                    (Leiden算法)

与传统 RAG 的对比

特性 传统向量 RAG GraphRAG
数据结构 向量数据库 知识图谱 + 社区摘要
检索方式 相似度检索 图遍历 + 社区索引
全局问题 效果差 效果好
局部问题 效果好 效果好
构建成本 高(大量 LLM 调用)
更新成本 中高

18.2.2 知识图谱构建

Python
"""GraphRAG知识图谱构建(使用graphrag库)"""
# python -m pip install -U graphrag

# 方式1: 使用微软graphrag CLI
# mkdir ragtest && cd ragtest
# graphrag init
# graphrag index

# 方式2: 使用LangChain + LLM构建知识图谱
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from pydantic import BaseModel, Field

class Entity(BaseModel):  # Pydantic BaseModel:自动数据验证和序列化
    name: str = Field(description="实体名称")
    type: str = Field(description="实体类型,如:人物/组织/技术/概念")
    description: str = Field(description="实体描述")

class Relationship(BaseModel):
    source: str = Field(description="源实体")
    target: str = Field(description="目标实体")
    relation: str = Field(description="关系类型")
    description: str = Field(description="关系描述")

class KnowledgeGraph(BaseModel):
    entities: list[Entity] = Field(description="实体列表")
    relationships: list[Relationship] = Field(description="关系列表")

llm = ChatOpenAI(model="gpt-5-mini")

kg_prompt = ChatPromptTemplate.from_messages([
    ("system", """你是一个知识图谱构建专家。从给定文本中提取实体和关系。
要求:
1. 提取所有重要实体(人物、组织、技术、概念等)
2. 提取实体间的关系
3. 每个实体和关系都需要有描述"""),
    ("human", "请从以下文本中提取知识图谱:\n\n{text}")
])

kg_chain = kg_prompt | llm.with_structured_output(KnowledgeGraph)

# 示例文本
text = """
OpenAI提供 GPT-5.4 作为当前通用旗舰模型,也提供成本更低的 gpt-5-mini。
Sam Altman是OpenAI的CEO,他推动了ChatGPT的商业化。
Google提供Gemini系列模型,与OpenAI在通用模型与Agent能力上持续竞争。
LangChain是一个流行的LLM应用开发框架,支持RAG和Agent开发。
"""

kg = kg_chain.invoke({"text": text})
print("实体数量:", len(kg.entities))
for entity in kg.entities:
    print(f"  [{entity.type}] {entity.name}: {entity.description}")

print("\n关系数量:", len(kg.relationships))
for rel in kg.relationships:
    print(f"  {rel.source} --[{rel.relation}]--> {rel.target}")

18.2.3 社区检测与摘要( Leiden 算法)

Python
"""社区检测与社区摘要"""
import networkx as nx
# pip install graspologic
from graspologic.partition import hierarchical_leiden

def build_graph_and_detect_communities(kg: KnowledgeGraph):
    """构建NetworkX图并进行社区检测"""
    G = nx.Graph()

    # 添加节点
    for entity in kg.entities:
        G.add_node(entity.name, type=entity.type, description=entity.description)

    # 添加边
    for rel in kg.relationships:
        G.add_edge(rel.source, rel.target, relation=rel.relation, description=rel.description)

    # Leiden算法社区检测
    # hierarchical_leiden 返回 HierarchicalCluster 列表,每项有 node, cluster, level 等属性
    community_mapping = hierarchical_leiden(G, max_cluster_size=10)

    # 社区分组
    communities = {}
    for item in community_mapping:
        community_id = item.cluster
        node_id = item.node
        if community_id not in communities:
            communities[community_id] = []
        communities[community_id].append(node_id)

    return G, communities

def generate_community_summaries(communities: dict, G: nx.Graph, llm) -> dict:
    """为每个社区生成摘要"""
    summaries = {}
    for community_id, nodes in communities.items():
        # 收集社区内的实体和关系信息
        community_info = []
        for node in nodes:
            node_data = G.nodes[node]
            community_info.append(f"实体: {node} ({node_data.get('type', 'unknown')})")
            for neighbor in G.neighbors(node):
                if neighbor in nodes:
                    edge_data = G.edges[node, neighbor]
                    community_info.append(f"  关系: {node} --{edge_data.get('relation', '')}--> {neighbor}")

        # LLM生成社区摘要
        info_text = "\n".join(community_info)
        summary = llm.invoke(f"请为以下知识图谱社区生成一段摘要:\n{info_text}")
        summaries[community_id] = summary.content

    return summaries

截至当前官方文档,GraphRAG 的 Query Engine 已提供 Local SearchGlobal SearchDRIFT SearchBasic Search。下面聚焦最常用的 Global SearchLocal Search

Python
"""GraphRAG查询模式"""

class GraphRAGQueryEngine:
    """GraphRAG查询引擎"""

    def __init__(self, community_summaries, graph, vectorstore, llm):
        self.community_summaries = community_summaries
        self.graph = graph
        self.vectorstore = vectorstore
        self.llm = llm

    def global_search(self, query: str) -> str:
        """
        Global Search:适合全局性/总结性问题
        策略:使用社区摘要进行Map-Reduce
        例如:"数据集中有哪些主要主题?"
        """
        # Map阶段:对每个社区摘要生成部分答案
        partial_answers = []
        for community_id, summary in self.community_summaries.items():
            response = self.llm.invoke(
                f"基于以下社区信息回答问题(如无关则回复N/A):\n"
                f"社区信息:{summary}\n问题:{query}"
            )
            if "N/A" not in response.content:
                partial_answers.append(response.content)

        # Reduce阶段:合并部分答案
        combined = "\n\n".join(partial_answers)
        final = self.llm.invoke(
            f"请综合以下信息回答问题:\n\n{combined}\n\n问题:{query}"
        )
        return final.content

    def local_search(self, query: str, top_k: int = 5) -> str:
        """
        Local Search:适合具体/局部问题
        策略:结合向量检索 + 图谱邻居遍历
        例如:"OpenAI的CEO是谁?"
        """
        # 向量检索相关实体
        relevant_docs = self.vectorstore.similarity_search(query, k=top_k)

        # 图扩展:获取相关实体的邻居信息
        context_parts = []
        for doc in relevant_docs:
            entity_name = doc.metadata.get("entity_name", "")
            if entity_name in self.graph:
                neighbors = list(self.graph.neighbors(entity_name))
                for neighbor in neighbors:
                    edge_data = self.graph.edges[entity_name, neighbor]
                    context_parts.append(
                        f"{entity_name} --[{edge_data.get('relation', '')}]--> {neighbor}"
                    )

        context = "\n".join(context_parts)
        response = self.llm.invoke(
            f"基于以下知识图谱信息回答问题:\n{context}\n\n问题:{query}"
        )
        return response.content

18.3 Agentic RAG

18.3.1 Agent 驱动的自适应检索

Agentic RAG 让 Agent 根据查询特点动态决定检索策略,而非使用固定管线。

Text Only
用户查询 → Agent判断 ─→ 直接用LLM回答(简单问题)
                      ├→ 向量检索(事实性问题)
                      ├→ 图谱检索(关系性问题)
                      ├→ SQL查询(结构化数据)
                      └→ 多步检索(复杂问题)

18.3.2 查询路由( Router )

Python
"""查询路由器:根据问题类型选择检索策略"""
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from pydantic import BaseModel, Field
from typing import Literal

class RouteDecision(BaseModel):
    """路由决策"""
    datasource: Literal["vectorstore", "knowledge_graph", "web_search", "direct_llm"] = Field(  # Literal限定取值范围
        description="选择数据源"
    )
    reasoning: str = Field(description="选择理由")

llm = ChatOpenAI(model="gpt-5-mini")

router_prompt = ChatPromptTemplate.from_messages([
    ("system", """你是一个查询路由专家。根据用户问题选择最佳数据源:

- vectorstore: 需要从企业知识库检索的事实性问题
- knowledge_graph: 涉及实体关系、「谁是谁」、「之间有什么联系」的问题
- web_search: 需要最新信息、实时数据的问题
- direct_llm: 通用知识、不需要外部检索的问题"""),
    ("human", "用户问题:{question}")
])

router_chain = router_prompt | llm.with_structured_output(RouteDecision)

# 路由使用
questions = [
    "公司的退款政策是什么?",           # → vectorstore
    "OpenAI和Google在AI领域的竞争关系?", # → knowledge_graph
    "今天的股票行情怎样?",              # → web_search
    "什么是快速排序算法?",              # → direct_llm
]

for q in questions:
    decision = router_chain.invoke({"question": q})
    print(f"Q: {q}")
    print(f"→ {decision.datasource} ({decision.reasoning})\n")

18.3.3 Self-RAG (自我反思检索)

Self-RAG 通过"反思标记"让模型决定是否需要检索、检索结果是否相关、生成是否有依据。

Python
"""Self-RAG实现"""
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from pydantic import BaseModel, Field

llm = ChatOpenAI(model="gpt-5-mini")

class RetrievalDecision(BaseModel):
    """是否需要检索"""
    need_retrieval: bool = Field(description="是否需要外部检索")
    reason: str = Field(description="原因")

class RelevanceCheck(BaseModel):
    """检索结果相关性"""
    is_relevant: bool = Field(description="检索结果是否与问题相关")
    relevant_parts: str = Field(description="相关部分摘要")

class GroundednessCheck(BaseModel):
    """生成结果的依据性"""
    is_grounded: bool = Field(description="回答是否有检索内容支撑")
    score: float = Field(description="依据性评分0-1")

def self_rag(question: str, retriever) -> str:
    """Self-RAG完整流程"""

    # Step 1: 判断是否需要检索
    decision = (
        ChatPromptTemplate.from_template("问题:{question}\n是否需要外部检索来回答?")
        | llm.with_structured_output(RetrievalDecision)
    ).invoke({"question": question})

    if not decision.need_retrieval:
        # 直接生成
        return llm.invoke(f"请回答:{question}").content

    # Step 2: 检索
    docs = retriever.invoke(question)
    context = "\n".join([doc.page_content for doc in docs])

    # Step 3: 检查检索结果相关性
    relevance = (
        ChatPromptTemplate.from_template(
            "问题:{question}\n检索内容:{context}\n检索内容是否与问题相关?"
        )
        | llm.with_structured_output(RelevanceCheck)
    ).invoke({"question": question, "context": context})

    if not relevance.is_relevant:
        # 检索不相关,重新检索或直接生成
        return llm.invoke(f"请回答(注意:没有找到相关参考资料):{question}").content

    # Step 4: 基于检索内容生成回答
    answer = llm.invoke(
        f"基于以下内容回答问题:\n\n参考内容:{context}\n\n问题:{question}"
    ).content

    # Step 5: 检查生成结果的依据性
    groundedness = (
        ChatPromptTemplate.from_template(
            "参考内容:{context}\n生成回答:{answer}\n回答是否有参考内容支撑?"
        )
        | llm.with_structured_output(GroundednessCheck)
    ).invoke({"context": context, "answer": answer})

    if not groundedness.is_grounded or groundedness.score < 0.7:
        # 依据性不足,重新生成
        answer = llm.invoke(
            f"请严格基于以下内容回答,不要加入额外信息:\n{context}\n\n问题:{question}"
        ).content

    return answer

18.3.4 CRAG ( Corrective RAG )

Python
"""CRAG: 修正性RAG"""
from typing import Literal
from pydantic import BaseModel, Field

class DocumentGrade(BaseModel):
    """文档评分"""
    grade: Literal["relevant", "ambiguous", "irrelevant"] = Field(
        description="文档与问题的相关程度"
    )

def corrective_rag(question: str, retriever, llm) -> str:
    """
    CRAG流程:
    1. 检索文档
    2. 评估每个文档的相关性
    3. 根据评估结果采取不同策略:
       - 所有相关 → 直接生成
       - 部分相关 → 补充网络搜索
       - 都不相关 → 完全使用网络搜索
    """
    # Step 1: 初始检索
    docs = retriever.invoke(question)

    # Step 2: 文档评分
    grader_prompt = ChatPromptTemplate.from_template(
        "问题:{question}\n文档:{document}\n请评估该文档与问题的相关程度。"
    )
    grader = grader_prompt | llm.with_structured_output(DocumentGrade)

    relevant_docs = []
    for doc in docs:
        grade = grader.invoke({"question": question, "document": doc.page_content})
        if grade.grade == "relevant":
            relevant_docs.append(doc)

    # Step 3: 根据评分采取策略
    relevance_ratio = len(relevant_docs) / len(docs) if docs else 0

    if relevance_ratio >= 0.8:
        # 高相关:直接使用检索结果
        context = "\n".join([d.page_content for d in relevant_docs])
        action = "direct_generate"
    elif relevance_ratio >= 0.3:
        # 部分相关:补充网络搜索
        context = "\n".join([d.page_content for d in relevant_docs])
        web_results = web_search(question)  # 补充搜索
        context += f"\n\n补充信息:{web_results}"
        action = "augmented_generate"
    else:
        # 低相关:完全依赖网络搜索
        context = web_search(question)
        action = "web_generate"

    # Step 4: 生成回答
    answer = llm.invoke(
        f"[策略: {action}]\n基于以下内容回答:\n{context}\n\n问题:{question}"
    ).content

    return answer

def web_search(query: str) -> str:
    """模拟网络搜索"""
    return f"网络搜索'{query}'的结果..."

18.4 高级检索策略

18.4.1 多向量检索( Multi-Vector Retrieval )

Python
"""多向量检索:父文档检索策略"""
from langchain.retrievers.multi_vector import MultiVectorRetriever
from langchain.storage import InMemoryStore
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_core.documents import Document
import uuid

embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma(collection_name="multi_vector", embedding_function=embeddings)
doc_store = InMemoryStore()  # 存储原始大文档(InMemoryStore支持Document对象)

retriever = MultiVectorRetriever(
    vectorstore=vectorstore,
    docstore=doc_store,
    id_key="doc_id"
)

# 策略1: 父文档+子chunk
# 用小chunk检索,返回大的父文档
parent_docs = [
    Document(page_content="这是一篇很长的文章..." * 100, metadata={"source": "doc1"})
]

child_splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=20)

for parent_doc in parent_docs:
    doc_id = str(uuid.uuid4())
    child_docs = child_splitter.split_documents([parent_doc])
    for child in child_docs:
        child.metadata["doc_id"] = doc_id

    retriever.vectorstore.add_documents(child_docs)
    retriever.docstore.mset([(doc_id, parent_doc)])  # 存储父文档

# 策略2: 文档摘要检索
# 用摘要的embedding检索,返回原始文档
llm = ChatOpenAI(model="gpt-5-mini")

for doc in parent_docs:
    doc_id = str(uuid.uuid4())
    summary = llm.invoke(f"请用一句话总结:{doc.page_content[:1000]}").content
    summary_doc = Document(page_content=summary, metadata={"doc_id": doc_id})

    retriever.vectorstore.add_documents([summary_doc])
    retriever.docstore.mset([(doc_id, doc)])

18.4.2 混合检索( Hybrid Retrieval )

Python
"""混合检索:Dense + Sparse + Reranking"""
from langchain_community.retrievers import BM25Retriever
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma
from langchain.retrievers import EnsembleRetriever
from langchain_core.documents import Document

# 准备文档
docs = [
    Document(page_content="LangChain是一个用于构建LLM应用的框架"),
    Document(page_content="向量数据库用于存储和检索嵌入向量"),
    Document(page_content="RAG是检索增强生成的缩写"),
    Document(page_content="Transformer架构是现代大模型的基础"),
]

# Sparse检索器(BM25关键词匹配)
bm25_retriever = BM25Retriever.from_documents(docs, k=3)

# Dense检索器(向量语义检索)
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma.from_documents(docs, embeddings)
vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 3})

# 混合检索(加权融合)
hybrid_retriever = EnsembleRetriever(
    retrievers=[bm25_retriever, vector_retriever],
    weights=[0.4, 0.6]  # BM25权重40%, 向量权重60%
)

results = hybrid_retriever.invoke("什么是RAG?")

# Reranking(使用BGE-reranker或Cohere Reranker)
# pip install langchain-cohere
# from langchain_cohere import CohereRerank
# from langchain.retrievers import ContextualCompressionRetriever

# reranker = CohereRerank(model="rerank-v3.5", top_n=3)
# reranking_retriever = ContextualCompressionRetriever(
#     base_compressor=reranker,
#     base_retriever=hybrid_retriever
# )

18.4.3 查询转换

Python
"""查询转换策略"""
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from pydantic import BaseModel, Field

llm = ChatOpenAI(model="gpt-5-mini")

# --- HyDE (Hypothetical Document Embedding) ---
def hyde_retrieval(query: str, retriever):
    """
    HyDE: 先让LLM生成假设性答案文档,
    用该文档的embedding去检索(比原始query效果更好)
    """
    hyde_prompt = ChatPromptTemplate.from_template(
        "请对以下问题写一段可能的答案(不需要保证准确):\n{query}"
    )
    hypothetical_doc = (hyde_prompt | llm).invoke({"query": query}).content

    # 用假设文档的embedding进行检索
    results = retriever.invoke(hypothetical_doc)
    return results

# --- Multi-Query(多查询扩展)---
class MultiQuery(BaseModel):
    queries: list[str] = Field(description="从不同角度重写的查询列表")

def multi_query_retrieval(query: str, retriever):
    """
    Multi-Query: 将原始查询改写为多个不同角度的查询,
    合并检索结果,增加召回率
    """
    expand_prompt = ChatPromptTemplate.from_template(
        """请将以下问题从3个不同角度进行改写,以提高检索召回率:
原始问题:{query}
要求:保持语义一致,但使用不同的表述方式。"""
    )

    multi = (expand_prompt | llm.with_structured_output(MultiQuery)).invoke({"query": query})

    # 对每个查询检索并去重
    all_docs = []
    seen_contents = set()
    for q in [query] + multi.queries:
        docs = retriever.invoke(q)
        for doc in docs:
            if doc.page_content not in seen_contents:
                all_docs.append(doc)
                seen_contents.add(doc.page_content)

    return all_docs

# --- Step-Back Prompting ---
def step_back_retrieval(query: str, retriever):
    """
    Step-Back: 先退一步问更高层次的问题,
    获取背景知识后再回答原始问题
    """
    step_back_prompt = ChatPromptTemplate.from_template(
        """请生成一个更高层次、更抽象的问题,帮助回答原始问题:
原始问题:{query}
更高层次的问题:"""
    )

    abstract_query = (step_back_prompt | llm).invoke({"query": query}).content

    # 同时检索原始问题和抽象问题
    original_docs = retriever.invoke(query)
    abstract_docs = retriever.invoke(abstract_query)

    return original_docs + abstract_docs

18.4.4 上下文压缩

Python
"""上下文压缩:减少噪声,提取关键信息"""
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-5-mini")

# LLM提取压缩器:只提取与查询相关的部分
compressor = LLMChainExtractor.from_llm(llm)

# base_retriever 是任意检索器
# compressed_retriever = ContextualCompressionRetriever(
#     base_compressor=compressor,
#     base_retriever=base_retriever
# )

# LongContext Reorder(长上下文重排序)
# 研究表明LLM更关注开头和结尾的内容
from langchain_community.document_transformers import LongContextReorder

reordering = LongContextReorder()
# reordered_docs = reordering.transform_documents(docs)
# 将最相关的文档放在开头和结尾

18.5 RAG 评估框架

18.5.1 RAGAS 评估指标

Python
"""RAGAS评估框架"""
# pip install -U ragas

from ragas import evaluate
from ragas.metrics import (
    faithfulness,          # 忠实度:回答是否基于检索内容
    answer_relevancy,      # 答案相关性:回答是否切题
    context_precision,     # 上下文精度:检索内容是否精准
    context_recall,        # 上下文召回:是否检索到所有需要的信息
    answer_correctness     # 答案正确性:与标准答案的吻合度
)
from datasets import Dataset

# 准备评估数据
eval_data = {
    "question": [
        "LangChain是什么?",
        "RAG的核心流程是什么?"
    ],
    "answer": [
        "LangChain是一个用于构建LLM应用的开源框架",
        "RAG的流程包括检索相关文档和基于文档生成回答"
    ],
    "contexts": [
        ["LangChain是一个流行的开源框架,专为构建大语言模型应用而设计"],
        ["RAG(检索增强生成)的核心流程:1.检索 2.增强 3.生成"]
    ],
    "ground_truth": [
        "LangChain是用于开发LLM驱动应用的开源框架",
        "RAG包括文档检索、上下文增强和LLM生成三个步骤"
    ]
}

dataset = Dataset.from_dict(eval_data)

# 执行评估
results = evaluate(
    dataset=dataset,
    metrics=[
        faithfulness,
        answer_relevancy,
        context_precision,
        context_recall,
        answer_correctness
    ]
)

print(results)
# 输出各指标评分 (0-1)

📌 当前版本说明:截至当前稳定版官方文档,ragas.evaluate() 仍可使用,但已被标记为 deprecated;新项目建议关注 @experiment 工作流,避免后续大版本迁移成本。

RAGAS 核心指标解读

指标 含义 评估什么 理想值
Faithfulness 忠实度 回答是否有检索依据(无 hallucination ) >0.9
Answer Relevancy 答案相关性 回答是否切题 >0.85
Context Precision 上下文精度 检索内容中有用信息占比 >0.8
Context Recall 上下文召回 需要的信息是否都检索到了 >0.8
Answer Correctness 答案正确性 与标准答案的吻合度 >0.85

18.5.2 自建评估 Pipeline

Python
"""自建RAG评估Pipeline"""
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
from typing import Literal
import json

class RAGEvalResult(BaseModel):
    """RAG评估结果"""
    faithfulness_score: float = Field(description="忠实度(0-1)")
    relevance_score: float = Field(description="相关性(0-1)")
    completeness_score: float = Field(description="完整性(0-1)")
    overall_score: float = Field(description="综合评分(0-1)")
    explanation: str = Field(description="评价说明")

llm = ChatOpenAI(model="gpt-5-mini")

def evaluate_rag_response(
    question: str,
    context: str,
    answer: str,
    ground_truth: str = ""
) -> RAGEvalResult:
    """评估RAG系统的单次回答"""

    eval_prompt = f"""请评估以下RAG系统的回答质量:

## 问题
{question}

## 检索到的上下文
{context}

## 系统回答
{answer}

## 参考答案(如有)
{ground_truth or "无"}

评分标准:
- faithfulness_score: 回答是否完全基于上下文,无编造(0-1)
- relevance_score: 回答是否与问题相关(0-1)
- completeness_score: 回答是否完整覆盖了问题要求(0-1)
- overall_score: 综合评分(0-1)"""

    result = llm.with_structured_output(RAGEvalResult).invoke(eval_prompt)
    return result

# 批量评估
def batch_evaluate(test_cases: list[dict]) -> dict:
    """批量评估RAG系统"""
    results = []
    for case in test_cases:
        result = evaluate_rag_response(
            question=case["question"],
            context=case["context"],
            answer=case["answer"],
            ground_truth=case.get("ground_truth", "")
        )
        results.append(result)

    # 汇总统计
    avg_scores = {
        "avg_faithfulness": sum(r.faithfulness_score for r in results) / len(results),
        "avg_relevance": sum(r.relevance_score for r in results) / len(results),
        "avg_completeness": sum(r.completeness_score for r in results) / len(results),
        "avg_overall": sum(r.overall_score for r in results) / len(results),
    }

    return avg_scores

18.6 生产环境 RAG 优化

18.6.1 Chunk 策略

Python
"""高级分块策略"""
from langchain_text_splitters import (
    RecursiveCharacterTextSplitter,
    MarkdownHeaderTextSplitter,
)
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai import OpenAIEmbeddings

# 策略1: 语义分割(根据语义相似度动态分块)
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
semantic_splitter = SemanticChunker(
    embeddings,
    breakpoint_threshold_type="percentile",  # percentile/standard_deviation/interquartile
    breakpoint_threshold_amount=95
)
# semantic_chunks = semantic_splitter.split_documents(docs)

# 策略2: Markdown标题分割(保留结构信息)
md_splitter = MarkdownHeaderTextSplitter(
    headers_to_split_on=[
        ("#", "Header 1"),
        ("##", "Header 2"),
        ("###", "Header 3"),
    ]
)
# md_chunks = md_splitter.split_text(markdown_text)

# 策略3: 递归分割 + 元数据增强
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=512,
    chunk_overlap=50,
    separators=["\n\n", "\n", "。", "!", "?", ";", " "]
)

18.6.2 元数据过滤与缓存策略

Python
"""元数据过滤与语义缓存"""

# 元数据过滤检索
# vectorstore.as_retriever(
#     search_kwargs={
#         "k": 5,
#         "filter": {
#             "source": "technical_doc",
#             "date": {"$gte": "2026-03-25"},
#             "department": "engineering"
#         }
#     }
# )

# 语义缓存(GPTCache / 自建)
from langchain_openai import OpenAIEmbeddings
import numpy as np
from functools import lru_cache
import hashlib

class SemanticCache:
    """简易语义缓存"""

    def __init__(self, embeddings, threshold: float = 0.95):
        self.embeddings = embeddings
        self.threshold = threshold
        self.cache: dict[str, dict] = {}  # key: hash, value: {embedding, response}

    def _get_embedding(self, text: str) -> list[float]:
        return self.embeddings.embed_query(text)

    def _cosine_similarity(self, a: list[float], b: list[float]) -> float:
        a, b = np.array(a), np.array(b)
        return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))

    def get(self, query: str) -> str | None:
        """查找语义相似的缓存"""
        query_emb = self._get_embedding(query)
        for key, item in self.cache.items():
            sim = self._cosine_similarity(query_emb, item["embedding"])
            if sim >= self.threshold:
                return item["response"]
        return None

    def set(self, query: str, response: str):
        """添加缓存"""
        key = hashlib.md5(query.encode()).hexdigest()
        self.cache[key] = {
            "embedding": self._get_embedding(query),
            "response": response
        }

    def rag_with_cache(self, query: str, rag_chain) -> str:
        """带缓存的RAG调用"""
        cached = self.get(query)
        if cached:
            print("🎯 缓存命中")
            return cached

        response = rag_chain.invoke(query)
        self.set(query, response)
        return response

18.7 完整代码示例:高级 RAG 管线

Python
"""完整示例:使用LangChain构建高级RAG管线"""
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_chroma import Chroma
from langchain_community.retrievers import BM25Retriever
from langchain.retrievers import EnsembleRetriever, ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough, RunnableLambda
from langchain_core.documents import Document
from langchain_text_splitters import RecursiveCharacterTextSplitter
from pydantic import BaseModel, Field
from typing import Literal

# ============ 配置 ============
llm = ChatOpenAI(model="gpt-5-mini")
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")

# ============ 文档准备 ============
sample_docs = [
    Document(page_content="LangChain是一个开源框架,用于构建基于大语言模型的应用。它提供了链、Agent、RAG等核心组件。", metadata={"source": "langchain_intro", "category": "framework"}),
    Document(page_content="LlamaIndex专注于RAG场景,提供了Documents→Nodes→Index→QueryEngine的标准管线。", metadata={"source": "llamaindex_intro", "category": "framework"}),
    Document(page_content="向量数据库如Chroma、Pinecone、Weaviate用于存储和检索文档的嵌入向量。", metadata={"source": "vector_db", "category": "infrastructure"}),
    Document(page_content="RAG(检索增强生成)通过检索外部知识来增强LLM的回答质量,减少幻觉。", metadata={"source": "rag_intro", "category": "technique"}),
    Document(page_content="RAGAS是一个RAG评估框架,提供faithfulness、answer_relevancy、context_precision等指标。", metadata={"source": "ragas_intro", "category": "evaluation"}),
]

# ============ 构建检索器 ============
# Dense检索
vectorstore = Chroma.from_documents(sample_docs, embeddings)
dense_retriever = vectorstore.as_retriever(search_kwargs={"k": 3})

# Sparse检索(BM25)
bm25_retriever = BM25Retriever.from_documents(sample_docs, k=3)

# 混合检索
hybrid_retriever = EnsembleRetriever(
    retrievers=[bm25_retriever, dense_retriever],
    weights=[0.4, 0.6]
)

# 上下文压缩
compressor = LLMChainExtractor.from_llm(ChatOpenAI(model="gpt-5-mini"))
compressed_retriever = ContextualCompressionRetriever(
    base_compressor=compressor,
    base_retriever=hybrid_retriever
)

# ============ 查询路由 ============
class RouteDecision(BaseModel):
    route: Literal["simple", "advanced"] = Field(description="查询路由")

router_prompt = ChatPromptTemplate.from_template(
    "判断问题复杂度:简单事实问题选simple,需要综合分析选advanced。\n问题:{question}"
)
router = router_prompt | llm.with_structured_output(RouteDecision)

# ============ 生成链 ============
rag_prompt = ChatPromptTemplate.from_template("""
基于以下检索内容回答问题。如果内容不足以回答,请说明。

检索内容:
{context}

问题:{question}

要求:
1. 回答要准确,不要编造
2. 引用信息来源
3. 如果不确定,坦诚说明
""")

def format_docs(docs):
    return "\n\n".join(f"[{doc.metadata.get('source', 'unknown')}] {doc.page_content}" for doc in docs)

def route_and_retrieve(question: str):
    """路由 + 检索"""
    decision = router.invoke({"question": question})
    if decision.route == "simple":
        docs = hybrid_retriever.invoke(question)
    else:
        docs = compressed_retriever.invoke(question)
    return format_docs(docs)

# 完整RAG链
advanced_rag_chain = (
    {
        "context": RunnableLambda(lambda x: route_and_retrieve(x["question"])),  # lambda匿名函数
        "question": RunnablePassthrough() | RunnableLambda(lambda x: x["question"])
    }
    | rag_prompt
    | llm
    | StrOutputParser()
)

# ============ 执行 ============
answer = advanced_rag_chain.invoke({"question": "什么是RAG?它有什么优势?"})
print(answer)

💡 进阶补充:重排序方法全面对比

上面 18.4.2 节使用了 EnsembleRetriever + CohereRerank 做混合检索和重排序。这里对主流重排序方法做一个全面对比:

方法 原理 优点 缺点 适用场景
RRF 倒数排名融合,仅用排名信息 无需分数归一化,简单高效 无法捕捉语义细微差异 多路检索融合的默认选择
Cross-Encoder 将 (query, doc) 拼接后用 BERT 做二分类 精度最高,捕捉 token 级交互 速度慢(每对都要推理) 小规模精排(<100 候选)
ColBERT 每个 token 独立向量,Late Interaction 精度接近 Cross-Encoder,速度更快 存储开销大(每 token 一个向量) 中大规模精排
RankLLM 用 LLM 直接对文档打分/排序 灵活,可结合指令 成本高,延迟大 复杂排序需求
Cohere Rerank 托管 API,内部使用 Cross-Encoder 变体 零部署,效果好 依赖网络,有成本 快速集成

生产推荐:先用 RRF 做多路融合召回 20~50 候选,再用 Cross-Encoder 或 Cohere Rerank 精排到 top-5,兼顾效率和质量。

💡 进阶补充:RAG Triad 评估框架

除了 RAGAS 指标体系,RAG Triad(RAG 三元组) 是另一个重要的评估框架,由三个核心维度构成:

Text Only
                   ┌──────────────────┐
                   │   RAG Triad      │
                   └──────┬───────────┘
           ┌──────────────┼──────────────┐
           ▼              ▼              ▼
  ┌────────────┐  ┌────────────┐  ┌────────────┐
  │ 上下文相关性 │  │   忠实度    │  │ 答案相关性  │
  │  Context    │  │ Faithfulness│  │  Answer    │
  │ Relevance   │  │            │  │ Relevance  │
  └────────────┘  └────────────┘  └────────────┘
  检索的内容是否   回答是否基于    回答是否真正
  与问题相关?     检索内容?      回答了问题?
  • 上下文相关性(Context Relevance):检索到的文档片段是否与用户问题相关?评估检索质量。
  • 忠实度(Faithfulness):生成的回答是否严格基于检索到的上下文,没有编造信息?评估幻觉。
  • 答案相关性(Answer Relevance):生成的回答是否真正回答了用户的问题?评估生成质量。

评估工具对比

工具 特点 适用场景
RAGAS 指标全面(5+ 指标),社区活跃 学术研究、系统对比
Phoenix (Arize) 可视化 UI,实时追踪,支持 LLM Tracing 生产监控、调试
LlamaIndex Evaluation 与 LlamaIndex 深度集成,内置 Faithfulness/Relevancy LlamaIndex 项目
自建 Pipeline 完全定制,可结合业务指标 企业定制化需求

最佳实践:每次系统变更(换 Embedding 模型、调分块策略、改 Prompt)后都跑一次评估流水线,关注 Context Precision 和 Faithfulness 两个核心指标。

💡 进阶补充:结构化生成(Formatted Generation)

在 RAG 系统中,经常需要让 LLM 输出结构化数据(JSON、表格等),而非自由文本。有三种主流方法:

方法一:Output Parsers(输出解析器)

Python
from langchain_core.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field

class RAGAnswer(BaseModel):
    answer: str = Field(description="回答内容")
    confidence: float = Field(description="置信度 0-1")
    sources: list[str] = Field(description="引用来源")

parser = PydanticOutputParser(pydantic_object=RAGAnswer)
# 将 parser.get_format_instructions() 加入 Prompt,LLM 输出后用 parser.parse() 解析

方法二:Function Calling / Tool Calling

Python
# OpenAI Function Calling 方式
llm_with_tools = llm.bind_tools([{
    "type": "function",
    "function": {
        "name": "structured_answer",
        "parameters": RAGAnswer.model_json_schema()
    }
}])
response = llm_with_tools.invoke("请回答...")
# response.tool_calls[0].function.arguments 即为结构化 JSON

方法三:with_structured_output(推荐)

Python
# LangChain 封装,自动选择最佳方式(Function Calling 或 JSON Mode)
structured_llm = llm.with_structured_output(RAGAnswer)
result = structured_llm.invoke("请基于上下文回答...")
print(result.answer, result.confidence, result.sources)

选型建议:优先使用 with_structured_output,它内部自动适配不同 LLM 的结构化输出能力。对于不支持 Function Calling 的模型,退化为 Prompt + Output Parser 方式。

📋 面试要点

高频面试题

Q1: 解释 Naive RAG 、 Advanced RAG 、 Modular RAG 和 Agentic RAG 的区别?

答: Naive RAG 是简单的检索+生成管线; Advanced RAG 加入预检索优化(查询重写)和后检索优化(重排序); Modular RAG 将各环节模块化,可灵活组合; Agentic RAG 由 Agent 动态决定检索策略,支持多步推理和自我反思。演进方向是从静态管线到智能自适应。

Q2: GraphRAG 的 Global Search 和 Local Search 有什么区别?

答: Global Search 使用社区摘要进行 Map-Reduce ,适合全局性问题(如"数据集的主要主题")。 Local Search 结合向量检索和图谱邻居遍历,适合具体问题(如"某实体的属性")。 Global Search 成本更高但能回答跨文档问题。

Q3: 什么是 Self-RAG ?与传统 RAG 有何不同?

答: Self-RAG 引入"反思标记",让模型在检索前判断是否需要检索,检索后评估相关性,生成后检查依据性。与传统 RAG 的固定管线不同, Self-RAG 是自适应的,可减少不必要检索和 hallucination 。

Q4: 如何评估 RAG 系统? RAGAS 框架有哪些核心指标?

答: RAGAS 核心指标:①Faithfulness (忠实度,回答是否有依据)②Answer Relevancy (答案相关性)③Context Precision (检索精度)④Context Recall (检索召回率)。评估既需自动指标也需人工评估,生产环境建议持续监控这些指标。

Q5: 混合检索为什么优于纯向量检索?

答:向量检索擅长语义匹配但可能错过关键词精确匹配; BM25 擅长关键词匹配但不理解语义。混合检索(如 BM25 0.4 + Dense 0.6 )结合两者优势,再通过 Reranker 二次排序,显著提升检索质量。实践中 Top-5 相关性可提升 10-20%。


✏️ 练习

练习 1 : GraphRAG 实践

使用 LangChain + LLM 构建一个简单的知识图谱,实现从一段文本中提取实体和关系,并进行基于图谱的问答。

💡 参考答案
Python
# 练习1:GraphRAG 实践 — 参考实现
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
import networkx as nx
import json

# 1. 初始化 LLM
llm = ChatOpenAI(model="gpt-5-mini", temperature=0)

# 2. 实体关系抽取 Prompt
extraction_prompt = ChatPromptTemplate.from_messages([
    ("system", """你是一个知识图谱构建助手。从给定文本中抽取实体和关系。
输出 JSON 格式:
{{"triples": [{{"subject": "...", "predicate": "...", "object": "..."}}]}}
只输出 JSON,不要其他内容。"""),
    ("human", "{text}")
])

# 3. 从文本中抽取三元组
sample_text = """
DeepSeek 是一家中国 AI 公司,总部位于杭州。
DeepSeek 发布了 DeepSeek-V3 模型,该模型采用 MoE 架构。
DeepSeek-V3 的参数量为 671B,其中激活参数为 37B。
DeepSeek 还发布了 DeepSeek-R1,这是一个推理模型。
"""

chain = extraction_prompt | llm
result = chain.invoke({"text": sample_text})
triples = json.loads(result.content)["triples"]

# 4. 构建知识图谱
G = nx.DiGraph()
for t in triples:
    G.add_edge(t["subject"], t["object"], relation=t["predicate"])

print(f"节点数: {G.number_of_nodes()}, 边数: {G.number_of_edges()}")
for u, v, data in G.edges(data=True):
    print(f"  {u} --[{data['relation']}]--> {v}")

# 5. 基于图谱的问答
qa_prompt = ChatPromptTemplate.from_messages([
    ("system", """基于以下知识图谱三元组回答问题。
三元组:{triples}
如果三元组不足以回答问题,请说明。"""),
    ("human", "{question}")
])

qa_chain = qa_prompt | llm
triples_str = "\n".join([f"- {t['subject']} {t['predicate']} {t['object']}" for t in triples])

answer = qa_chain.invoke({
    "triples": triples_str,
    "question": "DeepSeek-V3 的架构和参数量是什么?"
})
print(f"\n回答: {answer.content}")
**关键要点**: - 实体关系抽取是 GraphRAG 的核心步骤,需要精心设计 Prompt 确保输出格式一致 - 使用 `networkx.DiGraph` 适合原型验证,生产环境建议使用 Neo4j - 图谱问答可通过子图检索 + LLM 生成实现,也可直接将三元组注入 Prompt

练习 2 : Self-RAG 实现

实现 Self-RAG 的完整流程,包括:检索必要性判断→文档检索→相关性评估→生成→依据性检查。对比 Self-RAG 与 Naive RAG 在事实性问题上的表现。

💡 参考答案
Python
# 练习2:Self-RAG 实现参考
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from typing import Optional

llm = ChatOpenAI(model="gpt-5-mini", temperature=0)

# --- Step 1: 检索必要性判断 ---
retrieve_judge_prompt = ChatPromptTemplate.from_messages([
    ("system", "判断以下问题是否需要检索外部知识来回答。只回答 YES 或 NO。"),
    ("human", "{question}")
])

def should_retrieve(question: str) -> bool:
    chain = retrieve_judge_prompt | llm
    result = chain.invoke({"question": question})
    return "YES" in result.content.upper()

# --- Step 2: 文档检索(模拟) ---
mock_docs = {
    "DeepSeek": "DeepSeek-V3 是 MoE 架构,671B 参数,37B 激活参数。",
    "RAG": "RAG 通过检索增强生成,减少幻觉。",
    "LoRA": "LoRA 是低秩适配微调方法,仅训练少量参数。"
}

def retrieve(question: str, top_k: int = 2) -> list[str]:
    """简单关键词检索(生产环境用向量检索)"""
    return [v for k, v in mock_docs.items() if k.lower() in question.lower()][:top_k]

# --- Step 3: 相关性评估 ---
relevance_prompt = ChatPromptTemplate.from_messages([
    ("system", "评估检索到的文档是否与问题相关。只回答 RELEVANT 或 IRRELEVANT。"),
    ("human", "问题: {question}\n文档: {doc}")
])

def assess_relevance(question: str, doc: str) -> bool:
    chain = relevance_prompt | llm
    result = chain.invoke({"question": question, "doc": doc})
    return "RELEVANT" in result.content.upper()

# --- Step 4: 生成 ---
generate_prompt = ChatPromptTemplate.from_messages([
    ("system", "基于以下文档回答问题。如果文档不足以回答,请说明。\n文档: {docs}"),
    ("human", "{question}")
])

# --- Step 5: 依据性检查 ---
faithfulness_prompt = ChatPromptTemplate.from_messages([
    ("system", "检查回答是否完全基于给定文档。只回答 FAITHFUL 或 UNFAITHFUL。"),
    ("human", "文档: {docs}\n回答: {answer}")
])

def check_faithfulness(docs: str, answer: str) -> bool:
    chain = faithfulness_prompt | llm
    result = chain.invoke({"docs": docs, "answer": answer})
    return "FAITHFUL" in result.content.upper()

# --- Self-RAG 完整流程 ---
def self_rag(question: str) -> dict:
    # Step 1: 检索必要性判断
    need_retrieve = should_retrieve(question)

    docs = []
    if need_retrieve:
        # Step 2: 检索
        docs = retrieve(question)
        # Step 3: 相关性评估
        docs = [d for d in docs if assess_relevance(question, d)]

    # Step 4: 生成
    docs_str = "\n".join(docs) if docs else "无检索文档"
    gen_chain = generate_prompt | llm
    answer = gen_chain.invoke({"docs": docs_str, "question": question})

    # Step 5: 依据性检查
    is_faithful = check_faithfulness(docs_str, answer.content) if docs else None

    return {
        "question": question,
        "retrieved": need_retrieve,
        "docs": docs,
        "answer": answer.content,
        "faithful": is_faithful
    }

# 对比测试
questions = [
    "DeepSeek-V3 的参数量是多少?",   # 需要检索
    "1+1等于几?",                     # 不需要检索
]

for q in questions:
    result = self_rag(q)
    print(f"Q: {result['question']}")
    print(f"  需要检索: {result['retrieved']}")
    print(f"  回答: {result['answer'][:80]}...")
    print(f"  依据性: {result['faithful']}\n")
**关键要点**: - Self-RAG 的核心是"反思"机制:在检索前、检索后、生成后各设置一个判断点 - 与 Naive RAG 相比,Self-RAG 可跳过不必要检索(如常识问题),减少延迟和成本 - 依据性检查可过滤掉无根据的生成内容,提升可靠性

练习 3 :混合检索系统

使用 BM25 + 向量检索 + Cohere Reranker 构建混合检索系统,对比不同权重配置下的检索效果。

💡 参考答案
Python
# 练习3:混合检索系统参考实现
from rank_bm25 import BM25Okapi
from openai import OpenAI
import numpy as np

client = OpenAI()

# 1. 准备文档
documents = [
    "DeepSeek-V3 采用 MoE(混合专家)架构,总参数 671B,激活参数 37B。",
    "LoRA 通过低秩矩阵分解实现高效微调,仅训练 0.1% 的参数。",
    "RAG 系统通过检索外部知识增强生成,减少大模型幻觉。",
    "KV Cache 通过缓存注意力键值对加速推理,减少重复计算。",
    "GraphRAG 利用知识图谱进行多跳推理,适合复杂关联查询。",
    "量化技术(GPTQ/AWQ)将模型权重从 FP16 压缩到 INT4,降低显存需求。",
    "RLHF 通过人类反馈的强化学习对齐模型输出与人类偏好。",
    "向量数据库使用 HNSW 索引实现近似最近邻搜索,查询延迟在毫秒级。",
]

# 2. BM25 检索
tokenized_docs = [doc.split() for doc in documents]
bm25 = BM25Okapi(tokenized_docs)

def bm25_search(query: str, top_k: int = 5) -> list[tuple[int, float]]:
    scores = bm25.get_scores(query.split())
    top_indices = np.argsort(scores)[::-1][:top_k]
    return [(i, float(scores[i])) for i in top_indices]

# 3. 向量检索
def get_embeddings(texts: list[str]) -> list[list[float]]:
    resp = client.embeddings.create(
        model="text-embedding-3-small",
        input=texts
    )
    return [d.embedding for d in resp.data]

doc_embeddings = get_embeddings(documents)

def vector_search(query: str, top_k: int = 5) -> list[tuple[int, float]]:
    query_emb = get_embeddings([query])[0]
    scores = [float(np.dot(query_emb, d) / (np.linalg.norm(query_emb) * np.linalg.norm(d)))
              for d in doc_embeddings]
    top_indices = np.argsort(scores)[::-1][:top_k]
    return [(i, scores[i]) for i in top_indices]

# 4. 混合检索 — RRF (Reciprocal Rank Fusion)
def hybrid_search(query: str, bm25_weight: float = 0.4,
                  vector_weight: float = 0.6, top_k: int = 5) -> list[tuple[int, float]]:
    bm25_results = bm25_search(query, top_k=len(documents))
    vector_results = vector_search(query, top_k=len(documents))

    # RRF 融合
    rrf_scores = {}
    k = 60  # RRF 常数
    for rank, (idx, _) in enumerate(bm25_results):
        rrf_scores[idx] = rrf_scores.get(idx, 0) + bm25_weight / (k + rank + 1)
    for rank, (idx, _) in enumerate(vector_results):
        rrf_scores[idx] = rrf_scores.get(idx, 0) + vector_weight / (k + rank + 1)

    sorted_results = sorted(rrf_scores.items(), key=lambda x: x[1], reverse=True)[:top_k]
    return sorted_results

# 5. 对比不同权重
query = "如何减少大模型的显存使用?"
print(f"查询: {query}\n")

for bw, vw in [(1.0, 0.0), (0.0, 1.0), (0.4, 0.6), (0.3, 0.7)]:
    results = hybrid_search(query, bm25_weight=bw, vector_weight=vw, top_k=3)
    print(f"权重 BM25={bw:.1f} / Vector={vw:.1f}:")
    for idx, score in results:
        print(f"  [{score:.4f}] {documents[idx][:60]}...")
    print()
**关键要点**: - RRF(Reciprocal Rank Fusion)是混合检索的常用融合策略,无需归一化分数 - 实践中 BM25:Vector 权重比 0.3-0.4:0.6-0.7 通常效果最佳 - 可进一步加入 Reranker(如 Cohere Rerank 或 BGE-Reranker)做二次精排

练习 4 : RAG 评估

使用 RAGAS 框架对你构建的 RAG 系统进行评估,分析 Faithfulness 和 Relevancy 指标,找出系统瓶颈并提出优化方案。

💡 参考答案
Python
# 练习4:RAG 评估参考实现
# pip install ragas

from ragas import evaluate
from ragas.metrics import (
    faithfulness,
    answer_relevancy,
    context_precision,
    context_recall,
)
from ragas.dataset_schema import SingleTurnSample, EvaluationDataset
from langchain_openai import ChatOpenAI, OpenAIEmbeddings

# 1. 准备评估数据集
# 每条数据需要:question, answer, contexts(检索文档), ground_truth(参考答案)
test_data = [
    {
        "question": "DeepSeek-V3 的架构是什么?",
        "answer": "DeepSeek-V3 采用 MoE(混合专家)架构,总参数 671B,激活参数 37B。",
        "contexts": ["DeepSeek-V3 是 MoE 架构,671B 参数,37B 激活参数。"],
        "ground_truth": "DeepSeek-V3 采用 MoE 架构,671B 总参数,37B 激活参数。"
    },
    {
        "question": "LoRA 的核心原理是什么?",
        "answer": "LoRA 通过低秩矩阵分解实现参数高效微调。",
        "contexts": ["LoRA 通过低秩矩阵分解实现高效微调,仅训练 0.1% 的参数。"],
        "ground_truth": "LoRA 将权重更新分解为两个低秩矩阵的乘积,仅训练少量参数。"
    },
    {
        "question": "如何减少大模型推理延迟?",
        "answer": "可以使用 KV Cache 缓存注意力键值对来减少重复计算。",
        "contexts": ["KV Cache 通过缓存注意力键值对加速推理。量化可降低显存。"],
        "ground_truth": "可通过 KV Cache、量化(GPTQ/AWQ)、投机解码、Flash Attention 等技术减少延迟。"
    },
]

# 2. 构建 EvaluationDataset
samples = []
for d in test_data:
    samples.append(SingleTurnSample(
        user_input=d["question"],
        response=d["answer"],
        retrieved_contexts=d["contexts"],
        reference=d["ground_truth"],
    ))

dataset = EvaluationDataset(samples=samples)

# 3. 运行评估
llm = ChatOpenAI(model="gpt-5-mini")
embeddings = OpenAIEmbeddings()

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

print("=== RAGAS 评估结果 ===")
print(results)

# 4. 分析瓶颈并输出优化建议
print("\n=== 瓶颈分析与优化建议 ===")
scores = results.to_pandas()
for metric in ["faithfulness", "answer_relevancy", "context_precision", "context_recall"]:
    if metric in scores.columns:
        avg = scores[metric].mean()
        status = "✅ 良好" if avg >= 0.8 else "⚠️ 需优化" if avg >= 0.6 else "❌ 较差"
        print(f"  {metric}: {avg:.3f} {status}")

print("""
常见瓶颈及优化方案:
1. Faithfulness 低 → 改进 chunk 质量,增加上下文覆盖
2. Answer Relevancy 低 → 优化 Prompt,增加指令约束
3. Context Precision 低 → 改进检索策略(混合检索/Reranker)
4. Context Recall 低 → 增加召回数量,优化 chunk 策略(重叠/父子)
""")
**关键要点**: - RAGAS 四大核心指标对应 RAG 管线的不同环节:检索(Precision/Recall)和生成(Faithfulness/Relevancy) - 评估数据集的质量直接影响评估结果的可靠性,建议准备 20-50 条标注数据 - 根据指标短板定向优化:检索环节问题用混合检索/Reranker,生成环节问题用 Prompt 优化

📚 参考资料 - GraphRAG 论文: "From Local to Global: A Graph RAG Approach to Query-Focused Summarization" (2024) - Self-RAG 论文: "Self-RAG: Learning to Retrieve, Generate, and Critique through Self-Reflection" (2024) - CRAG 论文: "Corrective Retrieval Augmented Generation" (2024) - RAGAS 文档 - LangChain RAG 教程


最后更新日期: 2026-03-26 适用版本: LLM 应用指南 v2026