# 构建本地 RAG 问答系统:Ollama + LangChain 完全指南
很多企业在用 AI 处理内部文档时,都会担心数据泄露的问题。把机密文件发给第三方 API,总让人觉得不踏实。通用大模型的知识有截止日期,还经常一本正经地胡说八道。商业 API 调多了,成本也是笔不小的开销。
这些问题都可以通过本地部署的 RAG 系统来解决。
## 什么是 RAG
RAG(Retrieval-Augmented Generation,检索增强生成)本质上是给大模型接上一个外部知识库。用户提问时,系统先从知识库中找出相关的文档片段,然后把问题和这些片段一起交给 LLM,让它基于真实材料来回答。
这样一来,模型能回答私有知识库中的内容,回答的准确性也更有保障。
本文用 Ollama 做本地 LLM 运行时,LangChain 串联整个流程,Chroma 作为轻量级向量数据库。整个系统跑在本地,不需要网络。
## 环境准备
Python 3.10 或更高版本。
“`bash
# 创建虚拟环境
python -m venv rag-env
source rag-env/bin/activate
# 安装核心依赖
pip install langchain langchain-community langchain-chroma
pip install ollama
pip install pypdf
pip install sentence-transformers
pip install beautifulsoup4
“`
## 步骤一:安装 Ollama
Ollama 支持 Linux、macOS 和 Windows。Linux 安装:
“`bash
curl -fsSL https://ollama.com/install.sh | sh
“`
拉取模型:
“`bash
ollama pull llama3
“`
查看已安装的模型:
“`bash
ollama list
“`
启动服务(默认端口 11434):
“`bash
ollama serve
“`
## 步骤二:加载文档
以 PDF 为例,用 LangChain 的加载器读取:
“`python
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
def load_documents(file_path: str):
loader = PyPDFLoader(file_path)
documents = loader.load()
# 分割成长度适中的 chunk
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=[“\n\n”, “\n”, ” “, “”]
)
chunks = text_splitter.split_documents(documents)
return chunks
# 加载多个文档
documents = []
for file in [“doc1.pdf”, “doc2.pdf”, “doc3.pdf”]:
chunks = load_documents(file)
documents.extend(chunks)
print(f”总共 {len(documents)} 个文档块”)
“`
## 步骤三:构建向量存储
Chroma 是本地向量数据库,安装和使用都很轻量。Embedding 模型选择 sentence-transformers:
“`python
from langchain_chroma import Chroma
from langchain_huggingface import HuggingFaceEmbeddings
embeddings = HuggingFaceEmbeddings(
model_name=”sentence-transformers/all-MiniLM-L6-v2″,
model_kwargs={device: cpu}
)
vector_store = Chroma.from_documents(
documents=documents,
embedding=embeddings,
persist_directory=”./chroma_db”
)
print(“向量存储创建成功”)
“`
## 步骤四:连接本地 LLM
LangChain 提供了 Ollama 的集成:
“`python
from langchain_ollama import OllamaLLM
llm = OllamaLLM(
model=”llama3″,
temperature=0.7,
base_url=”http://localhost:11434″
)
# 测试一下
response = llm.invoke(“你好,请介绍一下自己”)
print(response)
“`
## 步骤五:组装问答链
把检索和生成串起来:
“`python
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain.prompts import ChatPromptTemplate
system_prompt = “””你是一个专业的技术助手。请根据提供的上下文信息回答用户的问题。
如果上下文中没有相关信息,请明确告知用户你无法从提供的文档中找到答案。
请用中文回答问题。
上下文:{context}”””
prompt = ChatPromptTemplate.from_messages([
(“system”, system_prompt),
(“human”, “{input}”)
])
question_answer_chain = create_stuff_documents_chain(llm, prompt)
retrieval_chain = create_retrieval_chain(
vector_store.as_retriever(search_kwargs={“k”: 3}),
question_answer_chain
)
def ask_question(question: str):
result = retrieval_chain.invoke({“input”: question})
print(f”问题:{question}”)
print(f”回答:{result[answer]}”)
print(“-” * 50)
return result
ask_question(“本文档的主要内容是什么?”)
“`
## 步骤六:进阶优化
实际使用中可以加上这些优化:
“`python
# 1. 压缩检索结果,减少 token 消耗
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor
compressor = LLMChainExtractor.from_llm(llm)
compression_retriever = ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=vector_store.as_retriever()
)
# 2. 结果重排序,提升准确度
from langchain_community.cross_encoders import HuggingFaceCrossEncoder
cross_encoder = HuggingFaceCrossEncoder(
model_name=”BAAI/bge-reranker-base”
)
def rerank_results(query: str, docs: list, top_k: int = 3):
doc_pairs = [(query, doc.page_content) for doc in docs]
scores = cross_encoder.predict(doc_pairs)
ranked = sorted(zip(docs, scores), key=lambda x: x[1], reverse=True)
return [doc for doc, score in ranked[:top_k]]
“`
## 完整代码
整合后的完整示例:
“`python
#!/usr/bin/env python3
“””
本地 RAG 问答系统 – Ollama + LangChain + Chroma
“””
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_chroma import Chroma
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_ollama import OllamaLLM
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain.prompts import ChatPromptTemplate
class LocalRAGSystem:
def __init__(self, model_name: str = “llama3″):
self.llm = OllamaLLM(model=model_name, temperature=0.3)
self.embeddings = HuggingFaceEmbeddings(
model_name=”sentence-transformers/all-MiniLM-L6-v2″,
model_kwargs={device: cpu}
)
self.vector_store = None
self.chain = None
def load_documents(self, file_paths: list):
documents = []
for file_path in file_paths:
if file_path.endswith(.pdf):
loader = PyPDFLoader(file_path)
documents.extend(loader.load())
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50
)
splits = text_splitter.split_documents(documents)
self.vector_store = Chroma.from_documents(
documents=splits,
embedding=self.embeddings,
persist_directory=”./data/chroma_db”
)
print(f”已处理 {len(splits)} 个文档块”)
return self
def build_chain(self):
system_prompt = “””你是一个专业的技术文档助手。
基于以下上下文信息回答用户的问题。如果无法从上下文中找到答案,请如实说明。
{context}”””
prompt = ChatPromptTemplate.from_messages([
(“system”, system_prompt),
(“human”, “{input}”)
])
question_answer_chain = create_stuff_documents_chain(self.llm, prompt)
self.chain = create_retrieval_chain(
self.vector_store.as_retriever(search_kwargs={“k”: 3}),
question_answer_chain
)
return self
def query(self, question: str) -> str:
if not self.chain:
raise ValueError(“请先构建问答链”)
result = self.chain.invoke({“input”: question})
return result[answer]
if __name__ == “__main__”:
rag = LocalRAGSystem(model_name=”llama3″)
rag.load_documents([“/path/to/your/document.pdf”])
rag.build_chain()
while True:
question = input(“\n请输入问题(输入 q 退出): “)
if question.lower() == q:
break
answer = rag.query(question)
print(f”\n回答: {answer}”)
“`
## 运行效果
“`
已处理 150 个文档块
请输入问题(输入 q 退出): 什么是RAG技术?
回答: RAG(Retrieval-Augmented Generation,检索增强生成)是一种将外部知识库
与大语言模型结合的技术。它通过以下步骤工作:首先,用户的查询会被转换为向量表示;
然后,系统会在向量数据库中检索与查询最相关的文档片段;最后,将检索到的上下文信息
与原始问题一起发送给 LLM,生成最终的回答。这种方法可以有效解决 LLM 知识过时、
产生幻觉等问题,特别适用于需要处理大量私有文档的企业场景。
请输入问题(输入 q 退出): 如何部署本地 LLM?
回答: 部署本地 LLM 有多种方式:
1. 使用 Ollama:最简单的方式,支持一键部署
2. 使用 LM Studio:图形界面友好
3. 使用 vLLM:适合生产环境,性能优秀
4. 使用 llama.cpp:资源占用低,支持量化
本文主要介绍使用 Ollama 的部署方式,因为它配置简单,主流模型支持完善。
请输入问题(输入 q 退出): q
“`
## 总结
这个方案的核心优势:
1. **数据不出本地** — 所有文档和向量数据都存在自己机器上
2. **没有按次收费** — 硬件一次性投入,之后随便用
3. **可扩展** — 支持 PDF、Word、Markdown 等多种格式,可以换不同的 Embedding 模型和 LLM
4. **可定制** — 提示词、检索参数、模型都可以根据需求调整
后续优化方向包括:换 BGE 这样的更强 Embedding 模型、用量化版模型省显存、把对话历史加进去做成多轮问答等。本地大模型会越来越好用,个人和团队搭建私有知识助手会越来越简单。