本文详细介绍了如何用纯代码从零开始构建RAG(检索增强生成)系统。内容包括RAG原理与调优策略,文本处理,分块向量化存储,构建RAG聊天机器人及前端实现。通过完整代码示例,帮助读者实现基于本地知识库的AI问答系统,解决大模型回答不够个性化、针对性的问题。
老规矩,先看最终效果。
我选取了“信贷业务手册.docx”文档作为本地知识库语料,500页,40万字。
在提问后,大模型调用本地知识库进行思考,输出结果还不错。

实现上述效果,基本可分为这几步:
- 处理本地知识库文本
- 完成文本分块、向量化和存储
- 构建RAG版chat
- 增加一个前端页面用于展示
以上步骤我后面会详细展开,在此之前,应大家的私信请求,先带大家了解RAG的原理。
这是落地实现和调优的依据和关键!
一、重要补充
1、RAG的原理
RAG是用本地知识库做补充,让最终的答案能更个性化、更有针对性。
整个过程分为建立索引和检索生成两个大的阶段,具体原理如下:
阶段一:建立索引
1)读取本地知识库文本,包括pdf、doc、ppt、excel、txt、md等;
2)将文本分割成不同的块(trunk),包括Split by Sentences、fix character count、overlapping window、RescursiveCharacterTextSpiltter等方法;
3)将切好的文本块转换成向量;
4)将向量存储到向量数据库,以便后续使用

阶段二:检索生成
1)将用户的Prompt转换成向量
2)将用户提示词向量与向量数据库中的向量做相似度匹配,找到相似的内容
3)把检索出来的相似内容和用户的问题拼接在一起,作为新的Prompt,给到大模型
4)大模型根据新的Prompt进行回答,相当于本地知识库中检索出来的内容将作为上下文供大模型参考

总结下来,RAG的整体运作流程如下:

2、RAG的调优策略
根据RAG原理,不难发现:如果想要获得高质量的、精准的答案,每一个环境都需要做好调优,才能达到最佳效果。
第一,索引建立阶段的调优
1)原始知识库文本的质量提升,包括数据清洗和标准化
2)文本分割成不同的块,分块策略的优化
3)将切好的文本块转换成向量,embbeding模型的调优
4)文本元数据的增强,比如更新时间,方便检索到最新版本
第二,检索阶段的调优
1)使用混合检索策略,比如向量+关键字
2)查询语言转写,相当于对用户的提示词进行治理
3)对向量数据库进行重排序(rerank)
第三,生成阶段的调优
1)优化提示词,比如增加系统提示词,做好限制
2)生成内容控制,比如对llm做微调
3)幻觉抑制,比如设置相似度的置信区间
其他
1)比如使用多模态的Embedding模型,支持图片、表格等非文本数据检索
2)增加评估模块,自动识别无效检索,并调用补充搜索
好了,知道了原理,就开始一步步建立成自己的RAG吧。
二、实现过程
1、环境准备
-
安装python3.10+以上版本
-
安装依赖包,可以放在requirements.txt中,通过pip install -r requirements.txt 命令安装
langchain
openai
faiss-cpu
flask
unstructured[all-docs]
sentence-transformers
3) 新建python工程,工程目录如下
RAGChat/
├── config.json
├── data/
│ └── # 存放导入的文本文件
├── app.py
├── utils/
│ ├── data_loader.py
│ ├── text_splitter.py
│ └── embedding.py
├── chatbot/
│ ├── rag_chatbot.py
└── frontend/
├── app.py
└── templates/
└── chat.html
2、配置文件,confg.json,填写好自己的url、model和apikey
{
"llm": {
"default": "deepseek",
"models": {
"deepseek": {
"url": "your_deepseek_url",
"model": "your_deepseek_model",
"apikey": "your_deepseek_apikey"
},
"other_model": {
"url": "your_other_model_url",
"model": "your_other_model",
"apikey": "your_other_model_apikey"
}
}
},
"embedding": {
"default": "bge-large-zh-v1.5",
"models": {
"bge-large-zh-v1.5": {
"url": "your_bge_url",
"model": "BAAI/bge-large-zh-v1.5",
"apikey": ""
},
"other_embedding_model": {
"url": "your_other_embedding_url",
"model": "your_other_embedding_model",
"apikey": "your_other_embedding_apikey"
}
}
}
}
3、处理本地知识库文本
1)删除空白页
2)去除图片,如果图片里有文字,OCR为文字
(因为开源免费的embedding模型就那几个,不支持多模态)
3)word文档转markdown格式
(这样对大模型更友好,转换工具参考[AI辅助工具:各种文档转Markdown]

(转换前)

(转换后)
4、文本分块、向量化和存储
1)本地知识库文本,要放到“data/”目录
2)核心代码
- 加载文本:langchain.document_loaders.load_documents
- 文本分块:langchain.document_loaders.RecursiveCharacterTextSplitter(递归)
- 文本向量化:HuggingFaceEmbeddings
- 存储向量:FAISS.from_documents

