LangChain 实现AdvancedRAG增强向量检索生成

Advanced‑RAG 是基于 Naive RAG 演化而来的高级检索增强生成方案,针对朴素RAG检索不准、上下文相关性差、召回噪声多等缺陷进行多层优化。该方案完全沿用朴素RAG的基础流水线模式、不改动核心架构,仅在各流程节点嵌入预处理、后处理增强模块,精细化优化检索环节。通过引入查询改写、重排序、上下文压缩、自我校验等多种增强策略,对召回的文档片段过滤去噪、重排优先级,精准筛选高价值上下文送入大模型,以此来提升提升知识库问答的精准度与稳定性,是目前工业级RAG落地的主流优化方案。

该模式摒弃单一检索逻辑,整合多维度增强策略,核心包含查询改写、多路检索、上下文压缩、重排序精筛、结果自我校验等能力,完整闭环执行链路如下:

  • 流程:用户提出原始问题 → Multi-Query多查询生成/MMR多样性检索 → 向量库多路召回 → 结果合并去重 → LLM上下文压缩粗过滤 → Reranker精准重排精筛 → 高价值上下文输入LLM → 生成精准答案

在所有增强模块中,Multi-Query多查询生成 + Reranker重排序是核心黄金组合:前者负责拓宽检索覆盖面、解决漏召问题,后者负责降噪提纯、解决召回不准、噪声冗余问题,二者互补彻底解决Naive-RAG的核心痛点。

环境依赖安装

本项目基于LangChain生态、Chroma向量库、ModelScope模型社区开发,依赖文档解析、文本分割、向量检索、重排序模型等核心库。为规避官方源网速慢、安装失败问题,统一使用清华PyPI镜像源批量安装所有依赖,保障环境搭建稳定高效。

bash 复制代码
CMD> pip install -i https://pypi.tuna.tsinghua.edu.cn/simple langchain-classic sentence-transformers modelscope langchain_chroma
CMD> 
CMD> pip list
Package                  Version
------------------------ -----------
langchain                1.3.15
langchain-classic        1.0.8
langchain-chroma         1.1.0
sentence-transformers    6.0.0
modelscope               1.39.1
modelscope-hub           0.2.0

Multi-Query 轻量化召回实现

Multi-Query 通过利用大模型的语义理解与改写能力,将用户单一的原始问题,自动生成多个表达方式不同、切入角度各异、语义等价的全新检索Query。生成的多条查询会分别送入向量数据库执行检索,最终将所有检索结果汇总、去重,形成体量更大、覆盖范围更广的候选文档集合。其目的是扩大召回池,把更多潜在的相关文档全部捞出来,宁可错捞不要漏。

本节将手动搭建轻量化Multi-Query多查询检索逻辑,不依赖框架封装接口,从零实现「查询生成-多路检索-结果去重」完整核心流程。代码基于本地私有化大模型生成多角度检索问句,搭配自定义模拟检索器完成测试,无需提前部署向量库,可直接运行验证Multi-Query的扩召回效果,同时能直观观测轻量模型查询改写的优缺点,为后续进阶优化打下基础。

python 复制代码
import os
from typing import List
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.documents import Document
from langchain_core.retrievers import BaseRetriever
from pydantic import Field

# 本地OpenAI兼容大模型配置
llm = ChatOpenAI(
    model="qwen2.5-1.5b-instruct-q4_k_m.gguf",
    base_url="http://127.0.0.1:11433/v1",
    api_key="dummy",
    temperature=0.3,
    max_tokens=800,
)

# 手写 Multi‑Query 多查询生成
multi_query_prompt = ChatPromptTemplate.from_messages([
    ("system", "针对用户问题生成3个不同角度检索查询,每行输出1条,不要多余文字,不要序号。"),
    ("human", "原始问题:{question}"),
])

generate_queries_chain = multi_query_prompt | llm | StrOutputParser()

def multi_query_retrieve(question: str, base_retriever: BaseRetriever) -> List[Document]:
    """多查询召回:生成多条query 多路检索+文本去重"""
    out = generate_queries_chain.invoke({"question": question})
    print("\n[大模型原始输出内容]:")
    print(out)
    
    query_list = [q.strip() for q in out.splitlines() if q.strip()]
    query_list.append(question)
    print(f"\n[Multi‑Query生成查询] → {query_list}")

    all_docs: List[Document] = []
    seen = set()
    for q in query_list:
        docs = base_retriever.invoke(q)
        for d in docs:
            key = d.page_content
            if key not in seen:
                seen.add(key)
                all_docs.append(d)
    
    print(f"[多路召回完成] 候选文档总数:{len(all_docs)}")
    return all_docs

