【大模型实战】告别幻觉!手把手用 LangChain + Ollama + FAISS 打造你的本地知识库问答系统(全程源码解析)
导语:你是否曾想让大模型读懂你私有的 PDF、TXT 文档,却又担心数据泄露?你是否受够了通用大模型的“幻觉”,渴望一个能基于你指定材料精准回答的专家?本文将带你从零开始,利用 LangChain 框架,结合 Ollama 本地部署的国产大模型(如 Qwen2),搭建一个完全运行在本地的 RAG(检索增强生成)知识库问答系统。全程干货,附完整源码,助你轻松踏入 AI 应用开发的大门!
一、为什么你需要 RAG ?
大语言模型(LLM)虽然强大,但存在两个致命短板:
-
知识截止:训练数据停留在过去,无法回答最新问题。
-
幻觉问题:对于不懂的内容,会一本正经地胡说八道,这在企业场景中是灾难性的。
RAG (Retrieval-Augmented Generation) 正是解决这两大痛点的银弹。它的核心思想非常简单:先检索,再增强,后生成。
当用户提问时,系统会先从你的私有知识库中精准检索出最相关的文档片段,然后将这些片段作为“参考资料”连同原问题一起塞给大模型,强制模型基于这些资料进行回答。这样既利用了 LLM 的强大理解与生成能力,又牢牢锁定了答案的边界,大大减少了幻觉。
今天,我们就用目前最主流的框架 LangChain,打造一个 100% 本地运行的 RAG 系统。
二、技术栈选型与环境准备
为了做到完全免费、数据不出本机,我们选用以下全链路开源/本地方案:
-
LLM 与 Embedding 模型:Ollama 本地部署
qwen2:7b(大语言模型)和nomic-embed-text(文本转向量模型)。 -
框架:LangChain(负责串联检索与生成流程)。
-
向量数据库:FAISS(Meta 开源的高效向量检索库)。
-
文档加载:PyPDF2, unstructured 等。
-
UI 界面:Gradio 或 Streamlit(本文提供 Streamlit 版本的轻量代码)。
2.1 安装 Ollama 并拉取模型
首先前往 Ollama 官网 下载并安装客户端。安装完成后,打开终端拉取所需模型:
bash
复制
下载
# 拉取通义千问 7B 模型(性能平衡,对中文友好) ollama pull qwen2:7b # 拉取轻量级 Embedding 模型,用于将文本转换为向量 ollama pull nomic-embed-text
验证模型是否就绪:
bash
复制
下载
ollama list
2.2 安装 Python 依赖库
建议使用 Conda 创建虚拟环境,然后安装以下核心库(本文写作时基于最新版):
bash
复制
下载
pip install langchain langchain-community langchain-ollama faiss-cpu pypdf unstructured streamlit
提示:unstructured 加载复杂文档依赖较多,如果仅需处理简单 TXT 或 PDF 纯文本,也可使用 PyPDFLoader,更轻量。
三、系统搭建核心步骤
我们将整个流程拆解为四个模块:
-
文档加载与智能切分
-
向量化存储(索引构建)
-
检索增强链组装
-
Streamlit 前端交互
3.1 第一步:文档加载与智能切分
良好的切分是 RAG 成功的基石。切分太短会丢失上下文,太长则检索精度下降。我们使用 LangChain 的 RecursiveCharacterTextSplitter,它会按照段落、句子、字符的优先级递归拆分。
python
复制
下载
from langchain_community.document_loaders import PyPDFLoader, TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
def load_and_split_documents(directory_path: str):
"""
加载指定目录下的 PDF 和 TXT 文件,并切分为文档块。
"""
documents = []
# 遍历目录,根据文件类型选用不同的加载器
import os
for file in os.listdir(directory_path):
file_path = os.path.join(directory_path, file)
if file.endswith(".pdf"):
loader = PyPDFLoader(file_path)
documents.extend(loader.load())
elif file.endswith(".txt"):
loader = TextLoader(file_path, encoding='utf-8')
documents.extend(loader.load())
# 文本切分:chunk_size=500,chunk_overlap=50 是实践中常用的折中值
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""]
)
chunks = text_splitter.split_documents(documents)
print(f"共加载 {len(documents)} 个文档,切分为 {len(chunks)} 个块。")
return chunks
3.2 第二步:向量化与向量库构建
利用 Ollama 的 embedding 模型将文本块转为向量,存入 FAISS。LangChain 提供了极其简便的接口。
python
复制
下载
from langchain_ollama import OllamaEmbeddings
from langchain_community.vectorstores import FAISS
# 初始化 Embedding 模型
embedding_model = OllamaEmbeddings(
model="nomic-embed-text", # 与上面 pull 的模型名一致
base_url="http://localhost:11434" # Ollama 本地服务地址
)
def create_vector_store(chunks, persist_path="./faiss_index"):
"""
将文档块向量化,并保存本地 FAISS 索引。
"""
# 直接从文档列表构建 FAISS 向量库
vectorstore = FAISS.from_documents(
documents=chunks,
embedding=embedding_model
)
# 持久化到本地,下次可直接加载,无需重新向量化
vectorstore.save_local(persist_path)
print("向量库已构建并保存。")
return vectorstore
# 快速加载已有索引的函数
def load_vector_store(persist_path="./faiss_index"):
return FAISS.load_local(persist_path, embedding_model, allow_dangerous_deserialization=True)
注意:allow_dangerous_deserialization=True 仅在加载自己信任的索引时使用。
3.3 第三步:组装 RAG 问答链
这是灵魂所在。我们使用 LangChain 的 检索器 (Retriever) 和 提示模板 (Prompt Template) 来构建 Chain。
python
复制
下载
from langchain_ollama import ChatOllama
from langchain.prompts import ChatPromptTemplate
from langchain.schema.runnable import RunnablePassthrough
from langchain.schema.output_parser import StrOutputParser
# 初始化本地千问模型
llm = ChatOllama(
model="qwen2:7b",
temperature=0.1, # 偏保守,降低幻觉
base_url="http://localhost:11434"
)
# 自定义 Prompt 模板:明确要求模型仅基于上下文回答,不知道就说不知道
prompt_template = """
你是一个严谨的文档知识助手。请仅根据以下【上下文】中的信息回答用户问题。
如果你从上下文中找不到足够的信息,请直接回答“根据提供的文档,我无法回答该问题”,不要编造。
【上下文】:
{context}
【用户问题】:
{question}
【回答】:
"""
prompt = ChatPromptTemplate.from_template(prompt_template)
def build_rag_chain(vectorstore):
"""
构建 RAG 链:检索 → 填充 Prompt → LLM 生成
"""
# 创建检索器,k=4 表示每次返回最相关的4个文本块
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
# 定义链式处理逻辑
rag_chain = (
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser() # 直接输出字符串结果
)
return rag_chain
链的工作流解释:
-
用户输入
question字符串。 -
RunnablePassthrough()原样传递question,同时触发retriever通过相似度搜索获取context。 -
两者一起填充进
prompt模板。 -
完整的 Prompt 发送给
ChatOllama模型。 -
StrOutputParser将模型返回的对象解析成纯文本。
3.4 第四步:用 Streamlit 搭建轻量 UI
为了让系统更好用,我们花十分钟搭建一个网页交互界面。
python
复制
下载
import streamlit as st
# ---- 初始化向量库和 RAG 链(使用缓存避免重复加载) ----
@st.cache_resource
def init_system():
# 尝试加载已保存的索引,若不存在则提示先构建
try:
vectorstore = load_vector_store("./faiss_index")
print("加载已有索引成功。")
except:
st.error("未找到向量索引!请先运行构建索引的脚本。")
st.stop()
rag_chain = build_rag_chain(vectorstore)
return rag_chain
st.set_page_config(page_title="本地知识库问答", page_icon="📚")
st.title("📚 基于本地文档的 RAG 智能问答")
st.markdown("技术栈:Ollama + LangChain + FAISS + Streamlit")
# 初始化系统
with st.spinner("正在加载模型和索引,首次加载可能需要几秒钟..."):
rag_chain = init_system()
# 聊天输入框
query = st.text_input("请输入你的问题:", placeholder="例如:这份合同的有效期是多久?")
if query:
with st.chat_message("user"):
st.write(query)
with st.chat_message("assistant"):
with st.spinner("思考中..."):
# 调用 RAG 链,使用 invoke 获取回答
answer = rag_chain.invoke(query)
st.write(answer)
运行你的应用:
bash
复制
下载
streamlit run app.py
浏览器会自动打开 http://localhost:8501,一个完全本地运行的知识库问答系统就诞生了!
https://img-blog.csdnimg.cn/direct/2b3c4d5e6f7g8h9i0j1k.png
(图:Streamlit 问答界面效果示意)
四、进阶优化与避坑指南
4.1 检索质量优化
-
元数据过滤:当文档有类别、时间等属性时,可在检索时使用
filter参数精准缩小范围。 -
混合检索:结合关键词(BM25)与向量检索,覆盖精确匹配和语义匹配,LangChain 有
BM25Retriever。 -
重排序:检索回 TopK 个片段后,用更精细的 CrossEncoder 模型重新打分排序,提升最相关片段的顺位。
4.2 大模型回答调优
-
调整
temperature:事实问答类设为 0~0.2,创意生成类可适当调高。 -
Prompt 工程:一定要在 Prompt 中加上“如果你不知道,就明确说不知道”之类的强制约束,这是对抗幻觉最直接有效的方式。
-
多轮对话:若需支持记忆,可集成
ConversationBufferMemory或使用 LangChain 的RunnableWithMessageHistory。
4.3 性能与资源
-
Ollama 量化:默认拉取的可能是 4-bit 量化版,资源占用很低,普通笔记本也能运行 7B 模型。
-
索引持久化:切记将向量库
save_local,避免每次重启都重新切片、向量化。
五、完整源码与总结
本文的所有代码均已整理,核心文件包含:
-
build_index.py:文档切分、向量化、存储索引。 -
app.py:Streamlit 前端与 RAG 链调用。
你可以这样组织项目结构:
text
复制
下载
rag_local_project/ ├── docs/ # 放入你的PDF、TXT文档 ├── faiss_index/ # 自动生成的向量库文件夹 ├── build_index.py └── app.py
通过这个项目,你不仅掌握了 RAG 的核心理论,还落地了一套可扩展的本地知识库底座。在此基础上,你可以轻松接入更多文档类型(Word、Markdown)、替换更强大的模型(如 Qwen2.5 或 Llama3),甚至部署到企业内部服务器。
大模型时代的魅力在于,你不再仅仅是技术的使用者,而是可以亲手创造出符合自己需求的 AI 应用。 如果你在复现过程中遇到任何问题,欢迎在评论区留言,我会一一解答。如果本文对你有帮助,不要吝啬你的 点赞、收藏,这是对我最大的鼓励!
更多推荐



所有评论(0)