概览
在上一篇文章中,我们成功把 PDF 文档变成了向量,存进了 ChromaDB 这个"图书馆"。但这时候的数据库是"死"的------它只是一堆数字,不会说话。
今天,我们要给这个图书馆装上一颗"大脑"(Ollama + Qwen3.5),并聘请一位"图书管理员"(LangChain)。我们要完成 RAG 流程的最后一步:检索增强生成。
简单来说,我们要实现这样一个流程:
- 你提问。
- 管理员去图书馆找资料。
- 把资料递给大脑。
- 大脑读懂资料,给你写答案。
准备好了吗?我们要开始写代码了!
📦 准备工作:安装必要的库
在开始之前,确保你的 Python 环境中已经安装了所有需要的库。打开终端,运行以下命令:
bash
# 安装 LangChain 核心库和社区贡献的组件
pip install langchain langchain-community
# 安装 ChromaDB 向量数据库
pip install chromadb
# 安装 HuggingFace 的嵌入模型库
# 注意:虽然代码中用的是 langchain_community.embeddings,但底层依赖 sentence-transformers
pip install sentence-transformers
📜 完整代码:一键复制测试
为了方便大家直接运行,这里先附上完整代码 。你可以直接复制到一个 .py 文件中(例如 rag_app.py),确保 Ollama 已经启动且 ./rag_db 文件夹存在,即可直接运行。
python
import os
# 设置国内镜像,防止下载模型超时(国内开发必备!)
os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'
from langchain_community.llms import Ollama
from langchain.prompts import PromptTemplate
from langchain.chains import RetrievalQA
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceEmbeddings
# ======================
# 1. 初始化配置
# ======================
print("🚀 正在启动 RAG 系统...")
# 初始化 Embedding 模型(翻译官)
# 推荐使用 BGE 中文模型,效果比多语言模型更好!
embeddings = HuggingFaceEmbeddings(
model_name="BAAI/bge-base-zh-v1.5",
model_kwargs={'device': 'cpu'},
encode_kwargs={'normalize_embeddings': True}
)
# 连接向量数据库(图书管理员)
vectordb = Chroma(
persist_directory="./rag_db",
embedding_function=embeddings,
collection_name="employee_handbook"
)
# 初始化本地大模型(大脑)
llm = Ollama(
model="qwen3.5:9b",
base_url="http://localhost:11434",
temperature=0.1
)
# ======================
# 2. 构建 Prompt 模板
# ======================
prompt_template = """
你是专业的问答助手。请根据下方的【参考资料】回答用户的问题。
规则:
1. 只根据【参考资料】回答,不要利用你的外部知识。
2. 如果资料里没有答案,请直接回答"根据现有资料无法回答"。
3. 回答要简洁、准确。
【参考资料】:
{context}
【用户问题】:{question}
【助手回答】:
"""
prompt = PromptTemplate(
template=prompt_template,
input_variables=["context", "question"]
)
# ======================
# 3. 组装 RAG 流水线
# ======================
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=vectordb.as_retriever(search_kwargs={"k": 3}),
chain_type_kwargs={"prompt": prompt},
return_source_documents=True
)
print("✅ 系统组装完成,准备就绪!\n")
# ======================
# 4. 开始对话
# ======================
while True:
query = input("👤 我: ")
if query.lower() in ["exit", "quit", "退出"]:
print("👋 再见!")
break
try:
result = qa_chain.invoke({"query": query})
print(f"🤖 助手: {result['result']}")
# 打印来源(可选)
# print(f"📄 来源: {[doc.metadata.get('source') for doc in result['source_documents']]}")
except Exception as e:
print(f"❌ 出错了: {e}")
🛠️ 第一步:准备工具箱(导入模块)
代码虽然可以直接跑,但我们得知道手里拿的是什么工具。别被这些英文名吓到,我给它们每个人都安排了"职位":
python
from langchain_community.llms import Ollama
# 【职位:大脑接口】负责连接本地的 Ollama 软件,把问题发给 Qwen3.5。
from langchain.prompts import PromptTemplate
# 【职位:话术导演】负责把"资料"和"问题"打包成模型能听懂的指令。
from langchain.chains import RetrievalQA
# 【职位:项目经理】负责统筹全局,自动安排"检索-组装-回答"的流水线。
from langchain_community.vectorstores import Chroma
# 【职位:图书管理员】负责去 ChromaDB 数据库里把相关的文档片段找出来。
from langchain_community.embeddings import HuggingFaceEmbeddings
# 【职位:翻译官】负责把你的中文问题翻译成向量(数字),让数据库能听懂。
🗺️ 系统架构图:各模块如何协作
为了更直观地理解这些模块是如何串联起来的,我们可以画一个简单的架构图。想象一下,当用户提出一个问题时,数据是如何在各个组件之间流动的:
用户提问
│
▼
┌─────────────┐
│ 翻译官 │ (HuggingFaceEmbeddings)
│ (将问题转为向量) │
└─────────────┘
│
▼
┌─────────────┐
│ 图书管理员 │ (ChromaDB)
│ (根据向量检索资料) │
└─────────────┘
│
▼
┌─────────────┐
│ 话术导演 │ (PromptTemplate)
│ (组装资料和指令) │
└─────────────┘
│
▼
┌─────────────┐
│ 项目经理 │ (RetrievalQA Chain)
│ (协调整个流程) │
└─────────────┘
│
▼
┌─────────────┐
│ 大脑接口 │ (Ollama)
│ (生成最终答案) │
└─────────────┘
│
▼
用户得到回答
这个闭环清晰地展示了从用户提问到得到回答的全过程。每个模块各司其职,协同工作,最终实现了智能问答。
🧠 第二步:加载"大脑"和"图书馆"
这一步我们要初始化两个核心对象:一个是负责思考的 Ollama,一个是负责存资料的 Chroma。
python
# 1. 初始化 Embedding 模型(翻译官)
embeddings = HuggingFaceEmbeddings(
model_name="BAAI/bge-base-zh-v1.5",
model_kwargs={'device': 'cpu'},
encode_kwargs={'normalize_embeddings': True} # 归一化,提升相似度准确度
)
# 2. 连接向量数据库(图书管理员)
vectordb = Chroma(
persist_directory="./rag_db", # 你的数据库文件夹路径
embedding_function=embeddings, # 把刚才的翻译官交给他
collection_name="employee_handbook" # 集合名称,要和入库时一致
)
# 3. 初始化本地大模型(大脑)
llm = Ollama(
model="qwen3.5:9b",
base_url="http://localhost:11434",
temperature=0.1 # 温度设低一点,让模型严谨点,别乱编
)
这里有个细节:我们在 HuggingFaceEmbeddings 里加了 normalize_embeddings=True。这就像给向量做了个"标准化整容",能让后续的相似度计算更精准,这是一个提升检索效果的小技巧。
🎬 第三步:构建"话术导演"(Prompt 模板)
这是 RAG 的灵魂!如果 Prompt 写得不好,模型就会开始"自由发挥"(产生幻觉)。我们需要明确告诉它:只看资料,不许瞎编。
python
prompt_template = """
你是专业的问答助手。请根据下方的【参考资料】回答用户的问题。
规则:
1. 只根据【参考资料】回答,不要利用你的外部知识。
2. 如果资料里没有答案,请直接回答"根据现有资料无法回答"。
3. 回答要简洁、准确。
【参考资料】:
{context}
【用户问题】:{question}
【助手回答】:
"""
prompt = PromptTemplate(
template=prompt_template,
input_variables=["context", "question"]
)
这里的 {context} 和 {question} 是两个"坑位"。
{context}:LangChain 会自动把从数据库里搜到的资料填在这里。{question}:会自动把你输入的问题填在这里。
🔗 第四步:组装流水线(RetrievalQA)
万事俱备,现在要把它们串起来了。我们使用 LangChain 的 RetrievalQA 链,它就像一个项目经理,自动帮我们处理复杂的流程。
python
qa_chain = RetrievalQA.from_chain_type(
llm=llm, # 指定大脑
chain_type="stuff", # 模式:把所有搜到的资料"塞"进提示词里
retriever=vectordb.as_retriever(search_kwargs={"k": 3}), # 指定图书管理员,每次找3个最相关的片段
chain_type_kwargs={"prompt": prompt}, # 把写好的话术模板交给项目经理
return_source_documents=True # 开启这个,能看到答案出自哪篇文档(调试神器)
)
这里有一个关键参数 chain_type_kwargs={"prompt": prompt}。它的作用就是把刚才定义的"规矩"(Prompt 模板)真正安装到流水线上。如果不加这一行,模型就会用默认的方式回答,可能就不会遵守"不许瞎编"的指令了。
💬 第五步:开始对话
最后,我们写一个简单的循环,就可以像聊天一样问问题了。
python
print(" RAG 系统已就绪,请输入问题(输入 'exit' 退出):")
while True:
query = input("\n 我: ")
if query.lower() in ["exit", "quit", "退出"]:
break
try:
result = qa_chain.invoke({"query": query})
print(f" 助手: {result['result']}")
# 打印来源(可选)
# print(f" 来源: {[doc.metadata.get('source') for doc in result['source_documents']]}")
except Exception as e:
print(f" 出错了: {e}")
🕵️♂️ 深度答疑:关于 sentence-transformers 的疑问
疑问: 代码里用的是 HuggingFaceEmbeddings,为什么安装命令里要装 sentence-transformers?代码里好像没用到它啊?
解答: 这是一个非常棒的观察!这涉及到 Python 库的**"套娃"关系**。
- 表层(LangChain): 我们在代码里用的是
HuggingFaceEmbeddings,这是 LangChain 提供的一个**"外壳"**(接口)。它的作用是让 LangChain 能统一调用各种模型。 - 里层(实际干活):
HuggingFaceEmbeddings这个外壳里面,实际上调用的就是sentence-transformers这个库。
打个比方:
- 你买了一台**"智能音箱"**(LangChain)。
- 你想听音乐,你对音箱说"播放音乐"(调用
HuggingFaceEmbeddings)。 - 音箱内部其实是连接了**"网易云音乐"**(
sentence-transformers)来播放的。 - 你虽然没直接操作网易云音乐的 App,但如果没有安装它,音箱就放不出歌。
所以,虽然我们代码里没 import sentence_transformers,但它是 HuggingFaceEmbeddings 的底层依赖,必须安装,否则程序会报错提示找不到模块。
🚫 避坑指南:为什么你的代码会报错?
除了上面的依赖问题,大家最容易踩的另一个坑就是**"库混用"**。
错误现象:
很多教程会教你直接用 from sentence_transformers import SentenceTransformer。
如果你直接用这个模型对象传给 LangChain 的 Chroma,会报错!
原因:
- ChromaDB 原生库:它只认"数字列表"(向量)。你给它什么它存什么,所以直接跑入库脚本没问题。
- LangChain :它是一个框架,它要求
embedding_function必须是一个**"懂 LangChain 规矩"**的对象(即必须有embed_documents等方法)。 - SentenceTransformer :底层的模型对象只有
encode方法,听不懂 LangChain 的指令。
解决方案:
一定要用 LangChain 封装好的类!
- 推荐用:
from langchain_community.embeddings import HuggingFaceEmbeddings
这就好比 LangChain 给了你一个"三孔插座",你必须插"三脚插头"(封装类),不能直接拿"螺丝刀"(底层模型)硬捅。
📌 总结
恭喜你!跑通这段代码,你就真正理解了 RAG 的核心闭环:
- Embedding 把文字变成向量。
- VectorStore 存储并检索向量。
- Prompt 约束模型的行为。
- Chain 把一切自动化。
现在,你拥有了一个完全本地化、保护隐私、且能基于私有数据回答问题的 AI 助手!快去试试问它几个刁钻的问题吧!