# 自定义Mock检索器(无向量库依赖,纯本地模拟)
class MockRetriever(BaseRetriever):
    mock_docs: List[Document] = Field(default_factory=list)

    def _get_relevant_documents(self, query: str, *, run_manager=None):
        print(f"[MockRetriever] 接收到检索query:{query}")
        return self.mock_docs

if __name__ == "__main__":

    question = "Advanced‑RAG对比Naive‑RAG做了哪些增强?"
    print(f"原始用户问题:{question}")

    test_docs = MockRetriever(mock_docs=[
        Document(
            page_content="llama.cpp是高性能GGUF格式本地大模型推理框架,支持CPU/GPU混合加速,能够对外提供兼容OpenAI接口的本地API服务,本程序使用该服务提供LLM能力。",
            metadata={"source":"local_env.md"}
        ),
        Document(
            page_content="Embedding向量不能直接使用对话型大模型,必须使用专门的嵌入模型;本示例使用Qwen3‑Embedding作为向量模型,用于Chroma向量库的文档向量化检索。",
            metadata={"source":"embedding_note.md"}
        ),
        Document(
            page_content="Naive‑RAG即朴素RAG,仅做基础向量相似度检索,没有多查询扩展、没有上下文过滤压缩、没有重排序模块,检索角度单一,复杂问题召回效果有限。",
            metadata={"source":"rag_compare.md"}
        ),
        Document(
            page_content="Advanced‑RAG在Naive‑RAG朴素RAG基础上做能力增强,典型优化手段包含MultiQuery多查询生成、MMR多样性检索、LLM上下文压缩过滤、Cross‑Encoder重排序。本程序完整实现以上增强链路。",
            metadata={"source":"rag_compare.md"}
        ),
        Document(
            page_content="Modular‑RAG将RAG流程拆成可插拔组件,检索器、压缩器、重排器都可以自由替换,但整体执行流程是固定的,本示例的链路就是模块化RAG的实践。",
            metadata={"source":"rag_intro.md"}
        ),
        Document(
            page_content="Agentic‑RAG依靠大模型自主规划决策,动态判断是否需要多次检索,适合多跳、复杂推理类问题,和本示例Advanced‑RAG固定检索链路有明显区别。",
            metadata={"source":"rag_intro.md"}
        ),
    ])

    # 执行多查询检索
    retrieved_docs = multi_query_retrieve(question, test_docs)
    
    # 打印最终召回的完整文档内容
    print("\n多路检索最终召回文档内容:")
    for idx, doc in enumerate(retrieved_docs, 1):
        print(f"\n[文档{idx}|来源:{doc.metadata['source']}]")
        print(doc.page_content)

通过真实运行输出,可以直观看到轻量本地模型的查询改写效果、多维度扩召回的实际作用,同时清晰暴露小模型改写幻觉、无效召回、引入噪声等核心问题,为后续优化方案提供直观依据。

bash 复制代码
CMD> python main.py
CMD> 
原始用户问题:Advanced‑RAG对比Naive‑RAG做了哪些增强?

[大模型原始输出内容]:
1. Advanced-RAG在训练时采用了更复杂的模型架构,如BERT或GPT,以提高性能。
2. Advanced-RAG在检索时使用了更先进的搜索算法,如深度搜索或卷积神经网络,以提高搜索效率。
3. Advanced-RAG在评估时采用了更全面的指标,如F1分数或准确率,以全面评估模型性能。

[Multi‑Query生成查询]
[
  '1. Advanced-RAG在训练时采用了更复杂的模型架构,如BERT或GPT,以提高性能。',
  '2. Advanced-RAG在检索时使用了更先进的搜索算法,如深度搜索或卷积神经网络,以提高搜索效率。',
  '3. Advanced-RAG在评估时采用了更全面的指标,如F1分数或准确率,以全面评估模型性能。',
  'Advanced‑RAG对比Naive‑RAG做了哪些增强?'
]

