# 用 LangChain + Ollama 在本地跑一个 RAG 问答系统
你有没有遇到过这种情况:电脑里存了几百份文档,想找某个具体信息却只能用 Ctrl+F 一个个搜?复制一大段给 AI,结果它根本不知道你在说什么。
这些问题可以用 RAG(检索增强生成)来解决。简单说就是让 AI 在回答问题前,先去你的文档里找相关信息,然后把找到的内容一起交给 AI 处理。这样 AI 就能基于你真正的知识库来回答,而不是胡编乱造。
今天来手把手教你搭建一个本地运行的 RAG 系统,全程只需要 Python 和 Ollama,不用任何云服务,不用付钱。
## 背景
大语言模型有两个天生的问题:一是知识截止日期,训练完成后它就不知道新东西了;二是它不知道你的私有数据,比如公司内部文档、个人笔记等。
传统解决办法是微调(Fine-tuning),但成本太高了——需要显卡、需要大量数据、需要重新训练。RAG 提供了另一个思路:不修改模型本身,让它在回答问题时先”查阅”你的文档。
2024 年之后,RAG 成了 AI 应用的标配。但大多数 RAG 方案都要依赖 OpenAI 的 API,要么收费,要么有隐私风险。这篇文章教你如何在本地跑一个完全私有的 RAG 系统。
## 目标
我们的目标是:构建一个本地问答系统,用户提问后,系统会:
1. 在指定的知识库(本地文件夹)中搜索相关内容
2. 把找到的相关内容整理成上下文
3. 把问题连同上下文一起交给本地运行的 LLM
4. 返回基于知识库的回答
技术要求:
– 全部在本地运行,不依赖外部 API
– 支持 PDF、TXT、Markdown 等常见文档格式
– 使用 Embedding 模型把文档转为向量
– 使用向量相似度搜索找到相关内容
– 使用 Ollama 运行 Qwen 等开源 LLM
## 步骤
### 1. 环境准备
确保安装了 Python 3.10+。然后创建项目目录并安装依赖:
“`bash
mkdir -p rag-demo && cd rag-demo
pip install langchain langchain-community langchain-ollama ollama pypdf python-dotenv
“`
langchain-ollama 是 LangChain 官方提供的 Ollama 集成模块,pypdf 用于读取 PDF 文件。
### 2. 下载运行 Ollama
根据你的操作系统,前往 https://ollama.com 下载并安装 Ollama。安装完成后运行:
“`bash
ollama pull qwen:7b
“`
这会下载 Qwen 7B 模型(大约 4GB,首次下载需要几分钟)。如果显卡够好,也可以选择 qwen:14b 或更大的模型。
然后在另一个终端启动 Ollama 服务:
“`bash
ollama serve
“`
### 3. 准备知识库
在项目根目录下创建 `docs` 文件夹,放入测试文档:
“`bash
mkdir docs
cat > docs/公司介绍.txt << EOF
## 公司介绍
创新科技成立于 2020 年,总部位于北京。
我们的核心产品包括:
- AI 智能助手
- 企业知识管理平台
- 自动化办公系统
2023 年年度营收为 5000 万元人民币,团队规模 100 人。
EOF
```
可以放入任意多的文档,支持 .txt、.md、.pdf 等格式。
### 4. 编写 RAG 核心代码
创建 `rag_app.py`:
```python
#!/usr/bin/env python3
"""本地 RAG 问答系统 - 基于 LangChain + Ollama"""
import os
from pathlib import Path
from langchain_community.document_loaders import TextLoader, PyPDFLoader, UnstructuredMarkdownLoader
from langchain_community.embeddings import OllamaEmbeddings
from langchain_community.vectorstores import Chroma
from langchain_community.chat_models import ChatOllama
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
# 配置参数
LLM_MODEL = "qwen:7b"
EMBED_MODEL = "nomic-embed-text"
CHROMA_PATH = "./chroma_db"
DOCS_PATH = "./docs"
def load_documents(doc_path: str):
"""加载指定目录下的所有文档"""
documents = []
path = Path(doc_path)
for file in path.rglob("*"):
if file.is_file():
suffix = file.suffix.lower()
try:
if suffix == ".txt":
loader = TextLoader(str(file), encoding="utf-8")
elif suffix == ".md":
loader = UnstructuredMarkdownLoader(str(file))
elif suffix == ".pdf":
loader = PyPDFLoader(str(file))
else:
continue
docs = loader.load()
for doc in docs:
doc.metadata["source"] = str(file)
documents.extend(docs)
print(f"加载: {file.name}")
except Exception as e:
print(f"加载失败 {file.name}: {e}")
return documents
def create_vectorstore(documents):
"""创建向量数据库"""
embeddings = OllamaEmbeddings(
model=EMBED_MODEL,
base_url="http://localhost:11434"
)
if os.path.exists(CHROMA_PATH):
print("加载已有向量数据库...")
vectorstore = Chroma(
persist_directory=CHROMA_PATH,
embedding_function=embeddings
)
else:
print("创建新的向量数据库...")
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50
)
splits = text_splitter.split_documents(documents)
vectorstore = Chroma.from_documents(
documents=splits,
embedding=embeddings,
persist_directory=CHROMA_PATH
)
return vectorstore
def build_qa_chain(vectorstore):
"""构建问答链"""
llm = ChatOllama(
model=LLM_MODEL,
base_url="http://localhost:11434",
temperature=0.7
)
retriever = vectorstore.as_retriever(
search_type="similarity",
search_kwargs={"k": 3}
)
combine_docs_chain = create_stuff_documents_chain(llm)
qa_chain = create_retrieval_chain(retriever, combine_docs_chain)
return qa_chain
def main():
print("=" * 50)
print("本地 RAG 知识问答系统")
print("=" * 50)
print("\n[1/4] 加载文档...")
documents = load_documents(DOCS_PATH)
print(f"共加载 {len(documents)} 个文档")
print("\n[2/4] 构建向量数据库...")
vectorstore = create_vectorstore(documents)
print(f"向量数据库就绪")
print("\n[3/4] 初始化问答系统...")
qa_chain = build_qa_chain(vectorstore)
print("问答系统就绪\n")
print("-" * 50)
print("你可以开始提问了(输入 quit 退出)")
print("-" * 50)
while True:
question = input("\n问题: ").strip()
if not question:
continue
if question.lower() in ["quit", "q", "exit"]:
print("再见!")
break
result = qa_chain.invoke({"input": question})
print(f"\n回答: {result[\"answer\"]}")
if "context" in result:
print("\n参考来源:")
for i, doc in enumerate(result["context"][:3], 1):
source = doc.metadata.get("source", "未知")
print(f" {i}. {source}")
if __name__ == "__main__":
main()
```
### 5. 下载 Embedding 模型
首次运行前需要下载 Embedding 模型:
```bash
ollama pull nomic-embed-text
```
这是专门针对中文优化的 Embedding 模型,效果比原来更好。
### 6. 运行系统
```bash
python rag_app.py
```
## 运行结果
系统启动后显示:
```
==================================================
本地 RAG 知识问答系统
==================================================
[1/4] 加载文档...
加载: 公司介绍.txt
共加载 1 个文档
[2/4] 构建向量数据库...
创建新的向量数据库...
向量数据库就绪
[3/4] 初始化问答系统...
问答系统就绪
--------------------------------------------------
你可以开始提问了(输入 quit 退出)
--------------------------------------------------
```
然后可以开始提问:
```
问题: 公司的年营收是多少?
回答: 根据提供的信息,创新科技2023年的年度营收为5000万元人民币。
参考来源:
1. 公司介绍.txt
```
再试一个问题:
```
问题: 公司有多少人?
回答: 公司团队规模为100人。
参考来源:
1. 公司介绍.txt
```
系统正确地从知识库中检索到了相关信息,并基于这些信息生成了回答。
## 总结
这篇文章我们完成了:
1. **理解了 RAG 的原理**——让 AI 在回答前先检索你的文档
2. **搭建了完整的技术栈**——LangChain + Ollama + Chroma 向量数据库
3. **实现了核心功能**——文档加载、向量化存储、相似度搜索、问答生成
4. **提供了完整的代码**——可以直接运行的项目代码
整个系统完全在本地运行,不依赖任何外部 API。文档不会离开你的电脑,隐私有保障。
后续优化方向:
- 使用更快的中文 Embedding 模型
- 增加增量更新知识库的功能
- 添加对话历史记录
- 支持更多文档格式
如果运行过程中遇到问题,评论区见。