执行结果
以下为详细代码,如果比较熟悉,可忽略,直接到第5点。
文本加载代码 data_loader.py
import os
from langchain.document_loaders import PyPDFLoader, UnstructuredMarkdownLoader, UnstructuredWordDocumentLoader, \
UnstructuredPowerPointLoader, UnstructuredExcelLoader, TextLoader
def load_documents(data_dir):
if not os.path.exists(data_dir):
print(f"数据目录 {data_dir} 不存在,请检查路径。")
return []
documents = []
# 遍历数据目录下的所有文件
for root, dirs, files in os.walk(data_dir):
for file in files:
file_path = os.path.join(root, file)
try:
if file.endswith('.txt'):
loader = TextLoader(file_path, encoding='utf-8')
elif file.endswith('.pdf'):
loader = PyPDFLoader(file_path)
elif file.endswith('.md'):
loader = UnstructuredMarkdownLoader(file_path)
elif file.endswith(('.doc', '.docx')):
loader = UnstructuredWordDocumentLoader(file_path)
elif file.endswith(('.ppt', '.pptx')):
loader = UnstructuredPowerPointLoader(file_path)
elif file.endswith(('.xls', '.xlsx')):
loader = UnstructuredExcelLoader(file_path)
else:
print(f"不支持的文件类型: {file_path},跳过该文件")
continue
loaded_docs = loader.load()
documents.extend(loaded_docs)
print(f"成功加载文件: {file_path}")
except Exception as e:
print(f"加载文件 {file_path} 失败: {e}")
return documents
文本分割代码 text_splitter.py
from langchain.text_splitter import RecursiveCharacterTextSplitter
def split_text(documents):
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
length_function=len
)
texts = text_splitter.split_documents(documents)
return texts
from langchain.text_splitter import CharacterTextSplitter
def split_text(documents):
if not documents:
print("输入的文档列表为空,无法进行分割。")
return []
text_splitter = CharacterTextSplitter(
separator="\n",
chunk_size=1000,
chunk_overlap=200,
length_function=len
)
texts = text_splitter.split_documents(documents)
print(f"成功分割出 {len(texts)} 个文本块。")
return texts
文本向量化代码 embedding.py
import json
from langchain.embeddings import HuggingFaceEmbeddings
def get_embeddings():
with open('config.json', 'r') as f:
config = json.load(f)
embedding_config = config['embedding']['models'][config['embedding']['default']]
model_name = embedding_config['model']
# 如果不能科学上网,可以使用本地模型初始化嵌入
embeddings = HuggingFaceEmbeddings(model_name=model_name)
# Test embedding generation
test_text = "This is a test sentence."
try:
test_embedding = embeddings.embed_query(test_text)
print(f"Test embedding length: {len(test_embedding)}")
except Exception as e:
print(f"Error generating test embedding: {e}")
return embeddings
5、构建RAG版chat
1)先明确流程
在客户输入问题之后,调用大模型之前,先进行本地知识库向量查询,拼接查询结果+历史聊天记录+客户问题=>形成新的提示词,再丢给大模型
2)核心代码:
加载向量:FAISS.load_local
初始化大模型:langchain_openai.ChatOpenAI
检索并生成:RetrievalQA.from_chain_type
以下为RAG聊天机器人核心代码 rag_chatbot.py
import json
import os
from langchain.vectorstores import FAISS
from langchain.chains import RetrievalQA
from langchain_openai import ChatOpenAI
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
from utils.data_loader import load_documents
from utils.text_splitter import split_text
from utils.embedding import get_embeddings
class RAGChatbot:
def __init__(self, data_dir='data', vectorstore_path='vectorstore'):
self.data_dir = data_dir
self.vectorstore_path = vectorstore_path
self.embeddings = get_embeddings()
if os.path.exists(self.vectorstore_path):
try:
self.vectorstore = FAISS.load_local(
self.vectorstore_path, self.embeddings, allow_dangerous_deserialization=True
)
print("Successfully loaded vector store from local")
except Exception as e:
print(f"Failed to load local vector store: {e}, will recreate")
self.documents = load_documents(self.data_dir)
self.texts = split_text(self.documents)
if not self.texts:
print("Warning: 'self.texts' list is empty. Please check document loading and splitting.")
return
self.vectorstore = FAISS.from_documents(self.texts, self.embeddings)
self.vectorstore.save_local(self.vectorstore_path)
else:
self.documents = load_documents(self.data_dir)
self.texts = split_text(self.documents)
if not self.texts:
print("Warning: 'self.texts' list is empty. Please check document loading and splitting.")
return
self.vectorstore = FAISS.from_documents(self.texts, self.embeddings)
self.vectorstore.save_local(self.vectorstore_path)
self.qa = self._init_qa_chain()
def _init_qa_chain(self):
with open('config.json', 'r') as f:
config = json.load(f)
llm_config = config['llm']['models'][config['llm']['default']]
api_key = llm_config.get('apikey')
model = llm_config.get('model')
temperature = llm_config.get('model_kwargs', {}).get('temperature', 0.7)
max_tokens = llm_config.get('model_kwargs', {}).get('max_length', 512)
# 添加 StreamingStdOutCallbackHandler 以支持流式输出
llm = ChatOpenAI(
base_url=llm_config.get('url'),
api_key=api_key,
model=model,
temperature=temperature,
max_tokens=max_tokens,
streaming=True,
# 这里先移除默认的回调,后面在 ask_stream 中自定义
callbacks=[]
)
retriever = self.vectorstore.as_retriever()
qa = RetrievalQA.from_chain_type(
llm=llm, chain_type="stuff", retriever=retriever
)
# 保存 llm 实例到类属性
self.llm = llm
return qa
def ask_stream(self, question, history):
context = ""
for item in history:
if item['role'] == 'user':
context += f"User: {item['content']}\n"
elif item['role'] == 'bot':
context += f"Bot: {item['content']}\n"
context += f"User: {question}\nBot: "
class CustomCallbackHandler(StreamingStdOutCallbackHandler):
def __init__(self):
super().__init__()
def on_llm_new_token(self, token: str, **kwargs) -> None:
print(f"Received token: {token}")
yield token
callback_handler = CustomCallbackHandler()
try:
response = self.qa.stream(
{"query": context},
callbacks=[callback_handler]
)
for chunk in response:
print(f"Received chunk: {chunk}",flush=True)
if 'result' in chunk:
answer_chunk = chunk['result']
for char in answer_chunk:
yield char
except Exception as e:
print(f"ask_stream 方法出现异常: {e}")
yield str(e)
6、最后一步,增加一个前端,then have fun
使用flask框架,新增一个前端,支持流式输出和多轮对话。
前端代码包括app.py和chat.html,代码太长,不便附在文中,需要者关注公众号获取。
注意事项
- 工程的目录结构要和上面的保持一致
2)记得在config.json里面配置好自己的apikey
3)启动服务:在工程根目录,执行 python .\frontend\app.py
4)启动之后,通过页面http://localhost:5000进行访问
根目录的app.py是不带前端的版本,提问通过cmd终端发起,前期调试程序的时候可以用。调通之后,可以删除。
最后吐槽一下,用langchain调用国产大模型。以及调试流式生成,真的是废了老鼻子劲了!强烈建议使用llama_index,会方便很多。
如何学习大模型 AI ?
由于新岗位的生产效率,要优于被取代岗位的生产效率,所以实际上整个社会的生产效率是提升的。
但是具体到个人,只能说是:
“最先掌握AI的人,将会比较晚掌握AI的人有竞争优势”。
这句话,放在计算机、互联网、移动互联网的开局时期,都是一样的道理。
我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。
我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。
这份完整版的大模型 AI 学习资料已经上传优快云,朋友们如果需要可以微信扫描下方优快云官方认证二维码免费领取【保证100%免费】