[MockRetriever] 接收到检索query:1. Advanced-RAG在训练时采用了更复杂的模型架构,如BERT或GPT,以提高性能。
[MockRetriever] 接收到检索query:2. Advanced-RAG在检索时使用了更先进的搜索算法,如深度搜索或卷积神经网络,以提高搜索效率。
[MockRetriever] 接收到检索query:3. Advanced-RAG在评估时采用了更全面的指标,如F1分数或准确率,以全面评估模型性能。
[MockRetriever] 接收到检索query:Advanced‑RAG对比Naive‑RAG做了哪些增强?

[多路召回完成] 候选文档总数:6

多路检索最终召回文档内容:

[文档1|来源:local_env.md]
llama.cpp是高性能GGUF格式本地大模型推理框架,支持CPU/GPU混合加速,能够对外提供兼容OpenAI接口的本地API服务,本程序使用该服务提供LLM能力。
[文档2|来源:embedding_note.md]
Embedding向量不能直接使用对话型大模型,必须使用专门的嵌入模型;本示例使用Qwen3‑Embedding作为向量模型,用于Chroma向量库的文档向量化检索。
[文档3|来源:rag_compare.md]
Naive‑RAG即朴素RAG,仅做基础向量相似度检索,没有多查询扩展、没有上下文过滤压缩、没有重排序模块,检索角度单一,复杂问题召回效果有限。
[文档4|来源:rag_compare.md]
Advanced‑RAG在Naive‑RAG朴素RAG基础上做能力增强,典型优化手段包含MultiQuery多查询生成、MMR多样性检索、LLM上下文压缩过滤、Cross‑Encoder重排序。本程序完整实现以上增强链路。
[文档5|来源:rag_intro.md]
Modular‑RAG将RAG流程拆成可插拔组件,检索器、压缩器、重排器都可以自由替换,但整体执行流程是固定的,本示例的链路就是模块化RAG的实践。
[文档6|来源:rag_intro.md]
Agentic‑RAG依靠大模型自主规划决策,动态判断是否需要多次检索,适合多跳、复杂推理类问题,和本示例Advanced‑RAG固定检索链路有明显区别。

本次测试使用的轻量本地小模型存在查询改写幻觉问题,生成的3条扩展Query与Advanced-RAG核心增强逻辑无关,属于无效改写。但多路召回机制依然生效,叠加原始问题完成4轮检索。

同时可以发现:单纯依赖Multi-Query扩召回,会召回大量无关文档,引入大量噪声。这也证明了仅扩召回无法解决精准度问题,必须搭配上下文压缩、Reranker重排序做精筛,形成完整闭环。

Multi-Query+MMR 上下文压缩进阶优化

基于LangChain官方组件与Chroma向量库,整合MMR多样性检索、Multi-Query多路扩召回、LLM上下文压缩三大能力。MMR解决检索结果重复冗余问题、提升内容多样性;Multi-Query解决漏召问题;LLM压缩实现初步降噪,形成初级增强检索链路。

python 复制代码
import os
from typing import List
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.retrievers import BaseRetriever
from langchain_core.documents import Document
from langchain_classic.retrievers.multi_query import MultiQueryRetriever
from langchain_classic.retrievers import ContextualCompressionRetriever
from langchain_classic.retrievers.document_compressors import LLMChainFilter

llm = ChatOpenAI(
    model="qwen2.5-1.5b-instruct-q4_k_m.gguf",
    base_url="http://127.0.0.1:11433/v1",
    api_key="dummy",
    temperature=0.3,
    max_tokens=800
)

embeddings = OpenAIEmbeddings(
    model="qwen3-embedding-local.gguf",
    base_url="http://127.0.0.1:11434/v1",
    api_key="dummy"
)

# 构建:MMR + MultiQuery + 上下文压缩 检索器
def build_compress_multiquery_retriever(db: Chroma) -> BaseRetriever:
    # MMR基础检索
    base_retriever = db.as_retriever(
        search_type="mmr",
        search_kwargs={"k":4, "fetch_k":10, "lambda_mult":0.3}
    )

    # Multi‑Query多查询生成
    multi_prompt = ChatPromptTemplate.from_messages([
        ("system","针对用户问题生成3个不同角度检索查询,每行一条,不要多余输出。"),
        ("human","原始问题:{question}")
    ])
    multi_retriever = MultiQueryRetriever.from_llm(
        retriever=base_retriever,
        llm=llm,
        prompt=multi_prompt
    )

    # 上下文压缩:LLM过滤无关片段
    compressor = LLMChainFilter.from_llm(llm)
    compress_retriever = ContextualCompressionRetriever(
        base_retriever=multi_retriever,
        base_compressor=compressor
    )
    return compress_retriever

