大模型应用开发(五):使用RAG搭建简单的本地知识库
学习目标
RAG技术概述
RAG核心原理与流程
NaiveRAG
LangChain快速搭建本地知识库-使用NaiveRAG
在上一篇文章中《大模型应用开发(四):主流三种开发模式(范式)》,RAG是大模型应用开发的范式,我们今天一起来认识一下RAG,并使用RAG快熟搭建一个本地知识库
一、什么是RAG
RAG(Retrieval-Augmented Generation)
-
检索增强生成,是一种结合信息检索(Retrieval)和文本生成(Generation)的技术
-
RAG技术通过实时检索相关文档或信息,并将其作为上下文输入到生成模型中,从而提高生成结果的时效性和准确性。

上图是RAG的基本流程:用户提出问题--->去知识库检索问题的答案(Retrieval)--->将问题和答案组装成新的提示词(Augmentation)--->将新提示词交给LLM生成答案(Generation)
1.1 RAG 的优势是什么?
-
解决知识时效性问题:大模型的训练数据通常是静态的,无法涵盖最新信息,而RAG可以检索外部知识库实时更新信息,确保回答的时效性
-
减少模型幻觉:通过引入外部知识,回答基于检索到的真实文档,RAG能够减少模型生成虚假或不准确内容的可能性
-
提升专业领域回答质量:RAG能够结合垂直领域的专业知识库,生成更具专业深度的回答
-
生成内容的溯源(可解释性):检索生成后的内容可追溯来源,使回答增加可信度
-
灵活性与可控性高:知识库可独立更新,不影响模型本身,企业能精确把控输出内容;
-
实施成本较低:无需大量标注数据或模型训练,仅需构建知识库与检索系统。
1.2 RAG 的核心原理与流程

Step1:数据预处理,构建索引知识库
-
知识整理及加载:收集并整理文档、网页、数据库等多源数据,构建外部知识库
-
文档分块:将文档切分为适当大小的片段(chunks),以便后续检索。分块策略需要在语义完整性与检索效率之间取得平衡
-
向量化处理:使用嵌入模型(如BGE、M3E、Chinese-Alpaca-2等)将文本块转换为向量,并存储在向量数据库中
Step2:检索阶段
-
查询处理:将用户输入的问题转换为向量,并在向量数据库中进行相似度检索,找到最相关的文本片段(使用向量数据库的相似度检索)
-
重排序:对检索结果进行相关性排序,选择最相关的片段作为生成阶段的输入(优化步骤)
Step3:生成阶段
-
上下文组装:将检索到的文本片段与用户问题结合,形成增强的上下文输入
-
生成回答:大语言模型基于增强的上下文生成最终回答
划重点:RAG 本质上就是重构了一个新的 Prompt!
二、NaiveRAG

