【保姆级 - 大模型应用开发】DeepSeek + Faiss + langchain 搭建本地知识库 检索 | 代码实战
文章目录
- RAG(Retrieval-Augmented Generation)-- 检索增强生成:
- ⭐ RAG 的三大步骤:数据预处理、检索和生成 💜 【理论基础】
- RAG 第一步:数据预处理
- RAG 第二步:检索 Retrieval
- RAG 第三步:生成 Generation
- ⭐ RAG 部署的三大步骤 :PDF文本提取、知识库构建和 QA💜 【代码实战】
- 1. PDF提取文本和页码信息:`text, page_numbers`
- 2. 根据文本和页码信息创建知识库 `knowledgeBase`
- 3. 根据知识库提供的 `docs` 进行 Q&A
RAG(Retrieval-Augmented Generation)-- 检索增强生成:
-
一种结合信息检索(Retrieval)和文本生成(Generation)的技术:通过实时检索相关信息,并将其作为上下文输入到生成模型中,从而提高生成结果的时效性和准确性 (← 优势)
目前感觉最好用的 RAG 产品是 notebookLM,可以先上手体验下 ( BUT 需要外网,不开源)
开源产品可以选择 Qwen-Agent ---- 本文是从头构建 RAG,也可以选择基于 Qwen-Agent 来快速实现 RAG 部署 (见主页博文)
⭐ RAG 的三大步骤:数据预处理、检索和生成 💜 【理论基础】
RAG 第一步:数据预处理
在让 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
让 AI 从知识库中找到与你问题相关的资料,主要包括以下几步:
- 查询处理:将你的问题转成向量(即数字表达),然后在向量数据库中搜索,找出最相关的内容片段
- 相关性排序:对初步搜索结果进行相关性排序,挑选最合适的片段,用于下一步生成答案
RAG 第三步:生成 Generation
让大模型结合相关资料,生成最终答案,主要包括以下几步:
- 上下文组装:把上一步找到的片段,与你的问题一起输入大模型,构成“增强版上下文”
- 生成回答:大语言模型根据增强后的上下文,生成准确、有依据的回答
⭐ RAG 部署的三大步骤 :PDF文本提取、知识库构建和 QA💜 【代码实战】
from typing import List, Tuple
DASHSCOPE_API_KEY = '' ## TODO: 填充你的 DASHSCOPE_API_KEY 🔍🔍🔍
1. PDF提取文本和页码信息:text, page_numbers
from PyPDF2 import PdfReaderdef 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_textpage_numbers.extend([page_number] * len(extracted_text.split("\n")))return text, page_numberspdf_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 DashScopeEmbeddingsdef 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. 分割文本为 chunkchunks = 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 knowledgeBaseknowledgeBase = 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 提问并打分,返回评分最高的那一块的答案,适合精准筛选 |