if __name__ == "__main__":
    mock_docs = [
        Document(
            page_content="llama.cpp是高性能GGUF格式本地大模型推理框架,支持CPU/GPU混合加速,能够对外提供兼容OpenAI接口的本地API服务,本程序使用该服务提供LLM能力。",
            metadata={"source":"local_env.md"}
        ),
        Document(
            page_content="Embedding向量不能直接使用对话型大模型,必须使用专门的嵌入模型;本示例使用Qwen3‑Embedding作为向量模型,用于Chroma向量库的文档向量化检索。",
            metadata={"source":"embedding_note.md"}
        ),
        Document(
            page_content="Naive‑RAG即朴素RAG,仅做基础向量相似度检索,没有多查询扩展、没有上下文过滤压缩、没有重排序模块,检索角度单一,复杂问题召回效果有限。",
            metadata={"source":"rag_compare.md"}
        ),
        Document(
            page_content="Advanced‑RAG在Naive‑RAG朴素RAG基础上做能力增强,典型优化手段包含MultiQuery多查询生成、MMR多样性检索、LLM上下文压缩过滤、Cross‑Encoder重排序。本程序完整实现以上增强链路。",
            metadata={"source":"rag_compare.md"}
        ),
        Document(
            page_content="Modular‑RAG将RAG流程拆成可插拔组件,检索器、压缩器、重排器都可以自由替换,但整体执行流程是固定的,本示例的链路就是模块化RAG的实践。",
            metadata={"source":"rag_intro.md"}
        ),
        Document(
            page_content="Agentic‑RAG依靠大模型自主规划决策,动态判断是否需要多次检索,适合多跳、复杂推理类问题,和本示例Advanced‑RAG固定检索链路有明显区别。",
            metadata={"source":"rag_intro.md"}
        ),
    ]

    text_splitter = RecursiveCharacterTextSplitter(chunk_size=300, chunk_overlap=50)
    split_docs = text_splitter.split_documents(mock_docs)

    # 内存向量库 无磁盘持久化
    vector_db = Chroma.from_documents(
        documents=split_docs,
        embedding=embeddings,
        persist_directory=None
    )

    retriever = build_compress_multiquery_retriever(vector_db)

    question = "[Advanced‑RAG相比Naive‑RAG做了哪些增强手段?]"
    print(f"\n用户提问:{question}")

    # 链路:MMR → MultiQuery多查询检索 → LLM上下文压缩过滤
    final_docs = retriever.invoke(question)

    print("\n[经过MMR+MultiQuery+上下文压缩后的文档结果]")
    if not final_docs:
        print("[过滤后没有保留任何文档]")
    else:
        for idx, doc in enumerate(final_docs, start=1):
            print(f"\n[文档{idx}]来源:{doc.metadata.get('source', '未知')}")
            print(f"[内容:{doc.page_content}]")

通过实际运行日志,可直观验证该基础增强链路的优化效果,同时精准复现「LLM单独压缩导致核心有效文档误杀、关键信息缺失」的工业级常见缺陷,佐证后续叠加Reranker重排模块的必要性。

bash 复制代码
CMD> python main.py
CMD> 
用户提问:[Advanced‑RAG相比Naive‑RAG做了哪些增强手段?]

[经过MMR+MultiQuery+上下文压缩后的文档结果]

[文档1]来源:rag_compare.md
[内容:Naive‑RAG即朴素RAG,仅做基础向量相似度检索,没有多查询扩展、没有上下文过滤压缩、没有重排序模块,检索角度单一,复杂问题召回效果有限。]

[文档2]来源:rag_intro.md
[内容:Agentic‑RAG依靠大模型自主规划决策,动态判断是否需要多次检索,适合多跳、复杂推理类问题,和本示例Advanced‑RAG固定检索链路有明显区别。]

Reranker 重排序精筛模块

