Qwen2.5-1.5B开源大模型教程:集成RAG插件扩展本地知识库能力
Qwen2.5-1.5B开源大模型教程:集成RAG插件扩展本地知识库能力
1. 引言:从通用对话到专业问答
如果你已经体验过在本地部署一个轻量级的AI对话助手,比如基于Qwen2.5-1.5B模型构建的聊天应用,你可能会发现一个有趣的现象:它能和你聊天气、写诗、解释概念,但当你问它一些非常具体、专业或者需要最新信息的问题时,它常常会“一本正经地胡说八道”。
这不是模型的错。像Qwen2.5-1.5B这样的通用大语言模型,它的知识主要来源于训练时“吃”进去的公开数据,截止到一个固定的时间点。它不知道你公司内部的规章制度,不了解你个人文档库里的技术方案,也无法获取今天早上刚发布的行业新闻。
那么,有没有办法让这个已经部署在本地、保护隐私的AI助手,瞬间变得“博学”起来,能够回答基于你特定知识库的问题呢?
答案是肯定的。这就是我们今天要做的:为你的本地Qwen2.5-1.5B对话助手,集成一个名为RAG(检索增强生成)的“外挂大脑”。通过本教程,你将学会如何让AI在回答前,先到你的专属知识库(比如一堆PDF、TXT文档)里查找相关信息,然后结合找到的资料,生成更准确、更专业的回答。
整个过程完全在本地运行,你的私人文档不会被上传到任何云端。让我们开始吧。
2. RAG是什么?为什么你的本地AI需要它?
在动手之前,我们先花几分钟,用人话把RAG这件事讲明白。这能帮你更好地理解我们每一步在做什么。
2.1 大模型的“记忆力”困境
你可以把Qwen2.5-1.5B这样的模型想象成一个博览群书、但记忆有些模糊的天才。它读过海量的书(训练数据),对通用知识有很好的理解,能进行逻辑推理和创造性写作。但是:
- 它记不住所有细节:模型参数有限,不可能记住训练时见过的每一句话。
- 它的知识会过时:训练完成后,它的知识就定格了,无法自动更新。
- 它不了解你的隐私:它不可能知道你电脑里那份未公开的项目报告写了什么。
所以,当你问“根据我上周提交的‘XX项目复盘报告.pdf’,我们下一步的优化重点是什么?”时,它只能根据“项目复盘”、“优化重点”这些通用词汇来编造一个听起来合理的答案,而不是基于你报告里的真实内容。
2.2 RAG:给AI配一个“实时秘书”
RAG(Retrieval-Augmented Generation,检索增强生成)就是为了解决这个问题。它的工作流程很像一个聪明的秘书:
- 你提问: “我们项目的下一步优化重点是什么?”
- 秘书检索: 秘书(检索器)立刻跑去你的文件柜(向量知识库),快速翻阅所有相关文档(你的项目报告、会议纪要等),找出和“优化重点”最相关的几段内容。
- 秘书汇报: 秘书把找到的这些关键段落,连同你的原始问题,一起交给老板(大语言模型)。
- 老板回答: 老板(模型)基于秘书提供的具体资料,结合自己的通用知识,给你一个 grounded(有依据)的、准确的回答。
这个过程中,老板(大模型)本身的知识没有被修改,它只是获得了一份“参考资料”。RAG的核心价值在于:将模型庞大的通用理解能力,与你私有的、最新的、具体的知识结合了起来。
2.3 本地RAG的优势
为什么我们要在本地做这件事?结合之前部署的Qwen2.5-1.5B本地助手,优势显而易见:
- 数据绝对安全:你的文档从被读取、处理到被检索,全程都在你自己的机器上,没有互联网传输。
- 回答质量高:回答基于你的真实资料,减少模型“幻觉”(胡编乱造)。
- 成本极低:利用本地已有的轻量模型和算力,无需为调用大型商用API付费。
- 定制化强:知识库完全由你掌控,可以随时增删改查,让AI助手服务于你的专属领域。
接下来,我们就一步步实现这个“AI秘书系统”。
3. 搭建你的本地知识库:文档处理与向量化
RAG系统的第一步是构建知识库。我们不能直接把一堆PDF、Word文档扔给AI,它看不懂。我们需要把这些非结构化的文档,转换成一种计算机和AI都能高效“理解”和“查找”的格式——向量(Embeddings)。
3.1 核心组件介绍
我们需要用到几个关键的Python库:
- LangChain:一个用于构建大模型应用的流行框架,它把文档加载、文本分割、向量化、检索这些繁琐步骤都封装好了,我们直接调用就行,大大简化开发。
- Chroma:一个轻量级、易用的开源向量数据库。我们可以把它想象成一个专门存储和检索“向量”的特殊数据库。它会在本地运行,存放我们所有文档的向量化结果。
- Sentence-Transformers:一个用于生成文本向量的模型库。我们用它来把一段段文本转换成数学向量。
3.2 步骤一:准备环境与文档
首先,确保你的Python环境已经安装了必要的库。如果你是从零开始,可以创建一个新的虚拟环境。
# 安装核心库
pip install langchain langchain-community chromadb sentence-transformers pypdf
# pypdf 用于读取PDF文档
# 如果你还需要处理Word、PPT等,可以安装 python-docx, pptx 等
假设你的文档都放在一个名为 my_docs 的文件夹里,里面可能有 project_report.pdf、meeting_notes.txt、requirements.docx 等文件。
3.3 步骤二:加载与分割文档
使用LangChain来加载和分割文档。分割的目的是因为大模型一次能处理的文本长度有限(Qwen2.5-1.5B的上下文长度是8192 tokens),我们需要把长文档切成语义相对完整的小块。
from langchain_community.document_loaders import DirectoryLoader, PyPDFLoader, TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 1. 加载文档
# 指定你的文档目录
documents_path = "./my_docs"
# 创建加载器,自动识别.pdf和.txt文件
loader = DirectoryLoader(
documents_path,
glob="**/*.pdf", # 加载所有pdf
loader_cls=PyPDFLoader, # 使用PDF加载器
)
# 如果你还有txt文件,可以再加载一次,或者使用多类型加载器
txt_loader = DirectoryLoader(
documents_path,
glob="**/*.txt",
loader_cls=TextLoader,
)
# 加载所有文档
pdf_docs = loader.load()
txt_docs = txt_loader.load()
all_docs = pdf_docs + txt_docs
print(f"成功加载了 {len(all_docs)} 个文档。")
# 2. 分割文本
# 创建一个文本分割器
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每个文本块的最大字符数(约等于150-200个词)
chunk_overlap=100, # 块与块之间的重叠字符数,避免语义被切断
length_function=len,
separators=["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""] # 按这些分隔符优先切割
)
# 执行分割
split_docs = text_splitter.split_documents(all_docs)
print(f"文档被分割成了 {len(split_docs)} 个文本块。")
3.4 步骤三:向量化并存入数据库
现在,我们把分割好的文本块转换成向量,并存入Chroma向量数据库。
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import Chroma
# 1. 选择嵌入模型(用于生成向量)
# 我们选用一个轻量且效果不错的开源模型
embedding_model = HuggingFaceEmbeddings(
model_name="all-MiniLM-L6-v2", # 这是一个轻量级的句子转换模型
model_kwargs={'device': 'cpu'}, # 如果没有GPU,就用CPU,速度尚可
encode_kwargs={'normalize_embeddings': True} # 标准化向量,有利于相似度计算
)
# 2. 创建向量数据库
# 指定一个持久化目录,这样下次启动就不需要重新处理文档了
persist_directory = "./chroma_db"
# 将分割后的文档转换为向量并存储
vectordb = Chroma.from_documents(
documents=split_docs,
embedding=embedding_model,
persist_directory=persist_directory
)
# 持久化保存到磁盘
vectordb.persist()
print(f"向量知识库已创建并保存至:{persist_directory}")
到这里,你的本地知识库就建好了!chroma_db 文件夹里保存了你所有文档的向量化索引。以后新增文档,只需要重复加载、分割、添加到数据库的步骤即可。
4. 集成RAG到Qwen2.5-1.5B对话流
知识库准备好了,现在我们要修改之前基于Streamlit的Qwen2.5-1.5B聊天应用,让它在回答前先进行检索。
4.1 修改后的应用架构
原来的流程是:用户问题 -> 模型 -> 回答。 新的RAG流程是:用户问题 -> 从知识库检索相关片段 -> 将“问题+相关片段”组合成提示 -> 模型 -> 回答。
4.2 核心代码集成
我们需要修改之前的Streamlit应用代码。以下是关键部分的代码展示:
import streamlit as st
from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline
import torch
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import Chroma
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
# --- 1. 初始化设置(只需运行一次)---
@st.cache_resource
def load_components():
"""加载模型、分词器、向量数据库和RAG链"""
print("🚀 正在加载组件...")
# 1.1 加载Qwen模型和分词器 (沿用之前的代码)
MODEL_PATH = "/root/qwen1.5b"
tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
MODEL_PATH,
torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
device_map="auto",
trust_remote_code=True
)
# 1.2 加载我们之前创建好的向量数据库
embedding_model = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
persist_directory = "./chroma_db"
vectordb = Chroma(
persist_directory=persist_directory,
embedding_function=embedding_model
)
# 1.3 创建一个检索器,从向量库中找出最相关的4个文本块
retriever = vectordb.as_retriever(search_kwargs={"k": 4})
# 1.4 定义一个提示词模板,告诉模型如何利用检索到的上下文
# 这是RAG效果好坏的关键!
prompt_template = """
请根据以下提供的上下文信息来回答问题。如果上下文信息不足以回答问题,请根据你自身的知识进行回答,并说明这一点。
上下文信息:
{context}
问题:{question}
请给出专业、准确的回答:
"""
PROMPT = PromptTemplate(
template=prompt_template,
input_variables=["context", "question"]
)
# 1.5 创建RAG链,将检索器、模型和提示模板连接起来
# 注意:这里我们使用LangChain的HuggingFacePipeline来包装我们的模型
qa_chain = RetrievalQA.from_chain_type(
llm=model, # 这里需要适配,见下方说明
chain_type="stuff", # 最简单的方式,将所有检索到的上下文塞进提示词
retriever=retriever,
chain_type_kwargs={"prompt": PROMPT},
return_source_documents=True # 返回检索到的源文档,便于调试
)
print("✅ 所有组件加载完毕!")
return model, tokenizer, qa_chain
# --- 说明:模型适配问题 ---
# LangChain的RetrievalQA期望的`llm`是一个符合其BaseLanguageModel接口的对象。
# 我们直接加载的transformers模型不符合这个接口。
# 解决方案A(推荐,更简单):使用HuggingFacePipeline包装
from langchain_huggingface import HuggingFacePipeline
from transformers import pipeline as hf_pipeline
# 在load_components函数内,创建模型后,这样包装:
text_gen_pipeline = hf_pipeline(
"text-generation",
model=model,
tokenizer=tokenizer,
device_map="auto",
max_new_tokens=1024,
temperature=0.7,
top_p=0.9,
do_sample=True,
)
llm_for_langchain = HuggingFacePipeline(pipeline=text_gen_pipeline)
# 然后在创建qa_chain时,使用 llm=llm_for_langchain
# --- 2. Streamlit 应用界面 ---
def main():
st.title("🧠 Qwen2.5-1.5B 本地智能助手 (RAG增强版)")
# 加载组件
model, tokenizer, qa_chain = load_components()
# 初始化会话状态,保存聊天历史
if "messages" not in st.session_state:
st.session_state.messages = []
# 显示聊天历史
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
# 聊天输入框
if prompt := st.chat_input("你好,我是Qwen,请问有什么可以帮您?"):
# 添加用户消息到历史
st.session_state.messages.append({"role": "user", "content": prompt})
with st.chat_message("user"):
st.markdown(prompt)
# 生成AI回复
with st.chat_message("assistant"):
message_placeholder = st.empty() # 先占个位,用于流式输出
full_response = ""
# 关键步骤:调用RAG链获取答案
try:
# 这里qa_chain返回一个字典,包含答案和源文档
result = qa_chain.invoke({"query": prompt})
rag_answer = result["result"]
source_docs = result["source_documents"]
# 模拟流式输出效果
for chunk in rag_answer:
full_response += chunk
message_placeholder.markdown(full_response + "▌")
message_placeholder.markdown(full_response)
# (可选)在侧边栏显示检索到的来源,增强可信度
with st.sidebar.expander("📚 本次回答参考了以下文档片段"):
for i, doc in enumerate(source_docs):
st.caption(f"片段 {i+1}:")
st.text(doc.page_content[:200] + "...") # 预览前200字符
st.divider()
except Exception as e:
full_response = f"抱歉,处理请求时出错了: {e}"
message_placeholder.markdown(full_response)
# 添加AI回复到历史
st.session_state.messages.append({"role": "assistant", "content": full_response})
# 侧边栏:清空对话按钮
with st.sidebar:
st.header("控制面板")
if st.button("🧹 清空对话历史"):
st.session_state.messages = []
st.rerun()
st.info("💡 提示:助手现在会优先从您本地的知识库中寻找答案。")
if __name__ == "__main__":
main()
4.3 代码关键点解析
- 加载组件:我们使用
st.cache_resource缓存模型、向量数据库和RAG链,避免每次交互都重新加载,极大提升响应速度。 - RAG链(RetrievalQA):这是LangChain提供的一个高级接口,它把检索、组合提示、调用模型、解析输出这几个步骤打包成了一个简单的
invoke调用。 - 提示词模板:我们定义了一个模板,明确告诉模型:“请根据下面的上下文信息回答问题。” 这能有效引导模型基于我们提供的资料生成答案,而不是自己瞎编。
- 流式输出与来源显示:为了更好的体验,我们模拟了流式输出。同时,在侧边栏展示了AI回答所参考的具体文档片段,这让回答更可信、可追溯。
5. 效果对比与进阶优化
完成集成后,启动你的Streamlit应用,体验一下增强后的AI助手。
5.1 效果对比示例
假设你的知识库里有一份 员工手册.pdf,里面规定了年假制度。
-
没有RAG时:
- 你问:“我们公司的年假有多少天?”
- AI可能回答:“不同公司的年假制度不同,通常根据工龄在5到15天之间……”(一个通用但可能错误的回答)
-
有RAG时:
- 你问:“我们公司的年假有多少天?”
- 检索器从
员工手册.pdf中找到了相关段落:“本公司规定,员工累计工作满1年,年假5天;满10年,年假10天……” - AI结合这段上下文回答:“根据公司规定,您的年假天数与工龄挂钩。工龄满1年可享受5天年假,满10年可享受10天年假……”(一个准确、具体的回答)
5.2 可能遇到的问题与优化
-
检索不到相关内容:如果问题太模糊或知识库没有相关文档,检索器可能返回不相关的片段。可以尝试:
- 优化检索:调整
search_kwargs={"k": 4}中的k值,增加检索数量。 - 优化分割:调整文本分割的
chunk_size和chunk_overlap,使文本块语义更完整。 - 优化提问:引导用户问得更具体。
- 优化检索:调整
-
回答仍包含幻觉:即使提供了上下文,模型有时也会忽略或曲解。可以:
- 强化提示词:在提示词模板中更严厉地要求“必须基于上下文”,例如:“请严格只根据以下上下文回答,如果上下文未提及,请直接说‘根据现有资料无法回答’。”
- 使用更好的嵌入模型:可以尝试更大的句子嵌入模型,如
all-mpnet-base-v2,但需要更多计算资源。
-
处理长文档或复杂问答:
- 尝试不同的Chain类型:我们用的是
chain_type="stuff",它简单地把所有检索到的上下文拼在一起。对于非常多的上下文,可能会超出模型长度限制。可以尝试"map_reduce"或"refine"等更复杂的链类型,它们能处理更长的文档。
- 尝试不同的Chain类型:我们用的是
6. 总结
通过本教程,我们完成了一次从0到1的升级,将一个通用的本地对话AI,改造成为一个具备私有知识库检索能力的专业助手。回顾一下核心步骤:
- 理解需求:认识到通用大模型在专业、实时、私有知识上的局限性,引入RAG解决方案。
- 构建知识库:使用LangChain和Chroma,将你的本地文档(PDF、TXT等)进行加载、分割、向量化并存储,建立了一个可快速检索的本地向量数据库。
- 集成RAG流程:修改原有的Streamlit应用,在用户提问后,先调用检索器从向量库中查找相关文档片段,然后将这些片段与问题一起构造成提示词,再交给Qwen2.5-1.5B模型生成最终答案。
- 体验与优化:看到了RAG带来的回答准确性的显著提升,并了解了如何进一步调试和优化检索效果。
这套方案的魅力在于它的灵活性和隐私性。你的知识库可以随时更新,放入最新的市场报告、产品文档、代码规范,你的AI助手就能立刻“学会”这些新知识。所有这一切,都在你的本地计算机上安全地运行。
现在,你的Qwen2.5-1.5B不再只是一个聊天伙伴,它已经成为了一个能够深度查阅你私人文档库的智能研究助理。动手试试,让它为你的学习和工作带来真正的效率革命吧。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。
更多推荐



所有评论(0)