NaiveRAG的一个最基本的RAG阶段,具体步骤如下:
基本的输入/输出:用户直接输入问题给LLM,若不使用检索增强生成(RAG),则直接由LLM对问题进行应答
使用RAG的流程:
-
Indexing => 如何更好地把知识存起来?将文本文档切块,然后向量化并存储到向量数据库,用于检索
-
Retrieval => 如何在大量的知识中,找到一小部分有用的,给到模型参考?通过向量相似度找到有用的块
-
Generation => 如何结合用户的提问和检索到的知识,让模型生成有用的答案?将问题和有用的块给LLM生成最终答案
划重点:上面三个步骤虽然看似简单,但在 RAG 应用从构建到落地实施的整个过程中,涉及较多复杂的工作内容!
三、LangChain快速搭建本地知识库检索-使用NaiveRAG
3.1. 环境准备
-
本地安装好 Conda 环境
-
推荐使用阿里大模型平台百炼:https://bailian.console.aliyun.com/
-
百炼平台使用
-
注册登录
-
申请api key
-
3.2. 搭建流程
构建知识库
-
加载文档数据
-
文档分块
-
文本块向量化处理并存储向量数据库
检索+生成
-
封装检索接口
-
构建调用流程:Query -> 检索 -> Prompt -> LLM -> 回复
3.3 代码案例
使用Conda创建虚拟环境并激活
conda create -n naive_rag python=3.12
conda activate naive_rag
激活后,终端开头会显示 (naive_rag),表示已进入该虚拟环境,此时安装的所有包都只属于这个环境
安装相关依赖
python版本使用的是3.12
PyPDF2==3.0.1
dashscope==1.23.3
langchain==0.3.25
langchain-community==0.3.24
langchain-openai==0.3.18
faiss-cpu==1.11.0
构建知识库代码
build_knowledge_store.py
# -*- coding=utf-8 -*-
# @description: 数据预处理,构建知识库
# @File : build_knowledge_store.py
# @author: zhouyer
# @time : 2026/3/10 22:05
import logging
import os
import pickle
from typing import List, Tuple
from PyPDF2 import PdfReader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_community.vectorstores import FAISS
# 设置百炼平台的api_key到环境变量中 DASHSCOPE_API_KEY_YY
os.environ["DASHSCOPE_API_KEY"] = os.getenv("DASHSCOPE_API_KEY_YY")
# 向量数据库存储路基
VECTOR_STORE_PATH = './document/vector_db_01'
def load_document(document_path: str) -> Tuple[str, List[int]]:
"""
** 数据预处理,构建索引库-第一步:加载文档数据**
从PDF中提取文本并记录每行文本对应的页码
参数:
document_path: 文档路径
返回:
text: 提取的文本内容
page_numbers: 每行文本对应的页码列表
"""
text = ""
page_numbers = []
# 读取pdf文档
pdf_reader = PdfReader(document_path)
for page_number, page in enumerate(pdf_reader.pages, start=1):
# 提取文本
extracted_text = page.extract_text()
if extracted_text:
text += extracted_text
page_numbers.extend([page_number] * len(extracted_text.split("\n")))
else:
logging.warning(f"No text found on page {page_number}.")
return text, page_numbers
def process_text_with_splitter(text: str) -> List[str]:
"""
** 数据预处理,构建索引库-第二步:文档分块 **
将文档切分为适当大小的片段(chunks),以便后续检索
参数:
text: 提取的文本内容
page_numbers: 每行文本对应的页码列表
返回:
chunks:文本分块结果
"""
# 创建文本分割器,用于将长文本分割成小块
text_splitter = RecursiveCharacterTextSplitter(
separators=["\n\n", "\n", ".", " ", ""],
chunk_size=512, # 每个块的字符数据,根据各大模型的效果,512个字符时模型很好理解语意的合适值
chunk_overlap=128, # 块与块之间的重叠部分大小,一般设置为块大小的10%~20%,即上一个chunk末尾的128个字符与下一个chunk开始的128个字符是一样的,为了保证上下文语意的连贯性
length_function=len,
)
# 分割文本
return text_splitter.split_text(text)
def chunk_document_embedding(chunks: List[str], page_numbers: List[int], save_path: str = None) -> FAISS:
"""
** 数据预处理,构建索引库-第二步:文本块向量化处理,并保存到向量数据库 **
在LangChain 中,提供了 from_texts 和 from_documents 两个通用方法,可以快捷地将数据从文本和文档中导入到向量数据库中。
将每个chunk向量化后保存的Faiss向量库,同时保存页码信息,方便后续溯源
:param chunks: 文档块
:param page_numbers: 每行文本对应的页码
:param save_path:可选,保存向量数据库的路径
:return knowledgeBase: 基于FAISS的向量存储对象
"""
# 存储每个文本块对应的页码信息
page_info = {chunk: page_numbers[i] for i, chunk in enumerate(chunks)}
# 调用阿里百炼平台文本嵌入模型,配置环境变量 DASHSCOPE_API_KEY
embeddings = DashScopeEmbeddings(
model="text-embedding-v4"
)
# 从文本块创建知识库
knowledge_base = FAISS.from_texts(chunks, embeddings)
print("已从文本块创建知识库...")
knowledge_base.page_info = page_info
# 如果提供了保存路径,则保存向量数据库和页码信息
if save_path:
# 确保目录存在
os.makedirs(save_path, exist_ok=True)
# 保存FAISS向量数据库
knowledge_base.save_local(save_path)
print(f"向量数据库已保存到: {save_path}")
# 保存页码信息到同一目录
with open(os.path.join(save_path, "page_info.pkl"), "wb") as f:
pickle.dump(page_info, f)
print(f"页码信息已保存到: {os.path.join(save_path, 'page_info.pkl')}")
return knowledge_base
if __name__ == '__main__':
pdf_document_path = "document/xxxx.pdf"
# 提取文本和页码信息
text, page_numbers = load_document(pdf_document_path)
# print(f'页码: {page_numbers}')
# print(f'文本: {text}')
print(f'总字符数:{len(text)}')
# 文档分块
chunks = process_text_with_splitter(text)
print(f"文本被分割成 {len(chunks)} 个块。")
# 向量化处理并存储
knowledge_store = chunk_document_embedding(chunks, page_numbers, save_path=VECTOR_STORE_PATH)
print(knowledge_store) # 返回一个FAISS对象----> <langchain_community.vectorstores.faiss.FAISS object at 0x107710980>
"""
总字符数:3881
文本被分割成 10 个块。
已从文本块创建知识库...
向量数据库已保存到: ./document/vector_db_01
页码信息已保存到: ./document/vector_db_01/page_info.pkl
<langchain_community.vectorstores.faiss.FAISS object at 0x111835d90>
"""
目前当前构建知识库的代码是一个非常基础的结构,仅能支持pdf文档,如果有其他的也可以进行改在
load_document:加载文档数据
process_text_with_splitter:文档分块
chunk_document_embedding:文本块向量化处理并存储
上面三个方法对应【构建知识库】搭建流程中的三个步骤
检索+生成代码
retrieval_generation.py
# -*- coding=utf-8 -*-
# @description: 检索+生成
# @File : retrieval_generation.py
# @author: zhouyer
# @time : 2026/3/10 22:24
import os
import pickle
from langchain.chains.question_answering import load_qa_chain
from langchain_community.callbacks.manager import get_openai_callback
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_community.vectorstores import FAISS
from langchain_openai import ChatOpenAI
# 设置百炼平台的api_key到环境变量中 DASHSCOPE_API_KEY_YY
os.environ["DASHSCOPE_API_KEY"] = os.getenv("DASHSCOPE_API_KEY_YY")
# 向量数据库存储路基
VECTOR_STORE_PATH = './document/vector_db_01'
def load_knowledge_base(load_path: str, embeddings=None) -> FAISS:
"""
从磁盘加载向量数据库和页码信息
参数:
load_path: 向量数据库的保存路径
embeddings: 可选,嵌入模型。如果为None,将创建一个新的DashScopeEmbeddings实例
返回:
knowledgeBase: 加载的FAISS向量数据库对象
"""
# 如果没有提供嵌入模型,则创建一个新的
if embeddings is None:
embeddings = DashScopeEmbeddings(
model="text-embedding-v4"
)
# 加载FAISS向量数据库,添加allow_dangerous_deserialization=True参数以允许反序列化
knowledge_base = FAISS.load_local(load_path, embeddings, allow_dangerous_deserialization=True)
print(f"向量数据库已从 {load_path} 加载。")
# 加载页码信息
page_info_path = os.path.join(load_path, "page_info.pkl")
if os.path.exists(page_info_path):
with open(page_info_path, "rb") as f:
page_info = pickle.load(f)
knowledge_base.page_info = page_info
print("页码信息已加载。")
else:
print("警告: 未找到页码信息文件。")
return knowledge_base
def simple_query(query: str):
'''
检索+生成
检索:传入问题--->从向量数据库中检索相似性答案(问题向量化+相似性检索)--->得到相似性的文本块(Top k)
生成:将问题+检索的答案结合,形成增强的上下文输入(Prompt)---->LLM--->输出最终回答
:param query: 问题
:return:
'''
if query:
###### 如何加载已保存的向量数据库
knowledge_store = load_knowledge_base(VECTOR_STORE_PATH)
# similarity_search():基础相似度搜索,传递 query(搜索语句)、k(返回条数)、filter(过滤器)、fetch_k(富余条数) 等。
docs = knowledge_store.similarity_search(query)
# 初始化对话大模型,不建议使用推理模型,因为推理模型还需要进一步逻辑推理,耗时而且没用,因为这里直接给咯答案,只需要整理,使用chat模型,即生成模型,
# deepseek r1就是推理不行,不能使用
chatLLM = ChatOpenAI(
# 若没有配置环境变量,请用百炼API Key将下行替换为:api_key="sk-xxx",
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
model="deepseek-v3"
)
# 加载问答链
chain = load_qa_chain(chatLLM, chain_type="stuff")
# 准备输入数据:将问题和相似性检索的答案chunks 交给LLM----->新的提示词。--- 这里就是RAG的A-增强
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"])
print("来源:")
# 记录唯一的页码
unique_pages = set()
# 显示每个文档块的来源页码
for doc in docs:
text_content = getattr(doc, "page_content", "")
source_page = knowledge_store.page_info.get(
text_content.strip(), "未知"
)
if source_page not in unique_pages:
unique_pages.add(source_page)
print(f"文本块页码: {source_page}")
if __name__ == '__main__':
############ 【检索】基于构建好的知识库进行 #############
# 设置查询问题
# query = "客户经理被投诉了,投诉一次扣多少分"
# query = "客户经理每年评聘申报时间是怎样的?"
query = "资深客户经理的考核标准是什么?"
simple_query(query)
"""
query = "客户经理被投诉了,投诉一次扣多少分"
查询已处理。成本: Tokens Used: 1253
Prompt Tokens: 1201
Prompt Tokens Cached: 0
Completion Tokens: 52
Reasoning Tokens: 0
Successful Requests: 1
Total Cost (USD): $0.0
根据工作质量考核标准中的服务质量考核部分,客户经理被投诉一次扣2分。具体条款如下:
2、客户服务效率低,态度生硬或不及时为客户提供维护服务,有客户投诉的,每投诉一次扣2分。
query = "客户经理每年评聘申报时间是怎样的?"
答案如下:
查询已处理。成本: Tokens Used: 1287
Prompt Tokens: 1239
Prompt Tokens Cached: 0
Completion Tokens: 48
Reasoning Tokens: 0
Successful Requests: 1
Total Cost (USD): $0.0
根据第十一条的规定,客户经理每年评聘的申报时间是**一月份**。分行人力资源部和个人业务部会在**二月份**组织统一的资格考试,考试合格者将获得个金客户经理资格证书,有效期为一年。
来源:
文本块页码: 1
query = "资深客户经理的考核标准是什么?"
根据提供的考核标准,资深客户经理的准入和考核要求如下:
1. **准入标准**:
- 储蓄业务(季日均余额):500万元
- 个贷业务(季新增发放个贷):800万元
2. **考核分值对应级别**:
- 5级:165分
- 4级:170分
- 3级:175分
- 2级:180分
- 1级:185分
3. **其他说明**:
- 个贷业务是中级以上客户经理(含资深)的考核进入标准。
- 超出最低标准的部分可按比例折算(50万储蓄=50万个贷=50张有效卡=5分)。
注意:资深客户经理还需满足基础素质要求(如学历、工作经验等)和工作质量考核(如避免投诉、差错等扣分项)。具体执行可能根据分行政策调整。
"""
load_knowledge_base:加载本地的知识库,用于检索
simple_query:相似度检索+LLM生成答案
3.4 小结
1. PDF文本提取与处理
-
使用PyPDF2库的PdfReader从PDF文件中提取文本在提取过程中记录每行文本对应的页码,便于后续溯源
-
使用RecursiveCharacterTextSplitter将长文本分割成小块,便于向量化处理
2. 向量数据库构建
-
使用OpenAIEmbeddings / DashScopeEmbeddings将文本块转换为向量表示
-
使用FAISS向量数据库存储文本向量,支持高效的相似度搜索为每个文本块保存对应的页码信息,实现查询结果溯源
3. 语义搜索与问答链
-
基于用户查询,使用
similarity_search在向量数据库中检索相关文本块 -
使用文本语言模型和
load_qa_chain构建问答链将检索到的文档和用户问题作为输入,生成回答
4. 成本跟踪与结果展示
-
使用get_openai_callback跟踪API调用成本
-
展示问答结果和来源页码,方便用户验证信息
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)