Reranker(重排序)是Advanced-RAG的核心精筛模块,作用于多路粗召回之后、答案生成之前。不同于向量相似度的浅层匹配,Reranker基于Cross-Encoder交叉编码器,对「用户问题-候选文档」做深度语义匹配,输出0~1区间的相关性分数,按分数降序筛选Top-N高价值文档,可以很好的过滤噪声、修正LLM压缩失真问题。

本方案采用BAAI/bge-reranker-v2-m3作为筛选引擎,该模型由北京智源人工智能研究院开源发布,属于Encoder‑only判别式Cross‑Encoder交叉编码器重排小模型,其大小为2.29GB,主要面向 RAG 检索排序场景并对中文支持友好,模型易用、适合生产落地。

通过Modelscope魔搭社区一键下载模型至本地,无需联网推理,支持私有化离线部署:

python 复制代码
from modelscope import snapshot_download

repo_id = "BAAI/bge-reranker-v2-m3"
local_dir = r"./models/bge-reranker-v2-m3"

model_dir = snapshot_download(
    repo_id,
    cache_dir="./models",
)
print(f"[*] 下载完毕,路径:{model_dir}")

上述代码运行结束后将会下载到bge-reranker-v2-m3重排序模型,接着使用如下代码独立实现Reranker重排核心逻辑,脱离完整RAG链路单独测试语义打分能力。

代码加载本地BGE重排模型,手动构造「用户问题-候选文档」匹配对,对粗召回的多条候选文档做精细化语义相关性打分,自动按分数降序排序,并划分相关性等级。

python 复制代码
from sentence_transformers import CrossEncoder

model = CrossEncoder("./models/models/BAAI--bge-reranker-v2-m3/snapshots/master")
query = "Advanced‑RAG相比Naive‑RAG做了哪些增强?"

docs = [
    "llama.cpp是高性能GGUF格式本地大模型推理框架,支持CPU/GPU混合加速,能够对外提供兼容OpenAI接口的本地API服务,本程序使用该服务提供LLM能力。",
    "Naive‑RAG即朴素RAG,仅做基础向量相似度检索,没有多查询扩展、没有上下文过滤压缩、没有重排序模块,检索角度单一,复杂问题召回效果有限。",
    "Advanced‑RAG在Naive‑RAG朴素RAG基础上做能力增强,典型优化手段包含MultiQuery多查询生成、MMR多样性检索、LLM上下文压缩过滤、Cross‑Encoder重排序。本程序完整实现以上增强链路。",
    "Modular‑RAG将RAG流程拆成可插拔组件,检索器、压缩器、重排器都可以自由替换,但整体执行流程是固定的,本示例的链路就是模块化RAG的实践。",
    "Agentic‑RAG依靠大模型自主规划决策,动态判断是否需要多次检索,适合多跳、复杂推理类问题,和本示例Advanced‑RAG固定检索链路有明显区别。"
]

pairs = [[query, d] for d in docs]
scores = model.predict(pairs)

# 绑定文档和分数,按分数降序排序
scored_docs = list(zip(docs, scores))
scored_docs.sort(key=lambda x: x[1], reverse=True)

print("[重排结果(分数从高到低)]")
for idx, (doc, score) in enumerate(scored_docs, start=1):
    if score >= 0.7:
        level = "高"
    elif score >= 0.2:
        level = "中"
    else:
        level = "低"

    print(f"#{idx} 分数={score:.4f} | 相关性:{level} | {doc}")

通过量化分数可以清晰区分高相关核心文档、弱相关干扰文档、完全无关噪声文档,直观体现Cross-Encoder重排模型的高精度语义匹配能力,完美解决前文LLM过滤误杀、降噪不彻底的问题。

bash 复制代码
CMD> python main.py
CMD> 
Loading weights: 100%|██████████| 393/393 [00:00<00:00, 4882.69it/s]