为什么要学习大模型?
我国在A大模型领域面临人才短缺,数量与质量均落后于发达国家。2023年,人才缺口已超百万,凸显培养不足。随着AI技术飞速发展,预计到2025年,这一缺口将急剧扩大至400万,严重制约我国AI产业的创新步伐。加强人才培养,优化教育体系,国际合作并进是破解困局、推动AI发展的关键。


大模型入门到实战全套学习大礼包
1、大模型系统化学习路线
作为学习AI大模型技术的新手,方向至关重要。 正确的学习路线可以为你节省时间,少走弯路;方向不对,努力白费。这里我给大家准备了一份最科学最系统的学习成长路线图和学习规划,带你从零基础入门到精通!

2、大模型学习书籍&文档
学习AI大模型离不开书籍文档,我精选了一系列大模型技术的书籍和学习文档(电子版),它们由领域内的顶尖专家撰写,内容全面、深入、详尽,为你学习大模型提供坚实的理论基础。

3、AI大模型最新行业报告
2025最新行业报告,针对不同行业的现状、趋势、问题、机会等进行系统地调研和评估,以了解哪些行业更适合引入大模型的技术和应用,以及在哪些方面可以发挥大模型的优势。

4、大模型项目实战&配套源码
学以致用,在项目实战中检验和巩固你所学到的知识,同时为你找工作就业和职业发展打下坚实的基础。

