【保姆级 - 大模型应用开发】DeepSeek + Faiss + langchain 从零搭建本地知识库 检索 | 代码实战

发布于:2025-08-06 ⋅ 阅读:(20) ⋅ 点赞:(0)

在这里插入图片描述


RAG(Retrieval-Augmented Generation)-- 检索增强生成:

  • 一种结合信息检索(Retrieval)和文本生成(Generation)的技术:通过实时检索相关信息,并将其作为上下文输入到生成模型中,从而提高生成结果的时效性准确性 (← 优势)

    目前感觉最好用的 RAG 产品是 notebookLM,可以先上手体验下 ( BUT 需要外网,不开源)
    开源产品可以选择 Qwen-Agent ---- 本文是从头构建 RAG,也可以选择基于 Qwen-Agent 来快速实现 RAG 部署 (见主页博文)

在这里插入图片描述


⭐ RAG 的三大步骤:数据预处理、检索和生成 💜 【理论基础】

RAG 第一步:数据预处理

Indexing => 如何更好地把知识存起来
在让 AI 使用外部知识前,需要先把原始资料整理成适合检索的格式,主要包括以下几步:

  • 知识库构建:汇总各种收集到的相关资料,比如文档,建立一个“外部知识库”

  • 文档分块:把长文档按语义切成一小段一小段(叫做 chunk)

    • ✏️ 说明:分得太碎可能语义不完整,分太大又不利于检索,要平衡
  • 向量化:使用“嵌入模型”把 chunk 转成“向量”并存进“向量数据库”,方便快速搜索

    类型 模型 特点 适用场景 开源状态
    通用模型 BGE-M3 多语言、长文本、混合检索 高精度 RAG、跨语文档 ✅ 开源
    text-embedding-3-large 英文表现强 英文场景 ❌ 闭源(OpenAI)
    Jina-embeddings-v2 模型小,推理快 实时任务、轻量场景 ✅ 开源
    中文模型 xiaobu-embedding-v2 语义理解强 中文分类、检索 ✅ 开源
    M3E-Turbo 本地部署好 法律、医疗文本 ✅ 开源
    stella-mrl-large-zh-v3.5-1792 关系抽取强 NLP分析、高语义需求 ✅ 开源
    指令驱动 & 复杂任务 gte-Qwen2-7B-instruct 支持代码与跨模态 多任务问答 ✅ 开源(协议限制)
    E5-mistral-7B Zero-shot强 动态语境 ✅ 开源
    企业级 BGE-M3 稳定部署、强检索 企业知识库 ✅ 开源
    E5-mistral-7B 支持微调 高复杂系统 ✅ 开源

    ✏️ 说明:可以在 https//modelscope.cn/ 找到并下载 embedding 模型 (以下示例中部署 BGE-M3,个人级不建议部署,代价大~看下流程即可)

    # 安装依赖(只需运行一次)
    # pip install FlagEmbedding
    
    # 1️⃣ 加载模型
    from FlagEmbedding import BGEM3FlagModel
    
    # 加载 BGE-M3 模型,use_fp16=True 可以加速(需要显卡支持)
    model = BGEM3FlagModel('BAAI/bge-m3', use_fp16=True)  ## 替换成你存 BGE 模型的路径
    
    # 2️⃣ 编码文本为向量
    # 设定两个句子列表,一个作为查询,一个作为候选答案
    sentences_query = ["什么是BGE M3?", "BM25的定义"]
    sentences_docs = [
        "BGE M3 是一个支持稠密检索、词匹配和多交互的嵌入模型。",
        "BM25 是一种用于排名的词袋模型。"
    ]
    
    # 使用模型编码成向量(dense_vecs)
    emb_query = model.encode(sentences_query, max_length=8192, batch_size=12)['dense_vecs']
    emb_docs = model.encode(sentences_docs)['dense_vecs']
    
    # 3️⃣ 计算相似度
    # 使用点乘计算 query 和每个文档的相似度(越高越相关)
    similarity = emb_query @ emb_docs.T
    
    # 输出相似度矩阵
    print("相似度矩阵:")
    print(similarity)  ## 形状是 [sentences_query 的数量, sentences_docs 的数量]
    

RAG 第二步:检索 Retrieval

Retrieval => 如何在大量的知识中,找到一小部分有用的,给到模型参考
让 AI 从知识库中找到与你问题相关的资料,主要包括以下几步:

  • 查询处理:将你的问题转成向量(即数字表达),然后在向量数据库中搜索,找出最相关的内容片段
  • 相关性排序:对初步搜索结果进行相关性排序,挑选最合适的片段,用于下一步生成答案

RAG 第三步:生成 Generation

Generation => 如何结合用户的提问和检索到的知识,让模型生成有用的答案
让大模型结合相关资料,生成最终答案,主要包括以下几步:

  • 上下文组装:把上一步找到的片段,与你的问题一起输入大模型,构成“增强版上下文”
  • 生成回答:大语言模型根据增强后的上下文,生成准确、有依据的回答

在这里插入图片描述