[重排结果(分数从高到低)]
#1 分数=0.9990 | 相关性:高 | Advanced‑RAG在Naive‑RAG朴素RAG基础上做能力增强,典型优化手段包含MultiQuery多查询生成、MMR多样性检索、LLM上下文压缩过滤、Cross‑Encoder重排序。本程序完整实现以上增强链路。
#2 分数=0.9535 | 相关性:高 | Naive‑RAG即朴素RAG,仅做基础向量相似度检索,没有多查询扩展、没有上下文过滤压缩、没有重排序模块,检索角度单一,复杂问题召回效果有限。
#3 分数=0.2778 | 相关性:中 | Agentic‑RAG依靠大模型自主规划决策,动态判断是否需要多次检索,适合多跳、复杂推理类问题,和本示例Advanced‑RAG固定检索链路有明显区别。
#4 分数=0.0069 | 相关性:低 | Modular‑RAG将RAG流程拆成可插拔组件,检索器、压缩器、重排器都可以自由替换,但整体执行流程是固定的,本示例的链路就是模块化RAG的实践。
#5 分数=0.0000 | 相关性:低 | llama.cpp是高性能GGUF格式本地大模型推理框架,支持CPU/GPU混合加速,能够对外提供兼容OpenAI接口的本地API服务,本程序使用该服务提供LLM能力。

全链路Advanced-RAG整合落地

本节为工业级可落地的全链路Advanced-RAG整合代码,集成前文所有优化模块,增加MMR多样性检索、Multi-Query多查询扩召回、LLM上下文粗压缩、BGE-Reranker精准重排。完整适配私有化本地部署,链路闭环、可直接用于项目,同时解决了前文单一模块存在的漏召、误杀、噪声过多等所有问题。

python 复制代码
import os
import uuid
from typing import List
from pydantic import Field
from sentence_transformers import CrossEncoder
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.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
from langchain_core.documents import Document
from langchain_classic.retrievers.multi_query import MultiQueryRetriever
from langchain_classic.retrievers import ContextualCompressionRetriever
from langchain_classic.retrievers.document_compressors import LLMChainFilter
from langchain_core.retrievers import BaseRetriever
from langchain_core.documents import Document

CHUNK_SIZE = 300
CHUNK_OVERLAP = 50
CHROMA_PERSIST_DIR = "./advanced_rag_chroma"
RETRIEVE_TOP_K = 4
FETCH_K = 10
LAMBDA_MULT = 0.3

llm = ChatOpenAI(
    model="qwen2.5-1.5b-instruct-q4_k_m.gguf",
    base_url="http://127.0.0.1:11433/v1",
    api_key="dummy",
    temperature=0.3,
    max_tokens=800,
)

embeddings = OpenAIEmbeddings(
    model="qwen3-embedding-local.gguf",
    base_url="http://127.0.0.1:11434/v1",
    api_key="dummy"
)

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=CHUNK_SIZE,
    chunk_overlap=CHUNK_OVERLAP,
    separators=["\n\n", "\n", "。", ",", " "]
)

def add_documents_safe(db, docs):
    ids = [str(uuid.uuid4()) for _ in docs]
    db.add_documents(docs, ids=ids)
    print(f"[+] 本次追加 {len(docs)} 个文本块,当前总数量:{db._collection.count()}")

def get_vector_store(documents: List[Document], incremental: bool = True) -> Chroma:
    if not incremental:
        if os.path.exists(CHROMA_PERSIST_DIR):
            import shutil
            shutil.rmtree(CHROMA_PERSIST_DIR)
            print("[*] 删除旧向量库,覆盖重建模式")

    split_docs = text_splitter.split_documents(documents)
    if os.path.exists(CHROMA_PERSIST_DIR):
        print("[+] 向量库已存在,增量追加")
        db = Chroma(persist_directory=CHROMA_PERSIST_DIR, embedding_function=embeddings)
        add_documents_safe(db, split_docs)
    else:
        print("[*] 新建向量库")
        db = Chroma.from_documents(
            documents=split_docs,
            embedding=embeddings,
            persist_directory=CHROMA_PERSIST_DIR
        )
        print(f"[+] 存入 {len(split_docs)} 个文本块")
    return db

def format_docs(docs: List[Document]) -> str:
    return "\n---\n".join(
        f"[来源:{doc.metadata.get('source','未知')}|页码:{doc.metadata.get('page','-')}]\n{doc.page_content}"
        for doc in docs
    )