5、大模型大厂面试真题
面试不仅是技术的较量,更需要充分的准备。在你已经掌握了大模型技术之后,就需要开始准备面试,我精心整理了一份大模型面试题库,涵盖当前面试中可能遇到的各种技术问题,让你在面试中游刃有余。

适用人群

第一阶段(10天):初阶应用
该阶段让大家对大模型 AI有一个最前沿的认识,对大模型 AI 的理解超过 95% 的人,可以在相关讨论时发表高级、不跟风、又接地气的见解,别人只会和 AI 聊天,而你能调教 AI,并能用代码将大模型和业务衔接。
- 大模型 AI 能干什么?
- 大模型是怎样获得「智能」的?
- 用好 AI 的核心心法
- 大模型应用业务架构
- 大模型应用技术架构
- 代码示例:向 GPT-3.5 灌入新知识
- 提示工程的意义和核心思想
- Prompt 典型构成
- 指令调优方法论
- 思维链和思维树
- Prompt 攻击和防范
- …
第二阶段(30天):高阶应用
该阶段我们正式进入大模型 AI 进阶实战学习,学会构造私有知识库,扩展 AI 的能力。快速开发一个完整的基于 agent 对话机器人。掌握功能最强的大模型开发框架,抓住最新的技术进展,适合 Python 和 JavaScript 程序员。
- 为什么要做 RAG
- 搭建一个简单的 ChatPDF
- 检索的基础概念
- 什么是向量表示(Embeddings)
- 向量数据库与向量检索
- 基于向量检索的 RAG
- 搭建 RAG 系统的扩展知识
- 混合检索与 RAG-Fusion 简介
- 向量模型本地部署
- …
第三阶段(30天):模型训练
恭喜你,如果学到这里,你基本可以找到一份大模型 AI相关的工作,自己也能训练 GPT 了!通过微调,训练自己的垂直大模型,能独立训练开源多模态大模型,掌握更多技术方案。
到此为止,大概2个月的时间。你已经成为了一名“AI小子”。那么你还想往下探索吗?
- 为什么要做 RAG
- 什么是模型
- 什么是模型训练
- 求解器 & 损失函数简介
- 小实验2:手写一个简单的神经网络并训练它
- 什么是训练/预训练/微调/轻量化微调
- Transformer结构简介
- 轻量化微调
- 实验数据集的构建
- …
第四阶段(20天):商业闭环
对全球大模型从性能、吞吐量、成本等方面有一定的认知,可以在云端和本地等多种环境下部署大模型,找到适合自己的项目/创业方向,做一名被 AI 武装的产品经理。
- 硬件选型
- 带你了解全球大模型
- 使用国产大模型服务
- 搭建 OpenAI 代理
- 热身:基于阿里云 PAI 部署 Stable Diffusion
- 在本地计算机运行大模型
- 大模型的私有化部署
- 基于 vLLM 部署大模型
- 案例:如何优雅地在阿里云私有部署开源大模型
- 部署一套开源 LLM 项目
- 内容安全
- 互联网信息服务算法备案
- …
学习是一个过程,只要学习就会有挑战。天道酬勤,你越努力,就会成为越优秀的自己。
如果你能在15天内完成所有的任务,那你堪称天才。然而,如果你能完成 60-70% 的内容,你就已经开始具备成为一名大模型 AI 的正确特征了。
这份完整版的大模型 AI 学习资料已经上传优快云,朋友们如果需要可以微信扫描下方优快云官方认证二维码免费领取【保证100%免费】

8314

被折叠的 条评论
为什么被折叠?