⭐ RAG 部署的三大步骤 :PDF提取、知识库创建和 Q&A💜 【代码实战】

from typing import List, Tuple
DASHSCOPE_API_KEY = ''  ## TODO: 填充你的 DASHSCOPE_API_KEY 🔍🔍🔍

1. PDF提取文本和页码信息:text, page_numbers

from PyPDF2 import PdfReader


def extract_text_with_page_numbers(pdf) -> Tuple[str, List[int]]:
    """
    从PDF中提取文本并记录每行文本对应的页码
    
    参数:
        pdf: PDF文件对象
    
    返回:
        text: 提取的文本内容
        page_numbers: 每行文本对应的页码列表
    """
    text = ""
    page_numbers = []

    for page_number, page in enumerate(pdf.pages, start=1):
        extracted_text = page.extract_text()
        if extracted_text:
            text += extracted_text
            page_numbers.extend([page_number] * len(extracted_text.split("\n")))

    return text, page_numbers


pdf_reader = PdfReader('./你准备的 PDF 文档(自行修改 🔍🔍🔍).pdf')  # 读取 PDF 文件
text, page_numbers = extract_text_with_page_numbers(pdf_reader)  # 提取文本和页码信息

2. 根据文本和页码信息创建知识库 knowledgeBase

from langchain_community.vectorstores import FAISS
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings import DashScopeEmbeddings


def process_text_with_splitter(text: str, page_numbers: List[int]) -> FAISS:
    """
    处理文本并创建 FAISS 知识库存储
    
    参数:
        text: 提取的文本内容
        page_numbers: 每行文本对应的页码列表
    
    返回:
        knowledgeBase: FAISS 知识库
    """
    # 步骤一:准备 chunks
    ## 1. 创建文本分割器
    text_splitter = RecursiveCharacterTextSplitter(
        separators=["\n\n", "\n", ".", " ", ""],
        chunk_size=1000,  		## 最大长度
        chunk_overlap=200,  	## 重叠长度
        length_function=len,
    )
    ## 2. 分割文本为 chunk
    chunks = text_splitter.split_text(text)
    # print(f"Text split into {len(chunks)} chunks.")
        
    # 步骤二:准备嵌入模型
    embeddings = DashScopeEmbeddings(
        model="text-embedding-v1",
        dashscope_api_key=DASHSCOPE_API_KEY,
    )
    
    # 步骤三:根据准备 chunks 和 嵌入模型,创建 FAISS 知识库
    knowledgeBase = FAISS.from_texts(chunks, embeddings)
    knowledgeBase.page_info = {chunk: 0 for i, chunk in enumerate(chunks)}  # 存储 chunk 对应的页码
    # TODO:暂时chunk 对应的页码都标注为了 0,待后续完善
    
    return knowledgeBase

knowledgeBase = process_text_with_splitter(text, page_numbers)
# knowledgeBase.save_local('./faiss-0804')  # 保存 (可选)

3. 根据知识库提供的 docs 进行 Q&A

from langchain_community.llms import Tongyi
from langchain.chains.question_answering import load_qa_chain
from langchain_community.callbacks.manager import get_openai_callback


# 加载问答链
llm = Tongyi(model_name="qwen-turbo", dashscope_api_key=DASHSCOPE_API_KEY) # qwen-turbo
chain = load_qa_chain(llm, chain_type="stuff")  

# 拼接查询问题 query + docs -> input_data 
query = "你的提问(自行修改 🔍🔍🔍)"
docs = knowledgeBase.similarity_search(query)
input_data = {"input_documents": docs, "question": query}
    
# 执行问答链 (同时使用回调函数跟踪 API 调用成本)
with get_openai_callback() as cost:
    response = chain.invoke(input=input_data)
    print(f"查询已处理。成本: {cost}")
    print(response["output_text"])
 
# 补充 response 回答依据
print("来源:")

unique_pages = set()  
for doc in docs:  # 获取每个文档块的来源页码,存储至 unique_pages 集合
    text_content = getattr(doc, "page_content", "")
    source_page = knowledgeBase.page_info.get(
        text_content.strip(), "未知"
    )
    unique_pages.add(source_page)
    print(f"文本块页码: {source_page}")

运行会输出类似下面的内容:

根据提供的信息,... 。具体相关内容如下:

......

来源:
文本块页码: ...

✏️ 其中,我们看到核心代码 chain = load_qa_chain(llm, chain_type="stuff")
---- LangChain 问答链的四种 chain_type 类型说明:

代码片段 功能说明
chain = load_qa_chain(llm, chain_type="stuff") 将所有文档拼接为一个 prompt,一次性输入给 LLM,适用于文档较小的情况
chain = load_qa_chain(llm, chain_type="map_reduce") 每个 chunk 单独作为 prompt 提问,然后将结果合并做最终回答,适合并行处理
chain = load_qa_chain(llm, chain_type="refine") 先从一个 chunk 得到初步回答,后续逐步 refine,适合多轮提炼答案
chain = load_qa_chain(llm, chain_type="map_rerank") 每个 chunk 提问并打分,返回评分最高的那一块的答案,适合精准筛选

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到