def build_advanced_retriever(vector_db: Chroma):
    base_retriever = vector_db.as_retriever(
        search_type="mmr",
        search_kwargs={
            "k": RETRIEVE_TOP_K,
            "fetch_k": FETCH_K,
            "lambda_mult": LAMBDA_MULT
        }
    )

    multi_query_prompt = ChatPromptTemplate.from_messages([
        ("system", """你是查询生成助手。针对用户问题,生成3个不同角度、不同措辞的检索查询,用于知识库向量检索。只输出查询,每行一条,不要多余解释。"""),
        ("human", "原始问题:{question}")
    ])

    multi_query_retriever = MultiQueryRetriever.from_llm(
        retriever=base_retriever,
        llm=llm,
        prompt=multi_query_prompt
    )

    compressor = LLMChainFilter.from_llm(llm)
    compression_retriever = ContextualCompressionRetriever(
        base_retriever=multi_query_retriever,
        base_compressor=compressor
    )
    return compression_retriever

def build_advanced_rag_chain(vector_db: Chroma):
    retriever = build_advanced_retriever(vector_db)

    rag_prompt = ChatPromptTemplate.from_messages([
        ("system", """你是企业知识库问答助手,严格依据提供的上下文回答。
                    1. 只使用上下文给出的信息,不要编造;知识库没有则输出"知识库中未找到相关内容"。
                    2. 回答尽量简洁准确,可以引用来源信息。
                    上下文参考:
        {context}"""),
        ("human", "{question}")
    ])

    advanced_rag_chain = (
        {"context": retriever | format_docs, "question": RunnablePassthrough()}
        | rag_prompt
        | llm
        | StrOutputParser()
    )
    return advanced_rag_chain, retriever

class RerankerRetriever(BaseRetriever):
    base_retriever: BaseRetriever = Field(description="底层召回检索器")
    reranker_model: CrossEncoder = Field(description="交叉编码器重排模型")
    top_n: int = Field(default=3, description="重排之后保留多少条")

    def _get_relevant_documents(self, query: str) -> List[Document]:
        candidates = self.base_retriever.invoke(query)
        if not candidates:
            return []

        pairs = [[query, doc.page_content] for doc in candidates]
        scores = self.reranker_model.predict(pairs)
        scored_docs = sorted(zip(candidates, scores), key=lambda x: x[1], reverse=True)
        keep_docs = [doc for doc, score in scored_docs[:self.top_n]]

        print(f"\n[Reranker重排后保留 {len(keep_docs)} 条文档]")
        for d, s in scored_docs[:self.top_n]:
            print(f"rerank_score={s:.4f} | source={d.metadata.get('source')}")
        return keep_docs

def build_reranker_advanced_rag(vector_db: Chroma):
    base_compress_retriever = build_advanced_retriever(vector_db)
    reranker = CrossEncoder("./models/models/BAAI--bge-reranker-v2-m3/snapshots/master")
    rerank_retriever = RerankerRetriever(
        base_retriever=base_compress_retriever,
        reranker_model=reranker,
        top_n=3
    )

    rag_prompt = ChatPromptTemplate.from_messages([
        ("system", """你是知识库问答助手,严格依据提供的上下文回答。无相关信息直接输出"知识库中未找到相关内容",禁止幻觉编造。上下文:{context}"""),
        ("human", "{question}")
    ])
    chain = (
        {"context": rerank_retriever | format_docs, "question": RunnablePassthrough()}
        | rag_prompt
        | llm
        | StrOutputParser()
    )
    return chain, rerank_retriever

if __name__ == "__main__":
    test_docs = [
        Document(
            page_content="llama.cpp是高性能GGUF格式本地大模型推理框架,支持CPU/GPU混合加速,能够对外提供兼容OpenAI接口的本地API服务,本程序使用该服务提供LLM能力。",
            metadata={"source":"local_env.md"}
        ),
        Document(
            page_content="Embedding向量不能直接使用对话型大模型,必须使用专门的嵌入模型;本示例使用Qwen3‑Embedding作为向量模型,用于Chroma向量库的文档向量化检索。",
            metadata={"source":"embedding_note.md"}
        ),
        Document(
            page_content="Naive‑RAG即朴素RAG,仅做基础向量相似度检索,没有多查询扩展、没有上下文过滤压缩、没有重排序模块,检索角度单一,复杂问题召回效果有限。",
            metadata={"source":"rag_compare.md"}
        ),
        Document(
            page_content="Advanced‑RAG在Naive‑RAG朴素RAG基础上做能力增强,典型优化手段包含MultiQuery多查询生成、MMR多样性检索、LLM上下文压缩过滤、Cross‑Encoder重排序。本程序完整实现以上增强链路。",
            metadata={"source":"rag_compare.md"}
        ),
        Document(
            page_content="Modular‑RAG将RAG流程拆成可插拔组件,检索器、压缩器、重排器都可以自由替换,但整体执行流程是固定的,本示例的链路就是模块化RAG的实践。",
            metadata={"source":"rag_intro.md"}
        ),
        Document(
            page_content="Agentic‑RAG依靠大模型自主规划决策,动态判断是否需要多次检索,适合多跳、复杂推理类问题,和本示例Advanced‑RAG固定检索链路有明显区别。",
            metadata={"source":"rag_intro.md"}
        ),
    ]

    db = get_vector_store(test_docs, incremental=False)
    print(f"\n向量库总块数:{db._collection.count()}")

    rag_chain, ret = build_reranker_advanced_rag(db)

    user_query = "Advanced‑RAG相比Naive‑RAG做了哪些增强手段?"
    print(f"\n[用户问题:{user_query}]")

    retrieved_docs = ret.invoke(user_query)
    print("\n[经过多查询+压缩+重排之后的上下文]")
    print(format_docs(retrieved_docs))

    answer = rag_chain.invoke(user_query)
    print("\n[Advanced‑RAG最终回答]")
    print(answer)

运行结果完整验证了「粗召回扩量+精筛降噪」的核心逻辑,彻底修复前文单一模块的各类缺陷,输出结果精准匹配用户问题、无幻觉、无关键信息缺失,完全达到生产环境落地标准。

bash 复制代码
CMD> python main.py
CMD> 
[*] 删除旧向量库,覆盖重建模式
[*] 新建向量库
[+] 存入 6 个文本块

向量库总块数:6
Loading weights: 100%|██████████| 393/393 [00:00<00:00, 4188.39it/s]

[用户问题:Advanced‑RAG相比Naive‑RAG做了哪些增强手段?]

[Reranker重排后保留 3 条文档]
rerank_score=0.9316 | source=rag_compare.md
rerank_score=0.2229 | source=rag_intro.md
rerank_score=0.0049 | source=rag_intro.md

[经过多查询+压缩+重排之后的上下文]
[来源:rag_compare.md|页码:-]
Naive‑RAG即朴素RAG,仅做基础向量相似度检索,没有多查询扩展、没有上下文过滤压缩、没有重排序模块,检索角度单一,复杂问题召回效果有限。
---
[来源:rag_intro.md|页码:-]
Agentic‑RAG依靠大模型自主规划决策,动态判断是否需要多次检索,适合多跳、复杂推理类问题,和本示例Advanced‑RAG固定检索链路有明显区别。
---
[来源:rag_intro.md|页码:-]
Modular‑RAG将RAG流程拆成可插拔组件,检索器、压缩器、重排器都可以自由替换,但整体执行流程是固定的,本示例的链路就是模块化RAG的实践。

[Reranker重排后保留 3 条文档]
rerank_score=0.9991 | source=rag_compare.md
rerank_score=0.9316 | source=rag_compare.md
rerank_score=0.2229 | source=rag_intro.md

[Advanced‑RAG最终回答]

Advanced‑RAG在Naive‑RAG朴素RAG基础上做了以下增强手段:
1. MultiQuery多查询生成
2. MMR多样性检索
3. LLM上下文压缩过滤
4. Cross-Encoder重排序
相关推荐
lyshark16 小时前
基于 HuggingFace Tokenizers 训练自定义分词器
大模型应用技术实践
lyshark2 天前
基于 vLLM+Nginx 构建负载均衡推理集群
大模型应用技术实践
lyshark3 天前
LLama-Factory 实现大模型LoRA-SFT微调指南
大模型应用技术实践
lyshark5 天前
千问大模型完整RLHF全参数微调指南
大模型应用技术实践
lyshark7 天前
千问大模型二次LoRA‑SFT指令微调指南
大模型应用技术实践
lyshark12 天前
轻量化小模型MiniMind从训练到落地指南
大模型应用技术实践
lyshark17 天前
Ubuntu 大模型HF转GGUF全流程实践指南
大模型应用技术实践·linux 系统运维技术实践
lyshark18 天前
LangChain 消息流输出与结构化处理
大模型应用技术实践
lyshark19 天前
Python 原生封装 Llama.cpp 大模型推理接口
大模型应用技术实践
lyshark20 天前
LangGraph Server Agent 框架本地部署指南
大模型应用技术实践